diff options
Diffstat (limited to 'railties')
371 files changed, 31957 insertions, 0 deletions
diff --git a/railties/.gitignore b/railties/.gitignore new file mode 100644 index 0000000000..80dd262d2f --- /dev/null +++ b/railties/.gitignore @@ -0,0 +1 @@ +log/ diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md new file mode 100644 index 0000000000..b9d2db773a --- /dev/null +++ b/railties/CHANGELOG.md @@ -0,0 +1,427 @@ +* The generated config file for the development environment includes a new + config line, commented out, showing how to enable the evented file watcher. + + *Xavier Noria* + +* `config.debug_exception_response_format` configures the format used + in responses when errors occur in development mode. + + Set `config.debug_exception_response_format` to render an HTML page with + debug info (using the value `:default`) or render debug info preserving + the response format (using the value `:api`). + + *Jorge Bejar* + +* Fix setting exit status code for rake test tasks. The exit status code + was not set when tests were fired with `rake`. Now, it is being set and it matches + behavior of running tests via `rails` command (`rails test`), so no matter if + `rake test` or `rails test` command is used the exit code will be set. + + *Arkadiusz Fal* + +* Add Command infrastructure to replace rake. + + Also move `rake dev:cache` to new infrastructure. You'll need to use + `rails dev:cache` to toggle development caching from now on. + + *Chuck Callebs* + +* Allow use of minitest-rails gem with Rails test runner. + + Fixes #22455. + + *Chris Kottom* + +* Add `bin/test` script to rails plugin. + + `bin/test` can use the same API as `bin/rails test`. + + *Yuji Yaginuma* + +* Make `static_index` part of the `config.public_file_server` config and + call it `public_file_server.index_name`. + + *Yuki Nishijima* + +* Deprecate `serve_static_files` in favor of `public_file_server.enabled`. + + Unifies the static asset options under `public_file_server`. + + To upgrade, replace occurrences of: + + ``` + config.serve_static_files = # false or true + ``` + + in your environment files, with: + + ``` + config.public_file_server.enabled = # false or true + ``` + + *Kasper Timm Hansen* + +* Deprecate `config.static_cache_control` in favor of + `config.public_file_server.headers`. + + To upgrade, replace occurrences of: + + ``` + config.static_cache_control = 'public, max-age=60' + ``` + + in your environment files, with: + + ``` + config.public_file_server.headers = { + 'Cache-Control' => 'public, max-age=60' + } + ``` + + `config.public_file_server.headers` can set arbitrary headers, sent along when + a response is delivered. + + *Yuki Nishijima* + +* Route generator should be idempotent + running generators several times no longer require you to cleanup routes.rb + + *Thiago Pinto* + +* Allow passing an environment to `config_for`. + + *Simon Eskildsen* + +* Allow rake:stats to account for rake tasks in lib/tasks + + *Kevin Deisz* + +* Added javascript to update the URL on mailer previews with the currently + selected email format. Reloading the page now keeps you on your selected + format rather than going back to the default html version. + + *James Kerr* + +* Add fail fast to `bin/rails test` + + Adding `--fail-fast` or `-f` when running tests will interrupt the run on + the first failure: + + ``` + # Running: + + ................................................S......E + + ArgumentError: Wups! Bet you didn't expect this! + test/models/bunny_test.rb:19:in `block in <class:BunnyTest>' + + bin/rails test test/models/bunny_test.rb:18 + + ....................................F + + This failed + + bin/rails test test/models/bunny_test.rb:14 + + Interrupted. Exiting... + + + Finished in 0.051427s, 1808.3872 runs/s, 1769.4972 assertions/s. + + ``` + + Note that any unexpected errors don't abort the run. + + *Kasper Timm Hansen* + +* Add inline output to `bin/rails test` + + Any failures or errors (and skips if running in verbose mode) are output + during a test run: + + ``` + # Running: + + .....S..........................................F + + This failed + + bin/rails test test/models/bunny_test.rb:14 + + .................................E + + ArgumentError: Wups! Bet you didn't expect this! + test/models/bunny_test.rb:19:in `block in <class:BunnyTest>' + + bin/rails test test/models/bunny_test.rb:18 + + .................... + + Finished in 0.069708s, 1477.6019 runs/s, 1448.9106 assertions/s. + ``` + + Output can be deferred to after a run with the `--defer-output` option. + + *Kasper Timm Hansen* + +* Fix displaying mailer previews on non local requests when config + `action_mailer.show_previews` is set + + *Wojciech Wnętrzak* + +* `rails server` will now honour the `PORT` environment variable + + *David Cornu* + +* Plugins generated using `rails plugin new` are now generated with the + version number set to 0.1.0. + + *Daniel Morris* + +* `I18n.load_path` is now reloaded under development so there's no need to + restart the server to make new locale files available. Also, I18n will no + longer raise for deleted locale files. + + *Kir Shatrov* + +* Add `bin/update` script to update development environment automatically. + + *Mehmet Emin İNAÇ* + +* Fix STATS_DIRECTORIES already defined warning when running rake from within + the top level directory of an engine that has a test app. + + Fixes #20510 + + *Ersin Akinci* + +* Make enabling or disabling caching in development mode possible with + rake dev:cache. + + Running rake dev:cache will create or remove tmp/caching-dev.txt. When this + file exists config.action_controller.perform_caching will be set to true in + config/environments/development.rb. + + Additionally, a server can be started with either --dev-caching or + --no-dev-caching included to toggle caching on startup. + + *Jussi Mertanen*, *Chuck Callebs* + +* Add a `--api` option in order to generate plugins that can be added + inside an API application. + + *Robin Dupret* + +* Fix `NoMethodError` when generating a scaffold inside a full engine. + + *Yuji Yaginuma* + +* Adding support for passing a block to the `add_source` action of a custom generator + + *Mike Dalton*, *Hirofumi Wakasugi* + +* `assert_file` understands paths with special characters + (eg. `v0.1.4~alpha+nightly`). + + *Diego Carrion* + +* Remove ContentLength middleware from the defaults. If you want it, just + add it as a middleware in your config. + + *Egg McMuffin* + +* Make it possible to customize the executable inside rerun snippets. + + *Yves Senn* + +* 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* + +* Generator a `.keep` file in the `tmp` folder by default as many scripts + assume the existence of this folder and most would fail if it is absent. + + See #20299. + + *Yoong Kang Lim*, *Sunny Juneja* + +* `config.static_index` configures directory `index.html` filename + + Set `config.static_index` to serve a static directory index file not named + `index`. E.g. to serve `main.html` instead of `index.html` for directory + requests, set `config.static_index` to `"main"`. + + *Eliot Sykes* + +* `bin/setup` uses built-in rake tasks (`log:clear`, `tmp:clear`). + + *Mohnish Thallavajhula* + +* Fix mailer previews with attachments by using the mail gem's own API to + locate the first part of the correct mime type. + + Fixes #14435. + + *Andrew White* + +* Remove sqlite support from `rails dbconsole`. + + *Andrew White* + +* Rename `railties/bin` to `railties/exe` to match the new Bundler executables + convention. + + *Islam Wazery* + +* Print `bundle install` output in `rails new` as soon as it's available. + + Running `rails new` will now print the output of `bundle install` as + it is available, instead of waiting until all gems finish installing. + + *Max Holder* + +* Respect `pluralize_table_names` when generating fixture file. + + Fixes #19519. + + *Yuji Yaginuma* + +* Add a new-line to the end of route method generated code. + + We need to add a `\n`, because we cannot have two routes + in the same line. + + *arthurnn* + +* Add `rake initializers`. + + This task prints out all defined initializers in the order they are invoked + by Rails. This is helpful for debugging issues related to the initialization + process. + + *Naoto Kaneko* + +* Created rake restart task. Restarts your Rails app by touching the + `tmp/restart.txt`. + + Fixes #18876. + + *Hyonjee Joo* + +* Add `config/initializers/active_record_belongs_to_required_by_default.rb`. + + Newly generated Rails apps have a new initializer called + `active_record_belongs_to_required_by_default.rb` which sets the value of + the configuration option `config.active_record.belongs_to_required_by_default` + to `true` when ActiveRecord is not skipped. + + As a result, new Rails apps require `belongs_to` association on model + to be valid. + + This initializer is *not* added when running `rake rails:update`, so + old apps ported to Rails 5 will work without any change. + + *Josef Šimánek* + +* `delete` operations in configurations are run last in order to eliminate + 'No such middleware' errors when `insert_before` or `insert_after` are added + after the `delete` operation for the middleware being deleted. + + Fixes #16433. + + *Guo Xiang Tan* + +* Newly generated applications get a `README.md` in Markdown. + + *Xavier Noria* + +* Remove the documentation tasks `doc:app`, `doc:rails`, and `doc:guides`. + + *Xavier Noria* + +* Force generated routes to be inserted into `config/routes.rb`. + + *Andrew White* + +* Don't remove all line endings from `config/routes.rb` when revoking scaffold. + + Fixes #15913. + + *Andrew White* + +* Rename `--skip-test-unit` option to `--skip-test` in app generator + + *Melanie Gilman* + +* Add the `method_source` gem to the default Gemfile for apps. + + *Sean Griffin* + +* Drop old test locations from `rake stats`: + + - test/functional + - test/unit + + *Ravil Bayramgalin* + +* Update `rake stats` to correctly count declarative tests + as methods in `_test.rb` files. + + *Ravil Bayramgalin* + +* Remove deprecated `test:all` and `test:all:db` tasks. + + *Rafael Mendonça França* + +* Remove deprecated `Rails::Rack::LogTailer`. + + *Rafael Mendonça França* + +* Remove deprecated `RAILS_CACHE` constant. + + *Rafael Mendonça França* + +* Remove deprecated `serve_static_assets` configuration. + + *Rafael Mendonça França* + +* Use local variables in `_form.html.erb` partial generated by scaffold. + + *Andrew Kozlov* + +* Add `config/initializers/callback_terminator.rb`. + + Newly generated Rails apps have a new initializer called + `callback_terminator.rb` which sets the value of the configuration option + `ActiveSupport.halt_callback_chains_on_return_false` to `false`. + + As a result, new Rails apps do not halt Active Record and Active Model + callback chains when a callback returns `false`; only when they are + explicitly halted with `throw(:abort)`. + + The terminator is *not* added when running `rake rails:update`, so returning + `false` will still work on old apps ported to Rails 5, displaying a + deprecation warning to prompt users to update their code to the new syntax. + + *claudiob* + +* Generated fixtures won't use the id when generated with references attributes. + + *Pablo Olmos de Aguilera Corradini* + +* Add `--skip-action-mailer` option to the app generator. + + *claudiob* + +* Autoload any second level directories called `app/*/concerns`. + + *Alex Robbin* + +Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/railties/CHANGELOG.md) for previous changes. diff --git a/railties/MIT-LICENSE b/railties/MIT-LICENSE new file mode 100644 index 0000000000..7c2197229d --- /dev/null +++ b/railties/MIT-LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2004-2015 David Heinemeier Hansson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file diff --git a/railties/RDOC_MAIN.rdoc b/railties/RDOC_MAIN.rdoc new file mode 100644 index 0000000000..8d847eaa1c --- /dev/null +++ b/railties/RDOC_MAIN.rdoc @@ -0,0 +1,73 @@ +== Welcome to \Rails + +\Rails is a web-application framework that includes everything needed to create +database-backed web applications according to the {Model-View-Controller (MVC)}[http://en.wikipedia.org/wiki/Model-view-controller] pattern. + +Understanding the MVC pattern is key to understanding \Rails. MVC divides your application +into three layers, each with a specific responsibility. + +The View layer is composed of "templates" that are responsible for providing +appropriate representations of your application's resources. Templates +can come in a variety of formats, but most view templates are \HTML with embedded Ruby +code (.erb files). + +The Model layer represents your domain model (such as Account, Product, Person, Post) +and encapsulates the business logic that is specific to your application. In \Rails, +database-backed model classes are derived from ActiveRecord::Base. Active Record allows +you to present the data from database rows as objects and embellish these data objects +with business logic methods. Although most \Rails models are backed by a database, models +can also be ordinary Ruby classes, or Ruby classes that implement a set of interfaces as +provided by the ActiveModel module. You can read more about Active Record in its +{README}[link:files/activerecord/README_rdoc.html]. + +The Controller layer is responsible for handling incoming HTTP requests and providing a +suitable response. Usually this means returning \HTML, but \Rails controllers can also +generate XML, JSON, PDFs, mobile-specific views, and more. Controllers manipulate models +and render view templates in order to generate the appropriate HTTP response. + +In \Rails, the Controller and View layers are handled together by Action Pack. +These two layers are bundled in a single package due to their heavy interdependence. +This is unlike the relationship between Active Record and Action Pack, which are +independent. Each of these packages can be used independently outside of \Rails. You +can read more about Action Pack in its {README}[link:files/actionpack/README_rdoc.html]. + +== Getting Started + +1. Install \Rails at the command prompt if you haven't yet: + + gem install rails + +2. At the command prompt, create a new \Rails application: + + rails new myapp + + where "myapp" is the application name. + +3. Change directory to +myapp+ and start the web server: + + cd myapp; rails server + + Run with <tt>--help</tt> or <tt>-h</tt> for options. + +4. Go to http://localhost:3000 and you'll see: + + "Welcome aboard: You're riding Ruby on Rails!" + +5. Follow the guidelines to start developing your application. You may find the following resources handy: + +* The \README file created within your application. +* {Getting Started with \Rails}[http://guides.rubyonrails.org/getting_started.html]. +* {Ruby on \Rails Tutorial}[http://ruby.railstutorial.org/ruby-on-rails-tutorial-book]. +* {Ruby on \Rails Guides}[http://guides.rubyonrails.org]. +* {The API Documentation}[http://api.rubyonrails.org]. + +== Contributing + +We encourage you to contribute to Ruby on \Rails! Please check out the {Contributing to Rails +guide}[http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html] for guidelines about how +to proceed. {Join us}[http://contributors.rubyonrails.org]! + + +== License + +Ruby on \Rails is released under the {MIT License}[http://www.opensource.org/licenses/MIT]. diff --git a/railties/README.rdoc b/railties/README.rdoc new file mode 100644 index 0000000000..a25658668c --- /dev/null +++ b/railties/README.rdoc @@ -0,0 +1,41 @@ += Railties -- Gluing the Engine to the Rails + +Railties is responsible for gluing all frameworks together. Overall, it: + +* handles the bootstrapping process for a Rails application; + +* manages the +rails+ command line interface; + +* and provides the Rails generators core. + + +== Download + +The latest version of Railties can be installed with RubyGems: + +* gem install railties + +Source code can be downloaded as part of the Rails project on GitHub + +* https://github.com/rails/rails/tree/master/railties + +== License + +Railties is released under the MIT license: + +* http://www.opensource.org/licenses/MIT + +== Support + +API documentation is at + +* http://api.rubyonrails.org + +Bug reports can be filed for the Ruby on Rails project here: + +* https://github.com/rails/rails/issues + +Feature requests should be discussed on the rails-core mailing list here: + +* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core + diff --git a/railties/Rakefile b/railties/Rakefile new file mode 100644 index 0000000000..cf130a5f14 --- /dev/null +++ b/railties/Rakefile @@ -0,0 +1,32 @@ +require 'rake/testtask' + +task :default => :test + +desc "Run all unit tests" +task :test => 'test:isolated' + +namespace :test do + task :isolated do + dirs = (ENV["TEST_DIR"] || ENV["TEST_DIRS"] || "**").split(",") + test_files = dirs.map { |dir| "test/#{dir}/*_test.rb" } + Dir[*test_files].each do |file| + next true if file.include?("fixtures") + dash_i = [ + 'test', + 'lib', + "#{File.dirname(__FILE__)}/../activesupport/lib", + "#{File.dirname(__FILE__)}/../actionpack/lib", + "#{File.dirname(__FILE__)}/../activemodel/lib" + ] + ruby "-w", "-I#{dash_i.join ':'}", file + end + end +end + +Rake::TestTask.new('test:regular') do |t| + t.libs << 'test' << "#{File.dirname(__FILE__)}/../activesupport/lib" + t.pattern = 'test/**/*_test.rb' + t.warning = false + t.verbose = true + t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) +end diff --git a/railties/exe/rails b/railties/exe/rails new file mode 100755 index 0000000000..82c17cabce --- /dev/null +++ b/railties/exe/rails @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby + +git_path = File.expand_path('../../../.git', __FILE__) + +if File.exist?(git_path) + railties_path = File.expand_path('../../lib', __FILE__) + $:.unshift(railties_path) +end +require "rails/cli" diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb new file mode 100644 index 0000000000..fe789f3c2a --- /dev/null +++ b/railties/lib/rails.rb @@ -0,0 +1,111 @@ +require 'rails/ruby_version_check' + +require 'pathname' + +require 'active_support' +require 'active_support/dependencies/autoload' +require 'active_support/core_ext/kernel/reporting' +require 'active_support/core_ext/module/delegation' +require 'active_support/core_ext/array/extract_options' + +require 'rails/application' +require 'rails/version' + +require 'active_support/railtie' +require 'action_dispatch/railtie' + +# UTF-8 is the default internal and external encoding. +silence_warnings do + Encoding.default_external = Encoding::UTF_8 + Encoding.default_internal = Encoding::UTF_8 +end + +module Rails + extend ActiveSupport::Autoload + + autoload :Info + autoload :InfoController + autoload :MailersController + autoload :WelcomeController + + class << self + @application = @app_class = nil + + attr_writer :application + attr_accessor :app_class, :cache, :logger + def application + @application ||= (app_class.instance if app_class) + end + + delegate :initialize!, :initialized?, to: :application + + # The Configuration instance used to configure the Rails environment + def configuration + application.config + end + + def backtrace_cleaner + @backtrace_cleaner ||= begin + # Relies on Active Support, so we have to lazy load to postpone definition until AS has been loaded + require 'rails/backtrace_cleaner' + Rails::BacktraceCleaner.new + end + end + + # Returns a Pathname object of the current rails project, + # otherwise it returns nil if there is no project: + # + # Rails.root + # # => #<Pathname:/Users/someuser/some/path/project> + def root + application && application.config.root + end + + # Returns the current Rails environment. + # + # Rails.env # => "development" + # Rails.env.development? # => true + # Rails.env.production? # => false + def env + @_env ||= ActiveSupport::StringInquirer.new(ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development") + end + + # Sets the Rails environment. + # + # Rails.env = "staging" # => "staging" + def env=(environment) + @_env = ActiveSupport::StringInquirer.new(environment) + end + + # Returns all rails groups for loading based on: + # + # * The Rails environment; + # * The environment variable RAILS_GROUPS; + # * The optional envs given as argument and the hash with group dependencies; + # + # groups assets: [:development, :test] + # + # # Returns + # # => [:default, :development, :assets] for Rails.env == "development" + # # => [:default, :production] for Rails.env == "production" + def groups(*groups) + hash = groups.extract_options! + env = Rails.env + groups.unshift(:default, env) + groups.concat ENV["RAILS_GROUPS"].to_s.split(",") + groups.concat hash.map { |k, v| k if v.map(&:to_s).include?(env) } + groups.compact! + groups.uniq! + groups + end + + # Returns a Pathname object of the public folder of the current + # rails project, otherwise it returns nil if there is no project: + # + # Rails.public_path + # # => #<Pathname:/Users/someuser/some/path/project/public> + def public_path + application && Pathname.new(application.paths["public"].first) + end + end +end diff --git a/railties/lib/rails/all.rb b/railties/lib/rails/all.rb new file mode 100644 index 0000000000..45361fca83 --- /dev/null +++ b/railties/lib/rails/all.rb @@ -0,0 +1,16 @@ +require "rails" + +%w( + active_record + action_controller + action_view + action_mailer + active_job + rails/test_unit + sprockets +).each do |framework| + begin + require "#{framework}/railtie" + rescue LoadError + end +end diff --git a/railties/lib/rails/api/task.rb b/railties/lib/rails/api/task.rb new file mode 100644 index 0000000000..a082932632 --- /dev/null +++ b/railties/lib/rails/api/task.rb @@ -0,0 +1,156 @@ +require 'rdoc/task' + +module Rails + module API + class Task < RDoc::Task + RDOC_FILES = { + 'activesupport' => { + :include => %w( + README.rdoc + lib/active_support/**/*.rb + ), + :exclude => 'lib/active_support/vendor/*' + }, + + 'activerecord' => { + :include => %w( + README.rdoc + lib/active_record/**/*.rb + ) + }, + + 'activemodel' => { + :include => %w( + README.rdoc + lib/active_model/**/*.rb + ) + }, + + 'actionpack' => { + :include => %w( + README.rdoc + lib/abstract_controller/**/*.rb + lib/action_controller/**/*.rb + lib/action_dispatch/**/*.rb + ) + }, + + 'actionview' => { + :include => %w( + README.rdoc + lib/action_view/**/*.rb + ), + :exclude => 'lib/action_view/vendor/*' + }, + + 'actionmailer' => { + :include => %w( + README.rdoc + lib/action_mailer/**/*.rb + ) + }, + + 'activejob' => { + :include => %w( + README.md + lib/active_job/**/*.rb + ) + }, + + 'railties' => { + :include => %w( + README.rdoc + lib/**/*.rb + ), + :exclude => 'lib/rails/generators/rails/**/templates/**/*.rb' + } + } + + def initialize(name) + super + + # Every time rake runs this task is instantiated as all the rest. + # Be lazy computing stuff to have as light impact as possible to + # the rest of tasks. + before_running_rdoc do + load_and_configure_sdoc + configure_rdoc_files + setup_horo_variables + end + end + + # Hack, ignore the desc calls performed by the original initializer. + def desc(description) + # no-op + end + + def load_and_configure_sdoc + require 'sdoc' + + self.title = 'Ruby on Rails API' + self.rdoc_dir = api_dir + + options << '-m' << api_main + options << '-e' << 'UTF-8' + + options << '-f' << 'sdoc' + options << '-T' << 'rails' + rescue LoadError + $stderr.puts %(Unable to load SDoc, please add\n\n gem 'sdoc', require: false\n\nto the Gemfile.) + exit 1 + end + + def configure_rdoc_files + rdoc_files.include(api_main) + + RDOC_FILES.each do |component, cfg| + cdr = component_root_dir(component) + + Array(cfg[:include]).each do |pattern| + rdoc_files.include("#{cdr}/#{pattern}") + end + + Array(cfg[:exclude]).each do |pattern| + rdoc_files.exclude("#{cdr}/#{pattern}") + end + end + end + + def setup_horo_variables + ENV['HORO_PROJECT_NAME'] = 'Ruby on Rails' + ENV['HORO_PROJECT_VERSION'] = rails_version + end + + def api_main + component_root_dir('railties') + '/RDOC_MAIN.rdoc' + end + end + + class RepoTask < Task + def load_and_configure_sdoc + super + options << '-g' # link to GitHub, SDoc flag + end + + def component_root_dir(component) + component + end + + def api_dir + 'doc/rdoc' + end + end + + class EdgeTask < RepoTask + def rails_version + "master@#{`git rev-parse HEAD`[0, 7]}" + end + end + + class StableTask < RepoTask + def rails_version + File.read('RAILS_VERSION').strip + end + end + end +end diff --git a/railties/lib/rails/app_loader.rb b/railties/lib/rails/app_loader.rb new file mode 100644 index 0000000000..a9fe21824e --- /dev/null +++ b/railties/lib/rails/app_loader.rb @@ -0,0 +1,64 @@ +require 'pathname' +require 'rails/version' + +module Rails + module AppLoader # :nodoc: + extend self + + RUBY = Gem.ruby + EXECUTABLES = ['bin/rails', 'script/rails'] + BUNDLER_WARNING = <<EOS +Looks like your app's ./bin/rails is a stub that was generated by Bundler. + +In Rails #{Rails::VERSION::MAJOR}, your app's bin/ directory contains executables that are versioned +like any other source code, rather than stubs that are generated on demand. + +Here's how to upgrade: + + bundle config --delete bin # Turn off Bundler's stub generator + rake rails:update:bin # Use the new Rails 4 executables + git add bin # Add bin/ to source control + +You may need to remove bin/ from your .gitignore as well. + +When you install a gem whose executable you want to use in your app, +generate it and add it to source control: + + bundle binstubs some-gem-name + git add bin/new-executable + +EOS + + def exec_app + original_cwd = Dir.pwd + + loop do + if exe = find_executable + contents = File.read(exe) + + if contents =~ /(APP|ENGINE)_PATH/ + exec RUBY, exe, *ARGV + break # non reachable, hack to be able to stub exec in the test suite + elsif exe.end_with?('bin/rails') && contents.include?('This file was generated by Bundler') + $stderr.puts(BUNDLER_WARNING) + Object.const_set(:APP_PATH, File.expand_path('config/application', Dir.pwd)) + require File.expand_path('../boot', APP_PATH) + require 'rails/commands' + break + end + end + + # If we exhaust the search there is no executable, this could be a + # call to generate a new application, so restore the original cwd. + Dir.chdir(original_cwd) and return if Pathname.new(Dir.pwd).root? + + # Otherwise keep moving upwards in search of an executable. + Dir.chdir('..') + end + end + + def find_executable + EXECUTABLES.find { |exe| File.file?(exe) } + end + end +end diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb new file mode 100644 index 0000000000..77efe3248d --- /dev/null +++ b/railties/lib/rails/application.rb @@ -0,0 +1,525 @@ +require 'fileutils' +require 'yaml' +require 'active_support/core_ext/hash/keys' +require 'active_support/core_ext/object/blank' +require 'active_support/key_generator' +require 'active_support/message_verifier' +require 'rails/engine' + +module Rails + # An Engine with the responsibility of coordinating the whole boot process. + # + # == Initialization + # + # Rails::Application is responsible for executing all railties and engines + # initializers. It also executes some bootstrap initializers (check + # Rails::Application::Bootstrap) and finishing initializers, after all the others + # are executed (check Rails::Application::Finisher). + # + # == Configuration + # + # Besides providing the same configuration as Rails::Engine and Rails::Railtie, + # the application object has several specific configurations, for example + # "cache_classes", "consider_all_requests_local", "filter_parameters", + # "logger" and so forth. + # + # Check Rails::Application::Configuration to see them all. + # + # == Routes + # + # The application object is also responsible for holding the routes and reloading routes + # whenever the files change in development. + # + # == Middlewares + # + # The Application is also responsible for building the middleware stack. + # + # == Booting process + # + # The application is also responsible for setting up and executing the booting + # process. From the moment you require "config/application.rb" in your app, + # the booting process goes like this: + # + # 1) require "config/boot.rb" to setup load paths + # 2) require railties and engines + # 3) Define Rails.application as "class MyApp::Application < Rails::Application" + # 4) Run config.before_configuration callbacks + # 5) Load config/environments/ENV.rb + # 6) Run config.before_initialize callbacks + # 7) Run Railtie#initializer defined by railties, engines and application. + # One by one, each engine sets up its load paths, routes and runs its config/initializers/* files. + # 8) Custom Railtie#initializers added by railties, engines and applications are executed + # 9) Build the middleware stack and run to_prepare callbacks + # 10) Run config.before_eager_load and eager_load! if eager_load is true + # 11) Run config.after_initialize callbacks + # + # == Multiple Applications + # + # If you decide to define multiple applications, then the first application + # that is initialized will be set to +Rails.application+, unless you override + # it with a different application. + # + # To create a new application, you can instantiate a new instance of a class + # that has already been created: + # + # class Application < Rails::Application + # end + # + # first_application = Application.new + # second_application = Application.new(config: first_application.config) + # + # In the above example, the configuration from the first application was used + # to initialize the second application. You can also use the +initialize_copy+ + # on one of the applications to create a copy of the application which shares + # the configuration. + # + # If you decide to define rake tasks, runners, or initializers in an + # application other than +Rails.application+, then you must run those + # these manually. + class Application < Engine + autoload :Bootstrap, 'rails/application/bootstrap' + autoload :Configuration, 'rails/application/configuration' + autoload :DefaultMiddlewareStack, 'rails/application/default_middleware_stack' + autoload :Finisher, 'rails/application/finisher' + autoload :Railties, 'rails/engine/railties' + autoload :RoutesReloader, 'rails/application/routes_reloader' + + class << self + def inherited(base) + super + Rails.app_class = base + add_lib_to_load_path!(find_root(base.called_from)) + end + + def instance + super.run_load_hooks! + end + + def create(initial_variable_values = {}, &block) + new(initial_variable_values, &block).run_load_hooks! + end + + def find_root(from) + find_root_with_flag "config.ru", from, Dir.pwd + end + + # Makes the +new+ method public. + # + # Note that Rails::Application inherits from Rails::Engine, which + # inherits from Rails::Railtie and the +new+ method on Rails::Railtie is + # private + public :new + end + + attr_accessor :assets, :sandbox + alias_method :sandbox?, :sandbox + attr_reader :reloaders + + delegate :default_url_options, :default_url_options=, to: :routes + + INITIAL_VARIABLES = [:config, :railties, :routes_reloader, :reloaders, + :routes, :helpers, :app_env_config, :secrets] # :nodoc: + + def initialize(initial_variable_values = {}, &block) + super() + @initialized = false + @reloaders = [] + @routes_reloader = nil + @app_env_config = nil + @ordered_railties = nil + @railties = nil + @message_verifiers = {} + @ran_load_hooks = false + + # are these actually used? + @initial_variable_values = initial_variable_values + @block = block + end + + # Returns true if the application is initialized. + def initialized? + @initialized + end + + def run_load_hooks! # :nodoc: + return self if @ran_load_hooks + @ran_load_hooks = true + ActiveSupport.run_load_hooks(:before_configuration, self) + + @initial_variable_values.each do |variable_name, value| + if INITIAL_VARIABLES.include?(variable_name) + instance_variable_set("@#{variable_name}", value) + end + end + + instance_eval(&@block) if @block + self + end + + # Reload application routes regardless if they changed or not. + def reload_routes! + routes_reloader.reload! + end + + # Returns the application's KeyGenerator + def key_generator + # number of iterations selected based on consultation with the google security + # team. Details at https://github.com/rails/rails/pull/6952#issuecomment-7661220 + @caching_key_generator ||= + if secrets.secret_key_base + unless secrets.secret_key_base.kind_of?(String) + raise ArgumentError, "`secret_key_base` for #{Rails.env} environment must be a type of String, change this value in `config/secrets.yml`" + end + key_generator = ActiveSupport::KeyGenerator.new(secrets.secret_key_base, iterations: 1000) + ActiveSupport::CachingKeyGenerator.new(key_generator) + else + ActiveSupport::LegacyKeyGenerator.new(secrets.secret_token) + end + end + + # Returns a message verifier object. + # + # This verifier can be used to generate and verify signed messages in the application. + # + # It is recommended not to use the same verifier for different things, so you can get different + # verifiers passing the +verifier_name+ argument. + # + # ==== Parameters + # + # * +verifier_name+ - the name of the message verifier. + # + # ==== Examples + # + # message = Rails.application.message_verifier('sensitive_data').generate('my sensible data') + # Rails.application.message_verifier('sensitive_data').verify(message) + # # => 'my sensible data' + # + # See the +ActiveSupport::MessageVerifier+ documentation for more information. + def message_verifier(verifier_name) + @message_verifiers[verifier_name] ||= begin + secret = key_generator.generate_key(verifier_name.to_s) + ActiveSupport::MessageVerifier.new(secret) + end + end + + # Convenience for loading config/foo.yml for the current Rails env. + # + # Example: + # + # # config/exception_notification.yml: + # production: + # url: http://127.0.0.1:8080 + # namespace: my_app_production + # development: + # url: http://localhost:3001 + # namespace: my_app_development + # + # # config/production.rb + # Rails.application.configure do + # config.middleware.use ExceptionNotifier, config_for(:exception_notification) + # end + def config_for(name, env: Rails.env) + yaml = Pathname.new("#{paths["config"].existent.first}/#{name}.yml") + + if yaml.exist? + require "erb" + (YAML.load(ERB.new(yaml.read).result) || {})[env] || {} + else + raise "Could not load configuration. No such file - #{yaml}" + end + rescue Psych::SyntaxError => e + raise "YAML syntax error occurred while parsing #{yaml}. " \ + "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \ + "Error: #{e.message}" + end + + # Stores some of the Rails initial environment parameters which + # will be used by middlewares and engines to configure themselves. + def env_config + @app_env_config ||= begin + validate_secret_key_config! + + super.merge({ + "action_dispatch.parameter_filter" => config.filter_parameters, + "action_dispatch.redirect_filter" => config.filter_redirect, + "action_dispatch.secret_token" => secrets.secret_token, + "action_dispatch.secret_key_base" => secrets.secret_key_base, + "action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions, + "action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local, + "action_dispatch.logger" => Rails.logger, + "action_dispatch.backtrace_cleaner" => Rails.backtrace_cleaner, + "action_dispatch.key_generator" => key_generator, + "action_dispatch.http_auth_salt" => config.action_dispatch.http_auth_salt, + "action_dispatch.signed_cookie_salt" => config.action_dispatch.signed_cookie_salt, + "action_dispatch.encrypted_cookie_salt" => config.action_dispatch.encrypted_cookie_salt, + "action_dispatch.encrypted_signed_cookie_salt" => config.action_dispatch.encrypted_signed_cookie_salt, + "action_dispatch.cookies_serializer" => config.action_dispatch.cookies_serializer, + "action_dispatch.cookies_digest" => config.action_dispatch.cookies_digest + }) + end + end + + # If you try to define a set of rake tasks on the instance, these will get + # passed up to the rake tasks defined on the application's class. + def rake_tasks(&block) + self.class.rake_tasks(&block) + end + + # Sends the initializers to the +initializer+ method defined in the + # Rails::Initializable module. Each Rails::Application class has its own + # set of initializers, as defined by the Initializable module. + def initializer(name, opts={}, &block) + self.class.initializer(name, opts, &block) + end + + # Sends any runner called in the instance of a new application up + # to the +runner+ method defined in Rails::Railtie. + def runner(&blk) + self.class.runner(&blk) + end + + # Sends any console called in the instance of a new application up + # to the +console+ method defined in Rails::Railtie. + def console(&blk) + self.class.console(&blk) + end + + # Sends any generators called in the instance of a new application up + # to the +generators+ method defined in Rails::Railtie. + def generators(&blk) + self.class.generators(&blk) + end + + # Sends the +isolate_namespace+ method up to the class method. + def isolate_namespace(mod) + self.class.isolate_namespace(mod) + end + + ## Rails internal API + + # This method is called just after an application inherits from Rails::Application, + # allowing the developer to load classes in lib and use them during application + # configuration. + # + # class MyApplication < Rails::Application + # require "my_backend" # in lib/my_backend + # config.i18n.backend = MyBackend + # end + # + # Notice this method takes into consideration the default root path. So if you + # are changing config.root inside your application definition or having a custom + # Rails application, you will need to add lib to $LOAD_PATH on your own in case + # you need to load files in lib/ during the application configuration as well. + def self.add_lib_to_load_path!(root) #:nodoc: + path = File.join root, 'lib' + if File.exist?(path) && !$LOAD_PATH.include?(path) + $LOAD_PATH.unshift(path) + end + end + + def require_environment! #:nodoc: + environment = paths["config/environment"].existent.first + require environment if environment + end + + def routes_reloader #:nodoc: + @routes_reloader ||= RoutesReloader.new + end + + # Returns an array of file paths appended with a hash of + # directories-extensions suitable for ActiveSupport::FileUpdateChecker + # API. + def watchable_args #:nodoc: + files, dirs = config.watchable_files.dup, config.watchable_dirs.dup + + ActiveSupport::Dependencies.autoload_paths.each do |path| + dirs[path.to_s] = [:rb] + end + + [files, dirs] + end + + # Initialize the application passing the given group. By default, the + # group is :default + def initialize!(group=:default) #:nodoc: + raise "Application has been already initialized." if @initialized + run_initializers(group, self) + @initialized = true + self + end + + def initializers #:nodoc: + Bootstrap.initializers_for(self) + + railties_initializers(super) + + Finisher.initializers_for(self) + end + + def config #:nodoc: + @config ||= Application::Configuration.new(self.class.find_root(self.class.called_from)) + end + + def config=(configuration) #:nodoc: + @config = configuration + end + + # Returns secrets added to config/secrets.yml. + # + # Example: + # + # development: + # secret_key_base: 836fa3665997a860728bcb9e9a1e704d427cfc920e79d847d79c8a9a907b9e965defa4154b2b86bdec6930adbe33f21364523a6f6ce363865724549fdfc08553 + # test: + # secret_key_base: 5a37811464e7d378488b0f073e2193b093682e4e21f5d6f3ae0a4e1781e61a351fdc878a843424e81c73fb484a40d23f92c8dafac4870e74ede6e5e174423010 + # production: + # secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> + # namespace: my_app_production + # + # +Rails.application.secrets.namespace+ returns +my_app_production+ in the + # production environment. + def secrets + @secrets ||= begin + secrets = ActiveSupport::OrderedOptions.new + yaml = config.paths["config/secrets"].first + if File.exist?(yaml) + require "erb" + all_secrets = YAML.load(ERB.new(IO.read(yaml)).result) || {} + env_secrets = all_secrets[Rails.env] + secrets.merge!(env_secrets.symbolize_keys) if env_secrets + end + + # Fallback to config.secret_key_base if secrets.secret_key_base isn't set + secrets.secret_key_base ||= config.secret_key_base + # Fallback to config.secret_token if secrets.secret_token isn't set + secrets.secret_token ||= config.secret_token + + secrets + end + end + + def secrets=(secrets) #:nodoc: + @secrets = secrets + end + + def to_app #:nodoc: + self + end + + def helpers_paths #:nodoc: + config.helpers_paths + end + + console do + require "pp" + end + + console do + unless ::Kernel.private_method_defined?(:y) + require "psych/y" + end + end + + # Return an array of railties respecting the order they're loaded + # and the order specified by the +railties_order+ config. + # + # While running initializers we need engines in reverse order here when + # copying migrations from railties ; we need them in the order given by + # +railties_order+. + def migration_railties # :nodoc: + ordered_railties.flatten - [self] + end + + protected + + alias :build_middleware_stack :app + + def run_tasks_blocks(app) #:nodoc: + railties.each { |r| r.run_tasks_blocks(app) } + super + require "rails/tasks" + task :environment do + ActiveSupport.on_load(:before_initialize) { config.eager_load = false } + + require_environment! + end + end + + def run_generators_blocks(app) #:nodoc: + railties.each { |r| r.run_generators_blocks(app) } + super + end + + def run_runner_blocks(app) #:nodoc: + railties.each { |r| r.run_runner_blocks(app) } + super + end + + def run_console_blocks(app) #:nodoc: + railties.each { |r| r.run_console_blocks(app) } + super + end + + # Returns the ordered railties for this application considering railties_order. + def ordered_railties #:nodoc: + @ordered_railties ||= begin + order = config.railties_order.map do |railtie| + if railtie == :main_app + self + elsif railtie.respond_to?(:instance) + railtie.instance + else + railtie + end + end + + all = (railties - order) + all.push(self) unless (all + order).include?(self) + order.push(:all) unless order.include?(:all) + + index = order.index(:all) + order[index] = all + order + end + end + + def railties_initializers(current) #:nodoc: + initializers = [] + ordered_railties.reverse.flatten.each do |r| + if r == self + initializers += current + else + initializers += r.initializers + end + end + initializers + end + + def default_middleware_stack #:nodoc: + default_stack = DefaultMiddlewareStack.new(self, config, paths) + default_stack.build_stack + end + + def validate_secret_key_config! #:nodoc: + if secrets.secret_key_base.blank? + ActiveSupport::Deprecation.warn "You didn't set `secret_key_base`. " + + "Read the upgrade documentation to learn more about this new config option." + + if secrets.secret_token.blank? + raise "Missing `secret_key_base` for '#{Rails.env}' environment, set this value in `config/secrets.yml`" + end + end + end + + private + + def build_request(env) + req = super + env["ORIGINAL_FULLPATH"] = req.fullpath + env["ORIGINAL_SCRIPT_NAME"] = req.script_name + req + end + + def build_middleware + config.app_middleware + super + end + end +end diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb new file mode 100644 index 0000000000..9baf8aa742 --- /dev/null +++ b/railties/lib/rails/application/bootstrap.rb @@ -0,0 +1,81 @@ +require "active_support/notifications" +require "active_support/dependencies" +require "active_support/descendants_tracker" + +module Rails + class Application + module Bootstrap + include Initializable + + initializer :load_environment_hook, group: :all do end + + initializer :load_active_support, group: :all do + require "active_support/all" unless config.active_support.bare + end + + initializer :set_eager_load, group: :all do + if config.eager_load.nil? + warn <<-INFO +config.eager_load is set to nil. Please update your config/environments/*.rb files accordingly: + + * development - set it to false + * test - set it to false (unless you use a tool that preloads your test environment) + * production - set it to true + +INFO + config.eager_load = config.cache_classes + end + end + + # Initialize the logger early in the stack in case we need to log some deprecation. + initializer :initialize_logger, group: :all do + Rails.logger ||= config.logger || begin + path = config.paths["log"].first + unless File.exist? File.dirname path + FileUtils.mkdir_p File.dirname path + end + + f = File.open path, 'a' + f.binmode + f.sync = config.autoflush_log # if true make sure every write flushes + + logger = ActiveSupport::Logger.new f + logger.formatter = config.log_formatter + logger = ActiveSupport::TaggedLogging.new(logger) + logger + rescue StandardError + logger = ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDERR)) + logger.level = ActiveSupport::Logger::WARN + logger.warn( + "Rails Error: Unable to access log file. Please ensure that #{path} exists and is writable " + + "(ie, make it writable for user and group: chmod 0664 #{path}). " + + "The log level has been raised to WARN and the output directed to STDERR until the problem is fixed." + ) + logger + end + + Rails.logger.level = ActiveSupport::Logger.const_get(config.log_level.to_s.upcase) + end + + # Initialize cache early in the stack so railties can make use of it. + initializer :initialize_cache, group: :all do + unless Rails.cache + Rails.cache = ActiveSupport::Cache.lookup_store(config.cache_store) + + if Rails.cache.respond_to?(:middleware) + config.middleware.insert_before(::Rack::Runtime, Rails.cache.middleware) + end + end + end + + # Sets the dependency loading mechanism. + initializer :initialize_dependency_mechanism, group: :all do + ActiveSupport::Dependencies.mechanism = config.cache_classes ? :require : :load + end + + initializer :bootstrap_hook, group: :all do |app| + ActiveSupport.run_load_hooks(:before_initialize, app) + end + end + end +end diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb new file mode 100644 index 0000000000..65cff1561a --- /dev/null +++ b/railties/lib/rails/application/configuration.rb @@ -0,0 +1,211 @@ +require 'active_support/core_ext/kernel/reporting' +require 'active_support/file_update_checker' +require 'rails/engine/configuration' +require 'rails/source_annotation_extractor' + +require 'active_support/deprecation' +require 'active_support/core_ext/string/strip' # for strip_heredoc + +module Rails + class Application + class Configuration < ::Rails::Engine::Configuration + attr_accessor :allow_concurrency, :asset_host, :autoflush_log, + :cache_classes, :cache_store, :consider_all_requests_local, :console, + :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, + :ssl_options, :public_file_server, + :session_options, :time_zone, :reload_classes_only_on_change, + :beginning_of_week, :filter_redirect, :x + + attr_writer :log_level + attr_reader :encoding, :api_only, :static_cache_control + + def initialize(*) + super + self.encoding = "utf-8" + @allow_concurrency = nil + @consider_all_requests_local = false + @filter_parameters = [] + @filter_redirect = [] + @helpers_paths = [] + @public_file_server = ActiveSupport::OrderedOptions.new + @public_file_server.enabled = true + @public_file_server.index_name = "index" + @force_ssl = false + @ssl_options = {} + @session_store = :cookie_store + @session_options = {} + @time_zone = "UTC" + @beginning_of_week = :monday + @log_level = nil + @generators = app_generators + @cache_store = [ :file_store, "#{root}/tmp/cache/" ] + @railties_order = [:all] + @relative_url_root = ENV["RAILS_RELATIVE_URL_ROOT"] + @reload_classes_only_on_change = true + @file_watcher = ActiveSupport::FileUpdateChecker + @exceptions_app = nil + @autoflush_log = true + @log_formatter = ActiveSupport::Logger::SimpleFormatter.new + @eager_load = nil + @secret_token = nil + @secret_key_base = nil + @api_only = false + @debug_exception_response_format = nil + @x = Custom.new + end + + def static_cache_control=(value) + ActiveSupport::Deprecation.warn <<-eow.strip_heredoc + `static_cache_control` is deprecated and will be removed in Rails 5.1. + Please use + `config.public_file_server.headers = { 'Cache-Control' => '#{value}' }` + instead. + eow + + @static_cache_control = value + end + + def serve_static_files + ActiveSupport::Deprecation.warn <<-eow.strip_heredoc + `serve_static_files` is deprecated and will be removed in Rails 5.1. + Please use `public_file_server.enabled` instead. + eow + + @public_file_server.enabled + end + + def serve_static_files=(value) + ActiveSupport::Deprecation.warn <<-eow.strip_heredoc + `serve_static_files` is deprecated and will be removed in Rails 5.1. + Please use `public_file_server.enabled = #{value}` instead. + eow + + @public_file_server.enabled = value + end + + def encoding=(value) + @encoding = value + silence_warnings do + Encoding.default_external = value + Encoding.default_internal = value + end + end + + def api_only=(value) + @api_only = value + generators.api_only = value + + @debug_exception_response_format ||= :api + end + + def debug_exception_response_format + @debug_exception_response_format || :default + end + + def debug_exception_response_format=(value) + @debug_exception_response_format = value + end + + def paths + @paths ||= begin + paths = super + paths.add "config/database", with: "config/database.yml" + paths.add "config/secrets", with: "config/secrets.yml" + paths.add "config/environment", with: "config/environment.rb" + paths.add "lib/templates" + paths.add "log", with: "log/#{Rails.env}.log" + paths.add "public" + paths.add "public/javascripts" + paths.add "public/stylesheets" + paths.add "tmp" + paths + end + end + + # Loads and returns the entire raw configuration of database from + # values stored in `config/database.yml`. + def database_configuration + path = paths["config/database"].existent.first + yaml = Pathname.new(path) if path + + config = if yaml && yaml.exist? + require "yaml" + require "erb" + YAML.load(ERB.new(yaml.read).result) || {} + elsif ENV['DATABASE_URL'] + # Value from ENV['DATABASE_URL'] is set to default database connection + # by Active Record. + {} + else + raise "Could not load database configuration. No such file - #{paths["config/database"].instance_variable_get(:@paths)}" + end + + config + rescue Psych::SyntaxError => e + raise "YAML syntax error occurred while parsing #{paths["config/database"].first}. " \ + "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \ + "Error: #{e.message}" + rescue => e + raise e, "Cannot load `Rails.application.database_configuration`:\n#{e.message}", e.backtrace + end + + def log_level + @log_level ||= (Rails.env.production? ? :info : :debug) + end + + def colorize_logging + ActiveSupport::LogSubscriber.colorize_logging + end + + def colorize_logging=(val) + ActiveSupport::LogSubscriber.colorize_logging = val + self.generators.colorize_logging = val + end + + def session_store(*args) + if args.empty? + case @session_store + when :disabled + nil + when :active_record_store + begin + ActionDispatch::Session::ActiveRecordStore + rescue NameError + raise "`ActiveRecord::SessionStore` is extracted out of Rails into a gem. " \ + "Please add `activerecord-session_store` to your Gemfile to use it." + end + when Symbol + ActionDispatch::Session.const_get(@session_store.to_s.camelize) + else + @session_store + end + else + @session_store = args.shift + @session_options = args.shift || {} + end + end + + def annotations + SourceAnnotationExtractor::Annotation + end + + class Custom #:nodoc: + def initialize + @configurations = Hash.new + end + + def method_missing(method, *args) + if method =~ /=$/ + @configurations[$`.to_sym] = args.first + else + @configurations.fetch(method) { + @configurations[method] = ActiveSupport::OrderedOptions.new + } + end + end + end + end + end +end diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb new file mode 100644 index 0000000000..ed6a1f82d3 --- /dev/null +++ b/railties/lib/rails/application/default_middleware_stack.rb @@ -0,0 +1,117 @@ +module Rails + class Application + class DefaultMiddlewareStack + attr_reader :config, :paths, :app + + def initialize(app, config, paths) + @app = app + @config = config + @paths = paths + end + + def build_stack + ActionDispatch::MiddlewareStack.new.tap do |middleware| + if config.force_ssl + middleware.use ::ActionDispatch::SSL, config.ssl_options + end + + middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header + + if config.public_file_server.enabled + headers = config.public_file_server.headers || {} + headers['Cache-Control'.freeze] = config.static_cache_control if config.static_cache_control + + middleware.use ::ActionDispatch::Static, paths["public"].first, index: config.public_file_server.index_name, headers: headers + end + + if rack_cache = load_rack_cache + require "action_dispatch/http/rack_cache" + middleware.use ::Rack::Cache, rack_cache + end + + if config.allow_concurrency == false + # User has explicitly opted out of concurrent request + # handling: presumably their code is not threadsafe + + middleware.use ::Rack::Lock + + elsif config.allow_concurrency == :unsafe + # Do nothing, even if we know this is dangerous. This is the + # historical behaviour for true. + + else + # Default concurrency setting: enabled, but safe + + unless config.cache_classes && config.eager_load + # Without cache_classes + eager_load, the load interlock + # is required for proper operation + + middleware.use ::ActionDispatch::LoadInterlock + end + end + + middleware.use ::Rack::Runtime + middleware.use ::Rack::MethodOverride unless config.api_only + middleware.use ::ActionDispatch::RequestId + + # Must come after Rack::MethodOverride to properly log overridden methods + middleware.use ::Rails::Rack::Logger, config.log_tags + middleware.use ::ActionDispatch::ShowExceptions, show_exceptions_app + middleware.use ::ActionDispatch::DebugExceptions, app, config.debug_exception_response_format + middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies + + unless config.cache_classes + middleware.use ::ActionDispatch::Reloader, lambda { reload_dependencies? } + end + + middleware.use ::ActionDispatch::Callbacks + middleware.use ::ActionDispatch::Cookies unless config.api_only + + if !config.api_only && config.session_store + if config.force_ssl && !config.session_options.key?(:secure) + config.session_options[:secure] = true + end + middleware.use config.session_store, config.session_options + middleware.use ::ActionDispatch::Flash + end + + middleware.use ::Rack::Head + middleware.use ::Rack::ConditionalGet + middleware.use ::Rack::ETag, "no-cache" + end + end + + private + + def reload_dependencies? + config.reload_classes_only_on_change != true || app.reloaders.map(&:updated?).any? + end + + def load_rack_cache + rack_cache = config.action_dispatch.rack_cache + return unless rack_cache + + begin + require 'rack/cache' + rescue LoadError => error + error.message << ' Be sure to add rack-cache to your Gemfile' + raise + end + + if rack_cache == true + { + metastore: "rails:/", + entitystore: "rails:/", + verbose: false + } + else + rack_cache + end + end + + def show_exceptions_app + config.exceptions_app || ActionDispatch::PublicExceptions.new(Rails.public_path) + end + end + end +end diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb new file mode 100644 index 0000000000..404e3c3e23 --- /dev/null +++ b/railties/lib/rails/application/finisher.rb @@ -0,0 +1,122 @@ +module Rails + class Application + module Finisher + include Initializable + + initializer :add_generator_templates do + config.generators.templates.unshift(*paths["lib/templates"].existent) + end + + initializer :ensure_autoload_once_paths_as_subset do + extra = ActiveSupport::Dependencies.autoload_once_paths - + ActiveSupport::Dependencies.autoload_paths + + unless extra.empty? + abort <<-end_error + autoload_once_paths must be a subset of the autoload_paths. + Extra items in autoload_once_paths: #{extra * ','} + end_error + end + end + + initializer :add_builtin_route do |app| + if Rails.env.development? + app.routes.append do + get '/rails/info/properties' => "rails/info#properties" + get '/rails/info/routes' => "rails/info#routes" + get '/rails/info' => "rails/info#index" + get '/' => "rails/welcome#index" + end + end + end + + initializer :build_middleware_stack do + build_middleware_stack + end + + initializer :define_main_app_helper do |app| + app.routes.define_mounted_helper(:main_app) + end + + initializer :add_to_prepare_blocks do + config.to_prepare_blocks.each do |block| + ActionDispatch::Reloader.to_prepare(&block) + end + end + + # This needs to happen before eager load so it happens + # in exactly the same point regardless of config.cache_classes + initializer :run_prepare_callbacks do + ActionDispatch::Reloader.prepare! + end + + initializer :eager_load! do + if config.eager_load + ActiveSupport.run_load_hooks(:before_eager_load, self) + config.eager_load_namespaces.each(&:eager_load!) + end + end + + # All initialization is done, including eager loading in production + initializer :finisher_hook do + ActiveSupport.run_load_hooks(:after_initialize, self) + end + + # Set routes reload after the finisher hook to ensure routes added in + # the hook are taken into account. + initializer :set_routes_reloader_hook do + reloader = routes_reloader + reloader.execute_if_updated + self.reloaders << reloader + ActionDispatch::Reloader.to_prepare do + # We configure #execute rather than #execute_if_updated because if + # autoloaded constants are cleared we need to reload routes also in + # case any was used there, as in + # + # mount MailPreview => 'mail_view' + # + # This means routes are also reloaded if i18n is updated, which + # might not be necessary, but in order to be more precise we need + # some sort of reloaders dependency support, to be added. + reloader.execute + end + end + + # Set clearing dependencies after the finisher hook to ensure paths + # added in the hook are taken into account. + initializer :set_clear_dependencies_hook, group: :all do + callback = lambda do + ActiveSupport::Dependencies.interlock.attempt_unloading do + ActiveSupport::DescendantsTracker.clear + ActiveSupport::Dependencies.clear + end + end + + if config.reload_classes_only_on_change + reloader = config.file_watcher.new(*watchable_args, &callback) + self.reloaders << reloader + + # Prepend this callback to have autoloaded constants cleared before + # any other possible reloading, in case they need to autoload fresh + # constants. + ActionDispatch::Reloader.to_prepare(prepend: true) do + # In addition to changes detected by the file watcher, if routes + # or i18n have been updated we also need to clear constants, + # that's why we run #execute rather than #execute_if_updated, this + # callback has to clear autoloaded constants after any update. + reloader.execute + end + else + ActionDispatch::Reloader.to_cleanup(&callback) + end + end + + # Disable dependency loading during request cycle + initializer :disable_dependency_loading do + if config.eager_load && config.cache_classes + ActiveSupport::Dependencies.unhook! + end + end + end + end +end diff --git a/railties/lib/rails/application/routes_reloader.rb b/railties/lib/rails/application/routes_reloader.rb new file mode 100644 index 0000000000..cf0a4e128f --- /dev/null +++ b/railties/lib/rails/application/routes_reloader.rb @@ -0,0 +1,54 @@ +require "active_support/core_ext/module/delegation" + +module Rails + class Application + class RoutesReloader + attr_reader :route_sets, :paths + delegate :execute_if_updated, :execute, :updated?, to: :updater + + def initialize + @paths = [] + @route_sets = [] + end + + def reload! + clear! + load_paths + finalize! + ensure + revert + end + + private + + def updater + @updater ||= begin + updater = ActiveSupport::FileUpdateChecker.new(paths) { reload! } + updater.execute + updater + end + end + + def clear! + route_sets.each do |routes| + routes.disable_clear_and_finalize = true + routes.clear! + end + end + + def load_paths + paths.each { |path| load(path) } + end + + def finalize! + route_sets.each(&:finalize!) + end + + def revert + route_sets.each do |routes| + routes.disable_clear_and_finalize = false + end + end + end + end +end diff --git a/railties/lib/rails/application_controller.rb b/railties/lib/rails/application_controller.rb new file mode 100644 index 0000000000..618a09a5b3 --- /dev/null +++ b/railties/lib/rails/application_controller.rb @@ -0,0 +1,16 @@ +class Rails::ApplicationController < ActionController::Base # :nodoc: + self.view_paths = File.expand_path('../templates', __FILE__) + layout 'application' + + protected + + def require_local! + unless local_request? + render html: '<p>For security purposes, this information is only available to local requests.</p>'.html_safe, status: :forbidden + end + end + + def local_request? + Rails.application.config.consider_all_requests_local || request.local? + end +end diff --git a/railties/lib/rails/backtrace_cleaner.rb b/railties/lib/rails/backtrace_cleaner.rb new file mode 100644 index 0000000000..5276eb33c9 --- /dev/null +++ b/railties/lib/rails/backtrace_cleaner.rb @@ -0,0 +1,32 @@ +require 'active_support/backtrace_cleaner' + +module Rails + class BacktraceCleaner < ActiveSupport::BacktraceCleaner + APP_DIRS_PATTERN = /^\/?(app|config|lib|test)/ + RENDER_TEMPLATE_PATTERN = /:in `_render_template_\w*'/ + EMPTY_STRING = ''.freeze + SLASH = '/'.freeze + DOT_SLASH = './'.freeze + + def initialize + super + @root = "#{Rails.root}/".freeze + add_filter { |line| line.sub(@root, EMPTY_STRING) } + add_filter { |line| line.sub(RENDER_TEMPLATE_PATTERN, EMPTY_STRING) } + add_filter { |line| line.sub(DOT_SLASH, SLASH) } # for tests + + add_gem_filters + add_silencer { |line| line !~ APP_DIRS_PATTERN } + end + + private + def add_gem_filters + gems_paths = (Gem.path | [Gem.default_dir]).map { |p| Regexp.escape(p) } + return if gems_paths.empty? + + gems_regexp = %r{(#{gems_paths.join('|')})/gems/([^/]+)-([\w.]+)/(.*)} + gems_result = '\2 (\3) \4'.freeze + add_filter { |line| line.sub(gems_regexp, gems_result) } + end + end +end diff --git a/railties/lib/rails/cli.rb b/railties/lib/rails/cli.rb new file mode 100644 index 0000000000..a8794bc0de --- /dev/null +++ b/railties/lib/rails/cli.rb @@ -0,0 +1,15 @@ +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::AppLoader.exec_app + +require 'rails/ruby_version_check' +Signal.trap("INT") { puts; exit(1) } + +if ARGV.first == 'plugin' + ARGV.shift + require 'rails/commands/plugin' +else + require 'rails/commands/application' +end diff --git a/railties/lib/rails/code_statistics.rb b/railties/lib/rails/code_statistics.rb new file mode 100644 index 0000000000..8e9097e1ef --- /dev/null +++ b/railties/lib/rails/code_statistics.rb @@ -0,0 +1,100 @@ +require 'rails/code_statistics_calculator' + +class CodeStatistics #:nodoc: + + TEST_TYPES = ['Controller tests', + 'Helper tests', + 'Model tests', + 'Mailer tests', + 'Job tests', + 'Integration tests'] + + def initialize(*pairs) + @pairs = pairs + @statistics = calculate_statistics + @total = calculate_total if pairs.length > 1 + end + + def to_s + print_header + @pairs.each { |pair| print_line(pair.first, @statistics[pair.first]) } + print_splitter + + if @total + print_line("Total", @total) + print_splitter + end + + print_code_test_stats + end + + private + def calculate_statistics + Hash[@pairs.map{|pair| [pair.first, calculate_directory_statistics(pair.last)]}] + end + + def calculate_directory_statistics(directory, pattern = /.*\.(rb|js|coffee|rake)$/) + stats = CodeStatisticsCalculator.new + + Dir.foreach(directory) do |file_name| + path = "#{directory}/#{file_name}" + + if File.directory?(path) && (/^\./ !~ file_name) + stats.add(calculate_directory_statistics(path, pattern)) + elsif file_name =~ pattern + stats.add_by_file_path(path) + end + end + + stats + end + + def calculate_total + @statistics.each_with_object(CodeStatisticsCalculator.new) do |pair, total| + total.add(pair.last) + end + end + + def calculate_code + code_loc = 0 + @statistics.each { |k, v| code_loc += v.code_lines unless TEST_TYPES.include? k } + code_loc + end + + def calculate_tests + test_loc = 0 + @statistics.each { |k, v| test_loc += v.code_lines if TEST_TYPES.include? k } + test_loc + end + + def print_header + print_splitter + puts "| Name | Lines | LOC | Classes | Methods | M/C | LOC/M |" + print_splitter + end + + def print_splitter + puts "+----------------------+--------+--------+---------+---------+-----+-------+" + end + + def print_line(name, statistics) + m_over_c = (statistics.methods / statistics.classes) rescue m_over_c = 0 + loc_over_m = (statistics.code_lines / statistics.methods) - 2 rescue loc_over_m = 0 + + puts "| #{name.ljust(20)} " \ + "| #{statistics.lines.to_s.rjust(6)} " \ + "| #{statistics.code_lines.to_s.rjust(6)} " \ + "| #{statistics.classes.to_s.rjust(7)} " \ + "| #{statistics.methods.to_s.rjust(7)} " \ + "| #{m_over_c.to_s.rjust(3)} " \ + "| #{loc_over_m.to_s.rjust(5)} |" + end + + def print_code_test_stats + code = calculate_code + tests = calculate_tests + + puts " Code LOC: #{code} Test LOC: #{tests} Code to Test Ratio: 1:#{sprintf("%.1f", tests.to_f/code)}" + puts "" + end +end diff --git a/railties/lib/rails/code_statistics_calculator.rb b/railties/lib/rails/code_statistics_calculator.rb new file mode 100644 index 0000000000..fad13e8517 --- /dev/null +++ b/railties/lib/rails/code_statistics_calculator.rb @@ -0,0 +1,86 @@ +class CodeStatisticsCalculator #:nodoc: + attr_reader :lines, :code_lines, :classes, :methods + + PATTERNS = { + rb: { + line_comment: /^\s*#/, + begin_block_comment: /^=begin/, + end_block_comment: /^=end/, + class: /^\s*class\s+[_A-Z]/, + method: /^\s*def\s+[_a-z]/, + }, + js: { + line_comment: %r{^\s*//}, + begin_block_comment: %r{^\s*/\*}, + end_block_comment: %r{\*/}, + method: /function(\s+[_a-zA-Z][\da-zA-Z]*)?\s*\(/, + }, + coffee: { + line_comment: /^\s*#/, + begin_block_comment: /^\s*###/, + end_block_comment: /^\s*###/, + class: /^\s*class\s+[_A-Z]/, + method: /[-=]>/, + } + } + + PATTERNS[:minitest] = PATTERNS[:rb].merge method: /^\s*(def|test)\s+['"_a-z]/ + PATTERNS[:rake] = PATTERNS[:rb] + + def initialize(lines = 0, code_lines = 0, classes = 0, methods = 0) + @lines = lines + @code_lines = code_lines + @classes = classes + @methods = methods + end + + def add(code_statistics_calculator) + @lines += code_statistics_calculator.lines + @code_lines += code_statistics_calculator.code_lines + @classes += code_statistics_calculator.classes + @methods += code_statistics_calculator.methods + end + + def add_by_file_path(file_path) + File.open(file_path) do |f| + self.add_by_io(f, file_type(file_path)) + end + end + + def add_by_io(io, file_type) + patterns = PATTERNS[file_type] || {} + + comment_started = false + + while line = io.gets + @lines += 1 + + if comment_started + if patterns[:end_block_comment] && line =~ patterns[:end_block_comment] + comment_started = false + end + next + else + if patterns[:begin_block_comment] && line =~ patterns[:begin_block_comment] + comment_started = true + next + end + end + + @classes += 1 if patterns[:class] && line =~ patterns[:class] + @methods += 1 if patterns[:method] && line =~ patterns[:method] + if line !~ /^\s*$/ && (patterns[:line_comment].nil? || line !~ patterns[:line_comment]) + @code_lines += 1 + end + end + end + + private + def file_type(file_path) + if file_path.end_with? '_test.rb' + :minitest + else + File.extname(file_path).sub(/\A\./, '').downcase.to_sym + end + end +end diff --git a/railties/lib/rails/command.rb b/railties/lib/rails/command.rb new file mode 100644 index 0000000000..f7753cbb83 --- /dev/null +++ b/railties/lib/rails/command.rb @@ -0,0 +1,70 @@ +require 'rails/commands/commands_tasks' + +module Rails + class Command #:nodoc: + attr_reader :argv + + def initialize(argv = []) + @argv = argv + + @option_parser = build_option_parser + @options = {} + end + + def self.run(task_name, argv) + command_name = command_name_for(task_name) + + if command = command_for(command_name) + command.new(argv).run(command_name) + else + Rails::CommandsTasks.new(argv).run_command!(task_name) + end + end + + def run(command_name) + parse_options_for(command_name) + @option_parser.parse! @argv + + public_send(command_name) + end + + def self.options_for(command_name, &options_to_parse) + @@command_options[command_name] = options_to_parse + end + + def self.set_banner(command_name, banner) + options_for(command_name) { |opts, _| opts.banner = banner } + end + + private + @@commands = [] + @@command_options = {} + + def parse_options_for(command_name) + @@command_options.fetch(command_name, proc {}).call(@option_parser, @options) + end + + def build_option_parser + OptionParser.new do |opts| + opts.on('-h', '--help', 'Show this help.') do + puts opts + exit + end + end + end + + def self.inherited(command) + @@commands << command + end + + def self.command_name_for(task_name) + task_name.gsub(':', '_').to_sym + end + + def self.command_for(command_name) + @@commands.find do |command| + command.public_instance_methods.include?(command_name) + end + end + end +end diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb new file mode 100644 index 0000000000..7627fcf5a0 --- /dev/null +++ b/railties/lib/rails/commands.rb @@ -0,0 +1,19 @@ +ARGV << '--help' if ARGV.empty? + +aliases = { + "g" => "generate", + "d" => "destroy", + "c" => "console", + "s" => "server", + "db" => "dbconsole", + "r" => "runner", + "t" => "test" +} + +command = ARGV.shift +command = aliases[command] || command + +require 'rails/command' +require 'rails/commands/dev_cache' + +Rails::Command.run(command, ARGV) diff --git a/railties/lib/rails/commands/application.rb b/railties/lib/rails/commands/application.rb new file mode 100644 index 0000000000..c998e6b6a8 --- /dev/null +++ b/railties/lib/rails/commands/application.rb @@ -0,0 +1,17 @@ +require 'rails/generators' +require 'rails/generators/rails/app/app_generator' + +module Rails + module Generators + class AppGenerator # :nodoc: + # We want to exit on failure to be kind to other libraries + # This is only when accessing via CLI + def self.exit_on_failure? + true + end + end + end +end + +args = Rails::Generators::ARGVScrubber.new(ARGV).prepare! +Rails::Generators::AppGenerator.start args diff --git a/railties/lib/rails/commands/commands_tasks.rb b/railties/lib/rails/commands/commands_tasks.rb new file mode 100644 index 0000000000..da3b9452d5 --- /dev/null +++ b/railties/lib/rails/commands/commands_tasks.rb @@ -0,0 +1,180 @@ +require 'rails/commands/rake_proxy' + +module Rails + # This is a class which takes in a rails command and initiates the appropriate + # initiation sequence. + # + # Warning: This class mutates ARGV because some commands require manipulating + # it before they are run. + class CommandsTasks # :nodoc: + include Rails::RakeProxy + + attr_reader :argv + + HELP_MESSAGE = <<-EOT +Usage: rails COMMAND [ARGS] + +The most common rails commands are: + generate Generate new code (short-cut alias: "g") + console Start the Rails console (short-cut alias: "c") + server Start the Rails server (short-cut alias: "s") + test Run tests (short-cut alias: "t") + dbconsole Start a console for the database specified in config/database.yml + (short-cut alias: "db") + new Create a new Rails application. "rails new my_app" creates a + new application called MyApp in "./my_app" + +All commands can be run with -h (or --help) for more information. + +In addition to those commands, there are: +EOT + + ADDITIONAL_COMMANDS = [ + [ 'destroy', 'Undo code generated with "generate" (short-cut alias: "d")' ], + [ 'plugin new', 'Generates skeleton for developing a Rails plugin' ], + [ 'runner', + 'Run a piece of code in the application environment (short-cut alias: "r")' ] + ] + + COMMAND_WHITELIST = %w(plugin generate destroy console server dbconsole runner new version help test) + + def initialize(argv) + @argv = argv + end + + def run_command!(command) + command = parse_command(command) + + if COMMAND_WHITELIST.include?(command) + send(command) + else + run_rake_task(command) + end + end + + def plugin + require_command!("plugin") + end + + def generate + generate_or_destroy(:generate) + end + + def destroy + generate_or_destroy(:destroy) + end + + def console + require_command!("console") + options = Rails::Console.parse_arguments(argv) + + # RAILS_ENV needs to be set before config/application is required + ENV['RAILS_ENV'] = options[:environment] if options[:environment] + + # shift ARGV so IRB doesn't freak + shift_argv! + + require_application_and_environment! + Rails::Console.start(Rails.application, options) + end + + def server + set_application_directory! + require_command!("server") + + Rails::Server.new.tap do |server| + # We need to require application after the server sets environment, + # otherwise the --environment option given to the server won't propagate. + require APP_PATH + Dir.chdir(Rails.application.root) + server.start + end + end + + def test + require_command!("test") + end + + def dbconsole + require_command!("dbconsole") + Rails::DBConsole.start + end + + def runner + require_command!("runner") + end + + def new + if %w(-h --help).include?(argv.first) + require_command!("application") + else + exit_with_initialization_warning! + end + end + + def version + argv.unshift '--version' + require_command!("application") + end + + def help + write_help_message + write_commands ADDITIONAL_COMMANDS + formatted_rake_tasks + end + + private + + def exit_with_initialization_warning! + puts "Can't initialize a new Rails application within the directory of another, please change to a non-Rails directory first.\n" + puts "Type 'rails' for help." + exit(1) + end + + def shift_argv! + argv.shift if argv.first && argv.first[0] != '-' + end + + def require_command!(command) + require "rails/commands/#{command}" + end + + def generate_or_destroy(command) + require 'rails/generators' + require_application_and_environment! + Rails.application.load_generators + require_command!(command) + end + + # Change to the application's path if there is no config.ru file in current directory. + # This allows us to run `rails server` from other directories, but still get + # the main config.ru and properly set the tmp directory. + def set_application_directory! + Dir.chdir(File.expand_path('../../', APP_PATH)) unless File.exist?(File.expand_path("config.ru")) + end + + def require_application_and_environment! + require APP_PATH + Rails.application.require_environment! + end + + def write_help_message + puts HELP_MESSAGE + end + + def write_commands(commands) + width = commands.map { |name, _| name.size }.max || 10 + commands.each { |command| printf(" %-#{width}s %s\n", *command) } + end + + def parse_command(command) + case command + when '--version', '-v' + 'version' + when '--help', '-h' + 'help' + else + command + end + end + end +end diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb new file mode 100644 index 0000000000..ea5d20ea24 --- /dev/null +++ b/railties/lib/rails/commands/console.rb @@ -0,0 +1,68 @@ +require 'optparse' +require 'irb' +require 'irb/completion' +require 'rails/commands/console_helper' + +module Rails + class Console + include ConsoleHelper + + class << self + def parse_arguments(arguments) + options = {} + + OptionParser.new do |opt| + opt.banner = "Usage: rails console [environment] [options]" + opt.on('-s', '--sandbox', 'Rollback database modifications on exit.') { |v| options[:sandbox] = v } + opt.on("-e", "--environment=name", String, + "Specifies the environment to run this console under (test/development/production).", + "Default: development") { |v| options[:environment] = v.strip } + opt.parse!(arguments) + end + + set_options_env(arguments, options) + end + end + + attr_reader :options, :app, :console + + def initialize(app, options={}) + @app = app + @options = options + + app.sandbox = sandbox? + app.load_console + + @console = app.config.console || IRB + end + + def sandbox? + options[:sandbox] + end + + def environment + options[:environment] ||= super + end + alias_method :environment?, :environment + + def set_environment! + Rails.env = environment + end + + def start + set_environment! if environment? + + if sandbox? + puts "Loading #{Rails.env} environment in sandbox (Rails #{Rails.version})" + puts "Any modifications you make will be rolled back on exit" + else + puts "Loading #{Rails.env} environment (Rails #{Rails.version})" + end + + if defined?(console::ExtendCommandBundle) + console::ExtendCommandBundle.include(Rails::ConsoleMethods) + end + console.start + end + end +end diff --git a/railties/lib/rails/commands/console_helper.rb b/railties/lib/rails/commands/console_helper.rb new file mode 100644 index 0000000000..8ee0b60012 --- /dev/null +++ b/railties/lib/rails/commands/console_helper.rb @@ -0,0 +1,34 @@ +require 'active_support/concern' + +module Rails + module ConsoleHelper # :nodoc: + extend ActiveSupport::Concern + + module ClassMethods + def start(*args) + new(*args).start + end + + private + def set_options_env(arguments, options) + if arguments.first && arguments.first[0] != '-' + env = arguments.first + if available_environments.include? env + options[:environment] = env + else + options[:environment] = %w(production development test).detect { |e| e =~ /^#{env}/ } || env + end + end + options + end + + def available_environments + Dir['config/environments/*.rb'].map { |fname| File.basename(fname, '.*') } + end + end + + def environment + ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development" + end + end +end
\ No newline at end of file diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb new file mode 100644 index 0000000000..2c36edfa3f --- /dev/null +++ b/railties/lib/rails/commands/dbconsole.rb @@ -0,0 +1,173 @@ +require 'erb' +require 'yaml' +require 'optparse' +require 'rails/commands/console_helper' + +module Rails + class DBConsole + include ConsoleHelper + + attr_reader :arguments + + class << self + def parse_arguments(arguments) + options = {} + + OptionParser.new do |opt| + opt.banner = "Usage: rails dbconsole [environment] [options]" + opt.on("-p", "--include-password", "Automatically provide the password from database.yml") do |v| + options['include_password'] = true + end + + opt.on("--mode [MODE]", ['html', 'list', 'line', 'column'], + "Automatically put the sqlite3 database in the specified mode (html, list, line, column).") do |mode| + options['mode'] = mode + end + + opt.on("--header") do |h| + options['header'] = h + end + + opt.on("-h", "--help", "Show this help message.") do + puts opt + exit + end + + opt.on("-e", "--environment=name", String, + "Specifies the environment to run this console under (test/development/production).", + "Default: development" + ) { |v| options[:environment] = v.strip } + + opt.parse!(arguments) + abort opt.to_s unless (0..1).include?(arguments.size) + end + + set_options_env(arguments, options) + end + end + + def initialize(arguments = ARGV) + @arguments = arguments + end + + def start + options = self.class.parse_arguments(arguments) + ENV['RAILS_ENV'] = options[:environment] || environment + + case config["adapter"] + when /^(jdbc)?mysql/ + args = { + 'host' => '--host', + 'port' => '--port', + 'socket' => '--socket', + 'username' => '--user', + 'encoding' => '--default-character-set', + 'sslca' => '--ssl-ca', + 'sslcert' => '--ssl-cert', + 'sslcapath' => '--ssl-capath', + 'sslcipher' => '--ssl-cipher', + 'sslkey' => '--ssl-key' + }.map { |opt, arg| "#{arg}=#{config[opt]}" if config[opt] }.compact + + if config['password'] && options['include_password'] + args << "--password=#{config['password']}" + elsif config['password'] && !config['password'].to_s.empty? + args << "-p" + end + + args << config['database'] + + find_cmd_and_exec(['mysql', 'mysql5'], *args) + + when /^postgres|^postgis/ + ENV['PGUSER'] = config["username"] if config["username"] + ENV['PGHOST'] = config["host"] if config["host"] + ENV['PGPORT'] = config["port"].to_s if config["port"] + ENV['PGPASSWORD'] = config["password"].to_s if config["password"] && options['include_password'] + find_cmd_and_exec('psql', config["database"]) + + when "sqlite3" + args = [] + + args << "-#{options['mode']}" if options['mode'] + args << "-header" if options['header'] + args << File.expand_path(config['database'], Rails.respond_to?(:root) ? Rails.root : nil) + + find_cmd_and_exec('sqlite3', *args) + + when "oracle", "oracle_enhanced" + logon = "" + + if config['username'] + logon = config['username'] + logon << "/#{config['password']}" if config['password'] && options['include_password'] + logon << "@#{config['database']}" if config['database'] + end + + find_cmd_and_exec('sqlplus', logon) + + when "sqlserver" + args = [] + + args += ["-D", "#{config['database']}"] if config['database'] + args += ["-U", "#{config['username']}"] if config['username'] + args += ["-P", "#{config['password']}"] if config['password'] + + if config['host'] + host_arg = "#{config['host']}" + host_arg << ":#{config['port']}" if config['port'] + args += ["-S", host_arg] + end + + find_cmd_and_exec("sqsh", *args) + + else + abort "Unknown command-line client for #{config['database']}." + end + end + + def config + @config ||= begin + if configurations[environment].blank? + raise ActiveRecord::AdapterNotSpecified, "'#{environment}' database is not configured. Available configuration: #{configurations.inspect}" + else + configurations[environment] + end + end + end + + def environment + Rails.respond_to?(:env) ? Rails.env : super + end + + protected + def configurations + require APP_PATH + ActiveRecord::Base.configurations = Rails.application.config.database_configuration + ActiveRecord::Base.configurations + end + + def find_cmd_and_exec(commands, *args) + commands = Array(commands) + + dirs_on_path = ENV['PATH'].to_s.split(File::PATH_SEPARATOR) + unless (ext = RbConfig::CONFIG['EXEEXT']).empty? + commands = commands.map{|cmd| "#{cmd}#{ext}"} + end + + full_path_command = nil + found = commands.detect do |cmd| + dirs_on_path.detect do |path| + full_path_command = File.join(path, cmd) + File.file?(full_path_command) && File.executable?(full_path_command) + end + end + + if found + exec full_path_command, *args + else + abort("Couldn't find database client: #{commands.join(', ')}. Check your $PATH and try again.") + end + end + end +end diff --git a/railties/lib/rails/commands/destroy.rb b/railties/lib/rails/commands/destroy.rb new file mode 100644 index 0000000000..ce26cc3fde --- /dev/null +++ b/railties/lib/rails/commands/destroy.rb @@ -0,0 +1,11 @@ +require 'rails/generators' + +#if no argument/-h/--help is passed to rails destroy command, then +#it generates the help associated. +if [nil, "-h", "--help"].include?(ARGV.first) + Rails::Generators.help 'destroy' + exit +end + +name = ARGV.shift +Rails::Generators.invoke name, ARGV, behavior: :revoke, destination_root: Rails.root diff --git a/railties/lib/rails/commands/dev_cache.rb b/railties/lib/rails/commands/dev_cache.rb new file mode 100644 index 0000000000..ec96e8f630 --- /dev/null +++ b/railties/lib/rails/commands/dev_cache.rb @@ -0,0 +1,21 @@ +require 'rails/command' + +module Rails + module Commands + # This is a wrapper around the Rails dev:cache command + class DevCache < Command + set_banner :dev_cache, 'Toggle development mode caching on/off' + def dev_cache + if File.exist? 'tmp/caching-dev.txt' + File.delete 'tmp/caching-dev.txt' + puts 'Development mode is no longer being cached.' + else + FileUtils.touch 'tmp/caching-dev.txt' + puts 'Development mode is now being cached.' + end + + FileUtils.touch 'tmp/restart.txt' + end + end + end +end diff --git a/railties/lib/rails/commands/generate.rb b/railties/lib/rails/commands/generate.rb new file mode 100644 index 0000000000..926c36b967 --- /dev/null +++ b/railties/lib/rails/commands/generate.rb @@ -0,0 +1,13 @@ +require 'rails/generators' + +#if no argument/-h/--help is passed to rails generate command, then +#it generates the help associated. +if [nil, "-h", "--help"].include?(ARGV.first) + Rails::Generators.help 'generate' + exit +end + +name = ARGV.shift + +root = defined?(ENGINE_ROOT) ? ENGINE_ROOT : Rails.root +Rails::Generators.invoke name, ARGV, behavior: :invoke, destination_root: root diff --git a/railties/lib/rails/commands/plugin.rb b/railties/lib/rails/commands/plugin.rb new file mode 100644 index 0000000000..52d8966ead --- /dev/null +++ b/railties/lib/rails/commands/plugin.rb @@ -0,0 +1,23 @@ +if ARGV.first != "new" + ARGV[0] = "--help" +else + ARGV.shift + unless ARGV.delete("--no-rc") + customrc = ARGV.index{ |x| x.include?("--rc=") } + railsrc = if customrc + File.expand_path(ARGV.delete_at(customrc).gsub(/--rc=/, "")) + else + File.join(File.expand_path("~"), '.railsrc') + end + if File.exist?(railsrc) + extra_args_string = File.read(railsrc) + extra_args = extra_args_string.split(/\n+/).flat_map(&:split) + puts "Using #{extra_args.join(" ")} from #{railsrc}" + ARGV.insert(1, *extra_args) + end + end +end + +require 'rails/generators' +require 'rails/generators/rails/plugin/plugin_generator' +Rails::Generators::PluginGenerator.start diff --git a/railties/lib/rails/commands/rake_proxy.rb b/railties/lib/rails/commands/rake_proxy.rb new file mode 100644 index 0000000000..f7d5df6b2f --- /dev/null +++ b/railties/lib/rails/commands/rake_proxy.rb @@ -0,0 +1,34 @@ +require 'rake' +require 'active_support' + +module Rails + module RakeProxy #:nodoc: + private + def run_rake_task(command) + ARGV.unshift(command) # Prepend the command, so Rake knows how to run it. + + Rake.application.standard_exception_handling do + Rake.application.init('rails') + Rake.application.load_rakefile + Rake.application.top_level + end + end + + def rake_tasks + return @rake_tasks if defined?(@rake_tasks) + + ActiveSupport::Deprecation.silence do + require_application_and_environment! + end + + Rake::TaskManager.record_task_metadata = true + Rake.application.instance_variable_set(:@name, 'rails') + Rails.application.load_tasks + @rake_tasks = Rake.application.tasks.select(&:comment) + end + + def formatted_rake_tasks + rake_tasks.map { |t| [ t.name_with_args, t.comment ] } + end + end +end diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb new file mode 100644 index 0000000000..86bce9b2fe --- /dev/null +++ b/railties/lib/rails/commands/runner.rb @@ -0,0 +1,62 @@ +require 'optparse' + +options = { environment: (ENV['RAILS_ENV'] || ENV['RACK_ENV'] || "development").dup } +code_or_file = nil + +if ARGV.first.nil? + ARGV.push "-h" +end + +ARGV.clone.options do |opts| + opts.banner = "Usage: rails runner [options] [<'Some.ruby(code)'> | <filename.rb>]" + + opts.separator "" + + opts.on("-e", "--environment=name", String, + "Specifies the environment for the runner to operate under (test/development/production).", + "Default: development") { |v| options[:environment] = v } + + opts.separator "" + + opts.on("-h", "--help", + "Show this help message.") { $stdout.puts opts; exit } + + opts.separator "" + opts.separator "Examples: " + + opts.separator " rails runner 'puts Rails.env'" + opts.separator " This runs the code `puts Rails.env` after loading the app" + opts.separator "" + opts.separator " rails runner path/to/filename.rb" + opts.separator " This runs the Ruby file located at `path/to/filename.rb` after loading the app" + + if RbConfig::CONFIG['host_os'] !~ /mswin|mingw/ + opts.separator "" + opts.separator "You can also use runner as a shebang line for your executables:" + opts.separator " -------------------------------------------------------------" + opts.separator " #!/usr/bin/env #{File.expand_path($0)} runner" + opts.separator "" + opts.separator " Product.all.each { |p| p.price *= 2 ; p.save! }" + opts.separator " -------------------------------------------------------------" + end + + opts.order! { |o| code_or_file ||= o } rescue retry +end + +ARGV.delete(code_or_file) + +ENV["RAILS_ENV"] = options[:environment] + +require APP_PATH +Rails.application.require_environment! +Rails.application.load_runner + +if code_or_file.nil? + $stderr.puts "Run '#{$0} -h' for help." + exit 1 +elsif File.exist?(code_or_file) + $0 = code_or_file + Kernel.load code_or_file +else + eval(code_or_file, binding, __FILE__, __LINE__) +end diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb new file mode 100644 index 0000000000..d3ea441f8e --- /dev/null +++ b/railties/lib/rails/commands/server.rb @@ -0,0 +1,143 @@ +require 'fileutils' +require 'optparse' +require 'action_dispatch' +require 'rails' + +module Rails + class Server < ::Rack::Server + class Options + def parse!(args) + args, options = args.dup, {} + + option_parser(options).parse! args + + options[:log_stdout] = options[:daemonize].blank? && (options[:environment] || Rails.env) == "development" + options[:server] = args.shift + options + end + + private + + def option_parser(options) + OptionParser.new do |opts| + opts.banner = "Usage: rails server [mongrel, thin etc] [options]" + opts.on("-p", "--port=port", Integer, + "Runs Rails on the specified port.", "Default: 3000") { |v| options[:Port] = v } + opts.on("-b", "--binding=IP", String, + "Binds Rails to the specified IP.", "Default: localhost") { |v| options[:Host] = v } + opts.on("-c", "--config=file", String, + "Uses a custom rackup configuration.") { |v| options[:config] = v } + opts.on("-d", "--daemon", "Runs server as a Daemon.") { options[:daemonize] = true } + opts.on("-e", "--environment=name", String, + "Specifies the environment to run this server under (test/development/production).", + "Default: development") { |v| options[:environment] = v } + opts.on("-P", "--pid=pid", String, + "Specifies the PID file.", + "Default: tmp/pids/server.pid") { |v| options[:pid] = v } + opts.on("-C", "--[no-]dev-caching", + "Specifies whether to perform caching in development.", + "true or false") { |v| options[:caching] = v } + + opts.separator "" + + opts.on("-h", "--help", "Shows this help message.") { puts opts; exit } + end + end + end + + def initialize(*) + super + set_environment + end + + # TODO: this is no longer required but we keep it for the moment to support older config.ru files. + def app + @app ||= begin + app = super + app.respond_to?(:to_app) ? app.to_app : app + end + end + + def opt_parser + Options.new + end + + def set_environment + ENV["RAILS_ENV"] ||= options[:environment] + end + + def start + print_boot_information + trap(:INT) { exit } + create_tmp_directories + setup_dev_caching + log_to_stdout if options[:log_stdout] + + super + ensure + # The '-h' option calls exit before @options is set. + # If we call 'options' with it unset, we get double help banners. + puts 'Exiting' unless @options && options[:daemonize] + end + + def middleware + Hash.new([]) + end + + def default_options + super.merge({ + Port: ENV.fetch('PORT', 3000).to_i, + DoNotReverseLookup: true, + environment: (ENV['RAILS_ENV'] || ENV['RACK_ENV'] || "development").dup, + daemonize: false, + caching: false, + pid: File.expand_path("tmp/pids/server.pid") + }) + end + + private + + def setup_dev_caching + return unless options[:environment] == "development" + + if options[:caching] == false + delete_cache_file + elsif options[:caching] + create_cache_file + end + end + + def print_boot_information + url = "#{options[:SSLEnable] ? 'https' : 'http'}://#{options[:Host]}:#{options[:Port]}" + puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}" + puts "=> Rails #{Rails.version} application starting in #{Rails.env} on #{url}" + puts "=> Run `rails server -h` for more startup options" + + puts "=> Ctrl-C to shutdown server" unless options[:daemonize] + end + + def create_cache_file + FileUtils.touch("tmp/caching-dev.txt") + end + + def delete_cache_file + FileUtils.rm("tmp/caching-dev.txt") if File.exist?("tmp/caching-dev.txt") + end + + def create_tmp_directories + %w(cache pids sockets).each do |dir_to_make| + FileUtils.mkdir_p(File.join(Rails.root, 'tmp', dir_to_make)) + end + end + + def log_to_stdout + wrapped_app # touch the app so the logger is set up + + console = ActiveSupport::Logger.new($stdout) + console.formatter = Rails.logger.formatter + console.level = Rails.logger.level + + Rails.logger.extend(ActiveSupport::Logger.broadcast(console)) + end + end +end diff --git a/railties/lib/rails/commands/test.rb b/railties/lib/rails/commands/test.rb new file mode 100644 index 0000000000..dd069f081f --- /dev/null +++ b/railties/lib/rails/commands/test.rb @@ -0,0 +1,9 @@ +require "rails/test_unit/minitest_plugin" + +if defined?(ENGINE_ROOT) + $: << File.expand_path('test', ENGINE_ROOT) +else + $: << File.expand_path('../../test', APP_PATH) +end + +exit Minitest.run(ARGV) diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb new file mode 100644 index 0000000000..30eafd59f2 --- /dev/null +++ b/railties/lib/rails/configuration.rb @@ -0,0 +1,135 @@ +require 'active_support/ordered_options' +require 'active_support/core_ext/object' +require 'rails/paths' +require 'rails/rack' + +module Rails + module Configuration + # MiddlewareStackProxy is a proxy for the Rails middleware stack that allows + # you to configure middlewares in your application. It works basically as a + # command recorder, saving each command to be applied after initialization + # over the default middleware stack, so you can add, swap, or remove any + # middleware in Rails. + # + # You can add your own middlewares by using the +config.middleware.use+ method: + # + # config.middleware.use Magical::Unicorns + # + # This will put the <tt>Magical::Unicorns</tt> middleware on the end of the stack. + # You can use +insert_before+ if you wish to add a middleware before another: + # + # config.middleware.insert_before Rack::Head, Magical::Unicorns + # + # There's also +insert_after+ which will insert a middleware after another: + # + # config.middleware.insert_after Rack::Head, Magical::Unicorns + # + # Middlewares can also be completely swapped out and replaced with others: + # + # config.middleware.swap ActionDispatch::Flash, Magical::Unicorns + # + # And finally they can also be removed from the stack completely: + # + # config.middleware.delete ActionDispatch::Flash + # + class MiddlewareStackProxy + def initialize(operations = [], delete_operations = []) + @operations = operations + @delete_operations = delete_operations + end + + def insert_before(*args, &block) + @operations << [__method__, args, block] + end + + alias :insert :insert_before + + def insert_after(*args, &block) + @operations << [__method__, args, block] + end + + def swap(*args, &block) + @operations << [__method__, args, block] + end + + def use(*args, &block) + @operations << [__method__, args, block] + end + + def delete(*args, &block) + @delete_operations << [__method__, args, block] + end + + def unshift(*args, &block) + @operations << [__method__, args, block] + end + + def merge_into(other) #:nodoc: + (@operations + @delete_operations).each do |operation, args, block| + other.send(operation, *args, &block) + end + + other + end + + def +(other) # :nodoc: + MiddlewareStackProxy.new(@operations + other.operations, @delete_operations + other.delete_operations) + end + + protected + def operations + @operations + end + + def delete_operations + @delete_operations + end + end + + class Generators #:nodoc: + attr_accessor :aliases, :options, :templates, :fallbacks, :colorize_logging, :api_only + attr_reader :hidden_namespaces + + def initialize + @aliases = Hash.new { |h,k| h[k] = {} } + @options = Hash.new { |h,k| h[k] = {} } + @fallbacks = {} + @templates = [] + @colorize_logging = true + @api_only = false + @hidden_namespaces = [] + end + + def initialize_copy(source) + @aliases = @aliases.deep_dup + @options = @options.deep_dup + @fallbacks = @fallbacks.deep_dup + @templates = @templates.dup + end + + def hide_namespace(namespace) + @hidden_namespaces << namespace + end + + def method_missing(method, *args) + method = method.to_s.sub(/=$/, '').to_sym + + return @options[method] if args.empty? + + if method == :rails || args.first.is_a?(Hash) + namespace, configuration = method, args.shift + else + namespace, configuration = args.shift, args.shift + namespace = namespace.to_sym if namespace.respond_to?(:to_sym) + @options[:rails][method] = namespace + end + + if configuration + aliases = configuration.delete(:aliases) + @aliases[namespace].merge!(aliases) if aliases + @options[namespace].merge!(configuration) + end + end + end + end +end diff --git a/railties/lib/rails/console/app.rb b/railties/lib/rails/console/app.rb new file mode 100644 index 0000000000..ac5836a588 --- /dev/null +++ b/railties/lib/rails/console/app.rb @@ -0,0 +1,37 @@ +require 'active_support/all' +require 'action_controller' + +module Rails + module ConsoleMethods + # reference the global "app" instance, created on demand. To recreate the + # instance, pass a non-false value as the parameter. + def app(create=false) + @app_integration_instance = nil if create + @app_integration_instance ||= new_session do |sess| + sess.host! "www.example.com" + end + end + + # create a new session. If a block is given, the new session will be yielded + # to the block before being returned. + def new_session + 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 + + # reloads the environment + def reload!(print=true) + puts "Reloading..." if print + ActionDispatch::Reloader.cleanup! + ActionDispatch::Reloader.prepare! + true + end + end +end diff --git a/railties/lib/rails/console/helpers.rb b/railties/lib/rails/console/helpers.rb new file mode 100644 index 0000000000..a33f71dc5b --- /dev/null +++ b/railties/lib/rails/console/helpers.rb @@ -0,0 +1,17 @@ +module Rails + module ConsoleMethods + # Gets the helper methods available to the controller. + # + # This method assumes an +ApplicationController+ exists, and it extends +ActionController::Base+ + def helper + ApplicationController.helpers + end + + # Gets a new instance of a controller object. + # + # This method assumes an +ApplicationController+ exists, and it extends +ActionController::Base+ + def controller + @controller ||= ApplicationController.new + end + end +end diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb new file mode 100644 index 0000000000..5757d235d2 --- /dev/null +++ b/railties/lib/rails/engine.rb @@ -0,0 +1,704 @@ +require 'rails/railtie' +require 'rails/engine/railties' +require 'active_support/core_ext/module/delegation' +require 'pathname' +require 'thread' + +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. + # 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 + # methods (like <tt>rake_tasks</tt> and +generators+) and configuration + # options that are available in railties can also be used in engines. + # + # == Creating an Engine + # + # 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 + # class Engine < Rails::Engine + # end + # end + # + # Then ensure that this file is loaded at the top of your <tt>config/application.rb</tt> + # (or in your +Gemfile+) and it will automatically load models, controllers and helpers + # inside +app+, load routes at <tt>config/routes.rb</tt>, load locales at + # <tt>config/locales/*</tt>, and load tasks at <tt>lib/tasks/*</tt>. + # + # == Configuration + # + # Besides the +Railtie+ configuration which is shared across the application, in a + # <tt>Rails::Engine</tt> you can access <tt>autoload_paths</tt>, <tt>eager_load_paths</tt> + # and <tt>autoload_once_paths</tt>, which, differently from a <tt>Railtie</tt>, are scoped to + # the current engine. + # + # class MyEngine < Rails::Engine + # # Add a load path for this specific Engine + # config.autoload_paths << File.expand_path("../lib/some/path", __FILE__) + # + # initializer "my_engine.add_middleware" do |app| + # app.middleware.use MyEngine::Middleware + # end + # end + # + # == Generators + # + # You can set up generators for engines with <tt>config.generators</tt> method: + # + # class MyEngine < Rails::Engine + # config.generators do |g| + # g.orm :active_record + # g.template_engine :erb + # g.test_framework :test_unit + # end + # end + # + # You can also set generators for an application by using <tt>config.app_generators</tt>: + # + # class MyEngine < Rails::Engine + # # note that you can also pass block to app_generators in the same way you + # # can pass it to generators method + # config.app_generators.orm :datamapper + # end + # + # == Paths + # + # 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: + # + # class MyEngine < Rails::Engine + # paths["app/controllers"] = "lib/controllers" + # end + # + # You can also have your controllers loaded from both <tt>app/controllers</tt> and + # <tt>lib/controllers</tt>: + # + # class MyEngine < Rails::Engine + # paths["app/controllers"] << "lib/controllers" + # end + # + # The available paths in an engine are: + # + # class MyEngine < Rails::Engine + # paths["app"] # => ["app"] + # paths["app/controllers"] # => ["app/controllers"] + # paths["app/helpers"] # => ["app/helpers"] + # paths["app/models"] # => ["app/models"] + # paths["app/views"] # => ["app/views"] + # paths["lib"] # => ["lib"] + # paths["lib/tasks"] # => ["lib/tasks"] + # paths["config"] # => ["config"] + # paths["config/initializers"] # => ["config/initializers"] + # paths["config/locales"] # => ["config/locales"] + # paths["config/routes.rb"] # => ["config/routes.rb"] + # end + # + # The <tt>Application</tt> class adds a couple more paths to this set. And as in your + # <tt>Application</tt>, all folders under +app+ are automatically added to the load path. + # If you have an <tt>app/services</tt> folder for example, it will be added by default. + # + # == Endpoint + # + # An engine can also be a rack application. It can be useful if you have a rack application that + # you would like to wrap with +Engine+ and provide with some of the +Engine+'s features. + # + # To do that, use the +endpoint+ method: + # + # module MyEngine + # class Engine < Rails::Engine + # endpoint MyRackApplication + # end + # end + # + # Now you can mount your engine in application's routes just like that: + # + # Rails.application.routes.draw do + # mount MyEngine::Engine => "/engine" + # end + # + # == Middleware stack + # + # As an engine can now be a rack endpoint, it can also have a middleware + # stack. The usage is exactly the same as in <tt>Application</tt>: + # + # module MyEngine + # class Engine < Rails::Engine + # middleware.use SomeMiddleware + # end + # end + # + # == Routes + # + # If you don't specify an endpoint, routes will be used as the default + # endpoint. You can use them just like you use an application's routes: + # + # # ENGINE/config/routes.rb + # MyEngine::Engine.routes.draw do + # get "/" => "posts#index" + # end + # + # == Mount priority + # + # Note that now there can be more than one router in your application, and it's better to avoid + # passing requests through many routers. Consider this situation: + # + # Rails.application.routes.draw do + # mount MyEngine::Engine => "/blog" + # get "/blog/omg" => "main#omg" + # end + # + # +MyEngine+ is mounted at <tt>/blog</tt>, and <tt>/blog/omg</tt> points to application's + # controller. In such a situation, requests to <tt>/blog/omg</tt> will go through +MyEngine+, + # and if there is no such route in +Engine+'s routes, it will be dispatched to <tt>main#omg</tt>. + # It's much better to swap that: + # + # Rails.application.routes.draw do + # get "/blog/omg" => "main#omg" + # mount MyEngine::Engine => "/blog" + # end + # + # Now, +Engine+ will get only requests that were not handled by +Application+. + # + # == Engine name + # + # There are some places where an Engine's name is used: + # + # * routes: when you mount an Engine with <tt>mount(MyEngine::Engine => '/my_engine')</tt>, + # it's used as default <tt>:as</tt> option + # * rake task for installing migrations <tt>my_engine:install:migrations</tt> + # + # Engine name is set by default based on class name. For <tt>MyEngine::Engine</tt> it will be + # <tt>my_engine_engine</tt>. You can change it manually using the <tt>engine_name</tt> method: + # + # module MyEngine + # class Engine < Rails::Engine + # engine_name "my_engine" + # end + # end + # + # == Isolated Engine + # + # Normally when you create controllers, helpers and models inside an engine, they are treated + # as if they were created inside the application itself. This means that all helpers and + # named routes from the application will be available to your engine's controllers as well. + # + # However, sometimes you want to isolate your engine from the application, especially if your engine + # has its own router. To do that, you simply need to call +isolate_namespace+. This method requires + # you to pass a module where all your controllers, helpers and models should be nested to: + # + # module MyEngine + # class Engine < Rails::Engine + # isolate_namespace MyEngine + # end + # end + # + # With such an engine, everything that is inside the +MyEngine+ module will be isolated from + # the application. + # + # Consider this controller: + # + # module MyEngine + # class FooController < ActionController::Base + # end + # end + # + # If the +MyEngine+ engine is marked as isolated, +FooController+ only has + # access to helpers from +MyEngine+, and <tt>url_helpers</tt> from + # <tt>MyEngine::Engine.routes</tt>. + # + # The next thing that changes in isolated engines is the behavior of routes. + # Normally, when you namespace your controllers, you also need to namespace + # the related routes. With an isolated engine, the engine's namespace is + # automatically applied, so you don't need to specify it explicity in your + # routes: + # + # MyEngine::Engine.routes.draw do + # resources :articles + # end + # + # If +MyEngine+ is isolated, The routes above will point to + # <tt>MyEngine::ArticlesController</tt>. You also don't need to use longer + # url helpers like +my_engine_articles_path+. Instead, you should simply use + # +articles_path+, like you would do with your main application. + # + # To make this behavior consistent with other parts of the framework, + # isolated engines also have an effect on <tt>ActiveModel::Naming</tt>. In a + # normal Rails app, when you use a namespaced model such as + # <tt>Namespace::Article</tt>, <tt>ActiveModel::Naming</tt> will generate + # names with the prefix "namespace". In an isolated engine, the prefix will + # be omitted in url helpers and form fields, for convenience. + # + # polymorphic_url(MyEngine::Article.new) + # # => "articles_path" # not "my_engine_articles_path" + # + # form_for(MyEngine::Article.new) do + # text_field :title # => <input type="text" name="article[title]" id="article_title" /> + # end + # + # Additionally, an isolated engine will set its own name according to its + # namespace, so <tt>MyEngine::Engine.engine_name</tt> will return + # "my_engine". It will also set +MyEngine.table_name_prefix+ to "my_engine_", + # meaning for example that <tt>MyEngine::Article</tt> will use the + # +my_engine_articles+ database table by default. + # + # == Using Engine's routes outside Engine + # + # Since you can now mount an engine inside application's routes, you do not have direct access to +Engine+'s + # <tt>url_helpers</tt> inside +Application+. When you mount an engine in an application's routes, a special helper is + # created to allow you to do that. Consider such a scenario: + # + # # config/routes.rb + # Rails.application.routes.draw do + # mount MyEngine::Engine => "/my_engine", as: "my_engine" + # get "/foo" => "foo#index" + # end + # + # Now, you can use the <tt>my_engine</tt> helper inside your application: + # + # class FooController < ApplicationController + # def index + # my_engine.root_url # => /my_engine/ + # end + # end + # + # There is also a <tt>main_app</tt> helper that gives you access to application's routes inside Engine: + # + # module MyEngine + # class BarController + # def index + # main_app.foo_path # => /foo + # end + # end + # end + # + # Note that the <tt>:as</tt> option given to mount takes the <tt>engine_name</tt> as default, so most of the time + # you can simply omit it. + # + # Finally, if you want to generate a url to an engine's route using + # <tt>polymorphic_url</tt>, you also need to pass the engine helper. Let's + # say that you want to create a form pointing to one of the engine's routes. + # All you need to do is pass the helper as the first element in array with + # attributes for url: + # + # form_for([my_engine, @user]) + # + # This code will use <tt>my_engine.user_path(@user)</tt> to generate the proper route. + # + # == Isolated engine's helpers + # + # Sometimes you may want to isolate engine, but use helpers that are defined for it. + # If you want to share just a few specific helpers you can add them to application's + # helpers in ApplicationController: + # + # class ApplicationController < ActionController::Base + # helper MyEngine::SharedEngineHelper + # end + # + # If you want to include all of the engine's helpers, you can use the #helper method on an engine's + # instance: + # + # class ApplicationController < ActionController::Base + # helper MyEngine::Engine.helpers + # end + # + # It will include all of the helpers from engine's directory. Take into account that this does + # not include helpers defined in controllers with helper_method or other similar solutions, + # only helpers defined in the helpers directory will be included. + # + # == Migrations & seed data + # + # Engines can have their own migrations. The default path for migrations is exactly the same + # as in application: <tt>db/migrate</tt> + # + # To use engine's migrations in application you can use the rake task below, which copies them to + # application's dir: + # + # rake ENGINE_NAME:install:migrations + # + # Note that some of the migrations may be skipped if a migration with the same name already exists + # in application. In such a situation you must decide whether to leave that migration or rename the + # migration in the application and rerun copying migrations. + # + # If your engine has migrations, you may also want to prepare data for the database in + # the <tt>db/seeds.rb</tt> file. You can load that data using the <tt>load_seed</tt> method, e.g. + # + # MyEngine::Engine.load_seed + # + # == Loading priority + # + # In order to change engine's priority you can use +config.railties_order+ in the main application. + # It will affect the priority of loading views, helpers, assets and all the other files + # related to engine or application. + # + # # load Blog::Engine with highest priority, followed by application and other railties + # config.railties_order = [Blog::Engine, :main_app, :all] + class Engine < Railtie + autoload :Configuration, "rails/engine/configuration" + + class << self + attr_accessor :called_from, :isolated + + alias :isolated? :isolated + alias :engine_name :railtie_name + + delegate :eager_load!, to: :instance + + def inherited(base) + unless base.abstract_railtie? + Rails::Railtie::Configuration.eager_load_namespaces << base + + base.called_from = begin + call_stack = caller_locations.map { |l| l.absolute_path || l.path } + + File.dirname(call_stack.detect { |p| p !~ %r[railties[\w.-]*/lib/rails|rack[\w.-]*/lib/rack] }) + end + end + + super + end + + def find_root(from) + find_root_with_flag "lib", from + end + + def endpoint(endpoint = nil) + @endpoint ||= nil + @endpoint = endpoint if endpoint + @endpoint + end + + def isolate_namespace(mod) + engine_name(generate_railtie_name(mod.name)) + + self.routes.default_scope = { module: ActiveSupport::Inflector.underscore(mod.name) } + self.isolated = true + + unless mod.respond_to?(:railtie_namespace) + name, railtie = engine_name, self + + mod.singleton_class.instance_eval do + define_method(:railtie_namespace) { railtie } + + unless mod.respond_to?(:table_name_prefix) + define_method(:table_name_prefix) { "#{name}_" } + end + + unless mod.respond_to?(:use_relative_model_naming?) + class_eval "def use_relative_model_naming?; true; end", __FILE__, __LINE__ + end + + unless mod.respond_to?(:railtie_helpers_paths) + define_method(:railtie_helpers_paths) { railtie.helpers_paths } + end + + unless mod.respond_to?(:railtie_routes_url_helpers) + define_method(:railtie_routes_url_helpers) {|include_path_helpers = true| railtie.routes.url_helpers(include_path_helpers) } + end + end + end + end + + # Finds engine with given path. + def find(path) + expanded_path = File.expand_path path + Rails::Engine.subclasses.each do |klass| + engine = klass.instance + return engine if File.expand_path(engine.root) == expanded_path + end + nil + end + end + + delegate :middleware, :root, :paths, to: :config + delegate :engine_name, :isolated?, to: :class + + def initialize + @_all_autoload_paths = nil + @_all_load_paths = nil + @app = nil + @config = nil + @env_config = nil + @helpers = nil + @routes = nil + @app_build_lock = Mutex.new + super + end + + # Load console and invoke the registered hooks. + # Check <tt>Rails::Railtie.console</tt> for more info. + def load_console(app=self) + require "rails/console/app" + require "rails/console/helpers" + run_console_blocks(app) + self + end + + # Load Rails runner and invoke the registered hooks. + # Check <tt>Rails::Railtie.runner</tt> for more info. + def load_runner(app=self) + run_runner_blocks(app) + self + end + + # Load Rake, railties tasks and invoke the registered hooks. + # Check <tt>Rails::Railtie.rake_tasks</tt> for more info. + def load_tasks(app=self) + require "rake" + run_tasks_blocks(app) + self + end + + # Load Rails generators and invoke the registered hooks. + # Check <tt>Rails::Railtie.generators</tt> for more info. + def load_generators(app=self) + require "rails/generators" + run_generators_blocks(app) + Rails::Generators.configure!(app.config.generators) + self + end + + # Eager load the application by loading all ruby + # files inside eager_load paths. + def eager_load! + config.eager_load_paths.each do |load_path| + matcher = /\A#{Regexp.escape(load_path.to_s)}\/(.*)\.rb\Z/ + Dir.glob("#{load_path}/**/*.rb").sort.each do |file| + require_dependency file.sub(matcher, '\1') + end + end + end + + def railties + @railties ||= Railties.new + end + + # Returns a module with all the helpers defined for the engine. + def helpers + @helpers ||= begin + helpers = Module.new + all = ActionController::Base.all_helpers_from_path(helpers_paths) + ActionController::Base.modules_for_helpers(all).each do |mod| + helpers.include(mod) + end + helpers + end + end + + # Returns all registered helpers paths. + def helpers_paths + paths["app/helpers"].existent + end + + # Returns the underlying rack application for this engine. + def app + @app || @app_build_lock.synchronize { + @app ||= begin + stack = default_middleware_stack + config.middleware = build_middleware.merge_into(stack) + config.middleware.build(endpoint) + end + } + end + + # Returns the endpoint for this engine. If none is registered, + # defaults to an ActionDispatch::Routing::RouteSet. + def endpoint + self.class.endpoint || routes + end + + # Define the Rack API for this engine. + def call(env) + req = build_request env + app.call req.env + end + + # Defines additional Rack env configuration that is added on each call. + def env_config + @env_config ||= {} + end + + # Defines the routes for this engine. If a block is given to + # routes, it is appended to the engine. + def routes + @routes ||= ActionDispatch::Routing::RouteSet.new_with_config(config) + @routes.append(&Proc.new) if block_given? + @routes + end + + # Define the configuration object for the engine. + def config + @config ||= Engine::Configuration.new(self.class.find_root(self.class.called_from)) + end + + # Load data from db/seeds.rb file. It can be used in to load engines' + # seeds, e.g.: + # + # Blog::Engine.load_seed + def load_seed + seed_file = paths["db/seeds.rb"].existent.first + load(seed_file) if seed_file + end + + # Add configured load paths to ruby load paths and remove duplicates. + initializer :set_load_path, before: :bootstrap_hook do + _all_load_paths.reverse_each do |path| + $LOAD_PATH.unshift(path) if File.directory?(path) + end + $LOAD_PATH.uniq! + end + + # Set the paths from which Rails will automatically load source files, + # and the load_once paths. + # + # This needs to be an initializer, since it needs to run once + # per engine and get the engine as a block parameter. + initializer :set_autoload_paths, before: :bootstrap_hook do + ActiveSupport::Dependencies.autoload_paths.unshift(*_all_autoload_paths) + ActiveSupport::Dependencies.autoload_once_paths.unshift(*_all_autoload_once_paths) + + # Freeze so future modifications will fail rather than do nothing mysteriously + config.autoload_paths.freeze + config.eager_load_paths.freeze + config.autoload_once_paths.freeze + end + + initializer :add_routing_paths do |app| + routing_paths = self.paths["config/routes.rb"].existent + + if routes? || routing_paths.any? + app.routes_reloader.paths.unshift(*routing_paths) + app.routes_reloader.route_sets << routes + end + end + + # I18n load paths are a special case since the ones added + # later have higher priority. + initializer :add_locales do + config.i18n.railties_load_path << paths["config/locales"] + end + + initializer :add_view_paths do + views = paths["app/views"].existent + unless views.empty? + ActiveSupport.on_load(:action_controller){ prepend_view_path(views) if respond_to?(:prepend_view_path) } + ActiveSupport.on_load(:action_mailer){ prepend_view_path(views) } + end + end + + initializer :load_environment_config, before: :load_environment_hook, group: :all do + paths["config/environments"].existent.each do |environment| + require environment + end + end + + initializer :prepend_helpers_path do |app| + if !isolated? || (app == self) + app.config.helpers_paths.unshift(*paths["app/helpers"].existent) + end + end + + initializer :load_config_initializers do + config.paths["config/initializers"].existent.sort.each do |initializer| + load_config_initializer(initializer) + end + end + + initializer :engines_blank_point do + # We need this initializer so all extra initializers added in engines are + # consistently executed after all the initializers above across all engines. + end + + rake_tasks do + next if self.is_a?(Rails::Application) + next unless has_migrations? + + namespace railtie_name do + namespace :install do + desc "Copy migrations from #{railtie_name} to application" + task :migrations do + ENV["FROM"] = railtie_name + if Rake::Task.task_defined?("railties:install:migrations") + Rake::Task["railties:install:migrations"].invoke + else + Rake::Task["app:railties:install:migrations"].invoke + end + end + end + end + end + + def routes? #:nodoc: + @routes + end + + protected + + def load_config_initializer(initializer) + ActiveSupport::Notifications.instrument('load_config_initializer.railties', initializer: initializer) do + load(initializer) + end + end + + def run_tasks_blocks(*) #:nodoc: + super + paths["lib/tasks"].existent.sort.each { |ext| load(ext) } + end + + def has_migrations? #:nodoc: + paths["db/migrate"].existent.any? + end + + def self.find_root_with_flag(flag, root_path, default=nil) #:nodoc: + + while root_path && File.directory?(root_path) && !File.exist?("#{root_path}/#{flag}") + parent = File.dirname(root_path) + root_path = parent != root_path && parent + end + + root = File.exist?("#{root_path}/#{flag}") ? root_path : default + raise "Could not find root path for #{self}" unless root + + Pathname.new File.realpath root + end + + def default_middleware_stack #:nodoc: + ActionDispatch::MiddlewareStack.new + end + + def _all_autoload_once_paths #:nodoc: + config.autoload_once_paths + end + + def _all_autoload_paths #:nodoc: + @_all_autoload_paths ||= (config.autoload_paths + config.eager_load_paths + config.autoload_once_paths).uniq + end + + def _all_load_paths #:nodoc: + @_all_load_paths ||= (config.paths.load_paths + _all_autoload_paths).uniq + end + + private + + def build_request(env) + env.merge!(env_config) + req = ActionDispatch::Request.new env + req.routes = routes + req.engine_script_name = req.script_name + req + end + + def build_middleware + config.middleware + end + end +end diff --git a/railties/lib/rails/engine/commands.rb b/railties/lib/rails/engine/commands.rb new file mode 100644 index 0000000000..a6d87b78e4 --- /dev/null +++ b/railties/lib/rails/engine/commands.rb @@ -0,0 +1,43 @@ +ARGV << '--help' if ARGV.empty? + +aliases = { + "g" => "generate", + "d" => "destroy", + "t" => "test" +} + +command = ARGV.shift +command = aliases[command] || command + +require ENGINE_PATH +engine = ::Rails::Engine.find(ENGINE_ROOT) + +case command +when 'generate', 'destroy', 'test' + require 'rails/generators' + Rails::Generators.namespace = engine.railtie_namespace + engine.load_generators + require "rails/commands/#{command}" + +when '--version', '-v' + ARGV.unshift '--version' + require 'rails/commands/application' + +else + puts "Error: Command not recognized" unless %w(-h --help).include?(command) + puts <<-EOT +Usage: rails COMMAND [ARGS] + +The common Rails commands available for engines are: + generate Generate new code (short-cut alias: "g") + destroy Undo code generated with "generate" (short-cut alias: "d") + test Run tests (short-cut alias: "t") + +All commands can be run with -h for more information. + +If you want to run any commands that need to be run in context +of the application, like `rails server` or `rails console`, +you should do it from application's directory (typically test/dummy). + EOT + exit(1) +end diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb new file mode 100644 index 0000000000..8cadbc3ddd --- /dev/null +++ b/railties/lib/rails/engine/configuration.rb @@ -0,0 +1,85 @@ +require 'rails/railtie/configuration' + +module Rails + class Engine + class Configuration < ::Rails::Railtie::Configuration + attr_reader :root + attr_accessor :middleware + attr_writer :eager_load_paths, :autoload_once_paths, :autoload_paths + + def initialize(root=nil) + super() + @root = root + @generators = app_generators.dup + @middleware = Rails::Configuration::MiddlewareStackProxy.new + end + + # Holds generators configuration: + # + # config.generators do |g| + # g.orm :data_mapper, migration: true + # g.template_engine :haml + # g.test_framework :rspec + # end + # + # If you want to disable color in console, do: + # + # config.generators.colorize_logging = false + # + def generators + @generators ||= Rails::Configuration::Generators.new + yield(@generators) if block_given? + @generators + end + + def paths + @paths ||= begin + paths = Rails::Paths::Root.new(@root) + + paths.add "app", eager_load: true, glob: "{*,*/concerns}" + paths.add "app/assets", glob: "*" + paths.add "app/controllers", eager_load: true + paths.add "app/helpers", eager_load: true + paths.add "app/models", eager_load: true + paths.add "app/mailers", eager_load: true + paths.add "app/views" + + paths.add "lib", load_path: true + paths.add "lib/assets", glob: "*" + paths.add "lib/tasks", glob: "**/*.rake" + + paths.add "config" + paths.add "config/environments", glob: "#{Rails.env}.rb" + paths.add "config/initializers", glob: "**/*.rb" + paths.add "config/locales", glob: "*.{rb,yml}" + paths.add "config/routes.rb" + + paths.add "db" + paths.add "db/migrate" + paths.add "db/seeds.rb" + + paths.add "vendor", load_path: true + paths.add "vendor/assets", glob: "*" + + paths + end + end + + def root=(value) + @root = paths.path = Pathname.new(value).expand_path + end + + def eager_load_paths + @eager_load_paths ||= paths.eager_load + end + + def autoload_once_paths + @autoload_once_paths ||= paths.autoload_once + end + + def autoload_paths + @autoload_paths ||= paths.autoload_paths + end + end + end +end diff --git a/railties/lib/rails/engine/railties.rb b/railties/lib/rails/engine/railties.rb new file mode 100644 index 0000000000..9969a1475d --- /dev/null +++ b/railties/lib/rails/engine/railties.rb @@ -0,0 +1,21 @@ +module Rails + class Engine < Railtie + class Railties + include Enumerable + attr_reader :_all + + def initialize + @_all ||= ::Rails::Railtie.subclasses.map(&:instance) + + ::Rails::Engine.subclasses.map(&:instance) + end + + def each(*args, &block) + _all.each(*args, &block) + end + + def -(others) + _all - others + end + end + end +end diff --git a/railties/lib/rails/gem_version.rb b/railties/lib/rails/gem_version.rb new file mode 100644 index 0000000000..7d74b1bfe5 --- /dev/null +++ b/railties/lib/rails/gem_version.rb @@ -0,0 +1,15 @@ +module Rails + # Returns the version of the currently loaded Rails as a <tt>Gem::Version</tt> + def self.gem_version + Gem::Version.new VERSION::STRING + end + + module VERSION + MAJOR = 5 + MINOR = 0 + TINY = 0 + PRE = "alpha" + + STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") + end +end diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb new file mode 100644 index 0000000000..e3d79521e7 --- /dev/null +++ b/railties/lib/rails/generators.rb @@ -0,0 +1,394 @@ +activesupport_path = File.expand_path('../../../../activesupport/lib', __FILE__) +$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path) + +require 'thor/group' + +require 'active_support' +require 'active_support/core_ext/object/blank' +require 'active_support/core_ext/kernel/singleton_class' +require 'active_support/core_ext/array/extract_options' +require 'active_support/core_ext/hash/deep_merge' +require 'active_support/core_ext/module/attribute_accessors' +require 'active_support/core_ext/string/inflections' + +module Rails + module Generators + autoload :Actions, 'rails/generators/actions' + autoload :ActiveModel, 'rails/generators/active_model' + autoload :Base, 'rails/generators/base' + autoload :Migration, 'rails/generators/migration' + autoload :NamedBase, 'rails/generators/named_base' + autoload :ResourceHelpers, 'rails/generators/resource_helpers' + autoload :TestCase, 'rails/generators/test_case' + + mattr_accessor :namespace + + DEFAULT_ALIASES = { + rails: { + actions: '-a', + orm: '-o', + javascripts: '-j', + javascript_engine: '-je', + resource_controller: '-c', + scaffold_controller: '-c', + stylesheets: '-y', + stylesheet_engine: '-se', + scaffold_stylesheet: '-ss', + template_engine: '-e', + test_framework: '-t' + }, + + test_unit: { + fixture_replacement: '-r', + } + } + + DEFAULT_OPTIONS = { + rails: { + api: false, + assets: true, + force_plural: false, + helper: true, + integration_tool: nil, + javascripts: true, + javascript_engine: :js, + orm: false, + resource_controller: :controller, + resource_route: true, + 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 + fallbacks.merge! config.fallbacks + templates_path.concat config.templates + templates_path.uniq! + hide_namespaces(*config.hidden_namespaces) + end + + def self.templates_path #:nodoc: + @templates_path ||= [] + end + + def self.aliases #:nodoc: + @aliases ||= DEFAULT_ALIASES.dup + end + + def self.options #:nodoc: + @options ||= DEFAULT_OPTIONS.dup + end + + # Hold configured generators fallbacks. If a plugin developer wants a + # generator group to fallback to another group in case of missing generators, + # they can add a fallback. + # + # For example, shoulda is considered a test_framework and is an extension + # of test_unit. However, most part of shoulda generators are similar to + # test_unit ones. + # + # Shoulda then can tell generators to search for test_unit generators when + # some of them are not available by adding a fallback: + # + # Rails::Generators.fallbacks[:shoulda] = :test_unit + def self.fallbacks + @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 + end + + # Track all generators subclasses. + def self.subclasses + @subclasses ||= [] + end + + # Rails finds namespaces similar to thor, it only adds one rule: + # + # Generators names must end with "_generator.rb". This is required because Rails + # looks in load paths and loads the generator just before it's going to be used. + # + # find_by_namespace :webrat, :rails, :integration + # + # Will search for the following generators: + # + # "rails:webrat", "webrat:integration", "webrat" + # + # Notice that "rails:generators:webrat" could be loaded as well, what + # Rails looks for is the first and last parts of the namespace. + def self.find_by_namespace(name, base=nil, context=nil) #:nodoc: + lookups = [] + lookups << "#{base}:#{name}" if base + lookups << "#{name}:#{context}" if context + + unless base || context + unless name.to_s.include?(?:) + lookups << "#{name}:#{name}" + lookups << "rails:#{name}" + end + lookups << "#{name}" + end + + lookup(lookups) + + namespaces = Hash[subclasses.map { |klass| [klass.namespace, klass] }] + + lookups.each do |namespace| + klass = namespaces[namespace] + return klass if klass + end + + invoke_fallbacks_for(name, base) || invoke_fallbacks_for(context, name) + end + + # Receives a namespace, arguments and the behavior to invoke the generator. + # It's used as the default entry point for generate, destroy and update + # commands. + def self.invoke(namespace, args=ARGV, config={}) + names = namespace.to_s.split(':') + if klass = find_by_namespace(names.pop, names.any? && names.join(':')) + args << "--help" if args.empty? && klass.arguments.any?(&:required?) + klass.start(args, config) + else + options = sorted_groups.flat_map(&:last) + suggestions = options.sort_by {|suggested| levenshtein_distance(namespace.to_s, suggested) }.first(3) + msg = "Could not find generator '#{namespace}'. " + msg << "Maybe you meant #{ suggestions.map {|s| "'#{s}'"}.to_sentence(last_word_connector: " or ", locale: :en) }\n" + msg << "Run `rails generate --help` for more options." + puts msg + end + end + + # Returns an array of generator namespaces that are hidden. + # Generator namespaces may be hidden for a variety of reasons. + # Some are aliased such as "rails:migration" and can be + # invoked with the shorter "migration", others are private to other generators + # such as "css:scaffold". + def self.hidden_namespaces + @hidden_namespaces ||= begin + orm = options[:rails][:orm] + test = options[:rails][:test_framework] + template = options[:rails][:template_engine] + css = options[:rails][:stylesheet_engine] + + [ + "rails", + "resource_route", + "#{orm}:migration", + "#{orm}:model", + "#{test}:controller", + "#{test}:helper", + "#{test}:integration", + "#{test}:mailer", + "#{test}:model", + "#{test}:scaffold", + "#{test}:view", + "#{test}:job", + "#{template}:controller", + "#{template}:scaffold", + "#{template}:mailer", + "#{css}:scaffold", + "#{css}:assets", + "css:assets", + "css:scaffold" + ] + end + end + + class << self + def hide_namespaces(*namespaces) + hidden_namespaces.concat(namespaces) + end + alias hide_namespace hide_namespaces + end + + # Show help message with available generators. + def self.help(command = 'generate') + puts "Usage: rails #{command} GENERATOR [args] [options]" + puts + puts "General options:" + puts " -h, [--help] # Print generator's options and usage" + puts " -p, [--pretend] # Run but do not make any changes" + puts " -f, [--force] # Overwrite files that already exist" + puts " -s, [--skip] # Skip files that already exist" + puts " -q, [--quiet] # Suppress status output" + puts + puts "Please choose a generator below." + puts + + print_generators + end + + def self.public_namespaces + lookup! + subclasses.map(&:namespace) + end + + def self.print_generators + sorted_groups.each { |b, n| print_list(b, n) } + end + + def self.sorted_groups + namespaces = public_namespaces + namespaces.sort! + groups = Hash.new { |h,k| h[k] = [] } + namespaces.each do |namespace| + base = namespace.split(':').first + groups[base] << namespace + end + rails = groups.delete("rails") + rails.map! { |n| n.sub(/^rails:/, '') } + rails.delete("app") + rails.delete("plugin") + + hidden_namespaces.each { |n| groups.delete(n.to_s) } + + [["rails", rails]] + groups.sort.to_a + end + + protected + + # This code is based directly on the Text gem implementation + # Returns a value representing the "cost" of transforming str1 into str2 + def self.levenshtein_distance str1, str2 + s = str1 + t = str2 + n = s.length + m = t.length + + return m if (0 == n) + return n if (0 == m) + + d = (0..m).to_a + x = nil + + # avoid duplicating an enumerable object in the loop + str2_codepoint_enumerable = str2.each_codepoint + + str1.each_codepoint.with_index do |char1, i| + e = i+1 + + str2_codepoint_enumerable.with_index do |char2, j| + cost = (char1 == char2) ? 0 : 1 + x = [ + d[j+1] + 1, # insertion + e + 1, # deletion + d[j] + cost # substitution + ].min + d[j] = e + e = x + end + + d[m] = x + end + + x + end + + # Prints a list of generators. + def self.print_list(base, namespaces) #:nodoc: + namespaces = namespaces.reject do |n| + hidden_namespaces.include?(n) + end + + return if namespaces.empty? + puts "#{base.camelize}:" + + namespaces.each do |namespace| + puts(" #{namespace}") + end + + puts + end + + # Try fallbacks for the given base. + def self.invoke_fallbacks_for(name, base) #:nodoc: + return nil unless base && fallbacks[base.to_sym] + invoked_fallbacks = [] + + Array(fallbacks[base.to_sym]).each do |fallback| + next if invoked_fallbacks.include?(fallback) + invoked_fallbacks << fallback + + klass = find_by_namespace(name, fallback) + return klass if klass + end + + nil + end + + # Receives namespaces in an array and tries to find matching generators + # in the load path. + def self.lookup(namespaces) #:nodoc: + paths = namespaces_to_paths(namespaces) + + paths.each do |raw_path| + ["rails/generators", "generators"].each do |base| + path = "#{base}/#{raw_path}_generator" + + begin + require path + return + rescue LoadError => e + raise unless e.message =~ /#{Regexp.escape(path)}$/ + rescue Exception => e + warn "[WARNING] Could not load generator #{path.inspect}. Error: #{e.message}.\n#{e.backtrace.join("\n")}" + end + end + end + end + + # This will try to load any generator in the load path to show in help. + def self.lookup! #:nodoc: + $LOAD_PATH.each do |base| + Dir[File.join(base, "{rails/generators,generators}", "**", "*_generator.rb")].each do |path| + begin + path = path.sub("#{base}/", "") + require path + rescue Exception + # No problem + end + end + end + end + + # Convert namespaces to paths by replacing ":" for "/" and adding + # an extra lookup. For example, "rails:model" should be searched + # in both: "rails/model/model_generator" and "rails/model_generator". + def self.namespaces_to_paths(namespaces) #:nodoc: + paths = [] + namespaces.each do |namespace| + pieces = namespace.split(":") + paths << pieces.dup.push(pieces.last).join("/") + paths << pieces.join("/") + end + paths.uniq! + paths + end + end +end diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb new file mode 100644 index 0000000000..5bbd2f1aed --- /dev/null +++ b/railties/lib/rails/generators/actions.rb @@ -0,0 +1,295 @@ +require 'open-uri' + +module Rails + module Generators + module Actions + def initialize(*) # :nodoc: + super + @in_group = nil + @after_bundle_callbacks = [] + end + + # Adds an entry into +Gemfile+ for the supplied gem. + # + # gem "rspec", group: :test + # gem "technoweenie-restful-authentication", lib: "restful-authentication", source: "http://gems.github.com/" + # gem "rails", "3.0", git: "git://github.com/rails/rails" + def gem(*args) + options = args.extract_options! + name, version = args + + # Set the message to be shown in logs. Uses the git repo if one is given, + # otherwise use name (version). + parts, message = [ quote(name) ], name + if version ||= options.delete(:version) + parts << quote(version) + message << " (#{version})" + end + message = options[:git] if options[:git] + + log :gemfile, message + + options.each do |option, value| + parts << "#{option}: #{quote(value)}" + end + + in_root do + str = "gem #{parts.join(", ")}" + str = " " + str if @in_group + str = "\n" + str + append_file "Gemfile", str, verbose: false + end + end + + # Wraps gem entries inside a group. + # + # gem_group :development, :test do + # gem "rspec-rails" + # end + def gem_group(*names, &block) + name = names.map(&:inspect).join(", ") + log :gemfile, "group #{name}" + + in_root do + append_file "Gemfile", "\ngroup #{name} do", force: true + + @in_group = true + instance_eval(&block) + @in_group = false + + append_file "Gemfile", "\nend\n", force: true + end + end + + # Add the given source to +Gemfile+ + # + # If block is given, gem entries in block are wrapped into the source group. + # + # add_source "http://gems.github.com/" + # + # add_source "http://gems.github.com/" do + # gem "rspec-rails" + # end + def add_source(source, options={}, &block) + log :source, source + + in_root do + if block + append_file "Gemfile", "source #{quote(source)} do", force: true + @in_group = true + instance_eval(&block) + @in_group = false + append_file "Gemfile", "\nend\n", force: true + else + prepend_file "Gemfile", "source #{quote(source)}\n", verbose: false + end + end + end + + # Adds a line inside the Application class for <tt>config/application.rb</tt>. + # + # If options <tt>:env</tt> is specified, the line is appended to the corresponding + # file in <tt>config/environments</tt>. + # + # environment do + # "config.action_controller.asset_host = 'cdn.provider.com'" + # end + # + # environment(nil, env: "development") do + # "config.action_controller.asset_host = 'localhost:3000'" + # end + def environment(data=nil, options={}) + sentinel = /class [a-z_:]+ < Rails::Application/i + env_file_sentinel = /Rails\.application\.configure do/ + data = yield if !data && block_given? + + in_root do + if options[:env].nil? + inject_into_file 'config/application.rb', "\n #{data}", after: sentinel, verbose: false + else + Array(options[:env]).each do |env| + inject_into_file "config/environments/#{env}.rb", "\n #{data}", after: env_file_sentinel, verbose: false + end + end + end + end + alias :application :environment + + # Run a command in git. + # + # git :init + # git add: "this.file that.rb" + # git add: "onefile.rb", rm: "badfile.cxx" + def git(commands={}) + if commands.is_a?(Symbol) + run "git #{commands}" + else + commands.each do |cmd, options| + run "git #{cmd} #{options}" + end + end + end + + # Create a new file in the <tt>vendor/</tt> directory. Code can be specified + # in a block or a data string can be given. + # + # vendor("sekrit.rb") do + # sekrit_salt = "#{Time.now}--#{3.years.ago}--#{rand}--" + # "salt = '#{sekrit_salt}'" + # end + # + # vendor("foreign.rb", "# Foreign code is fun") + def vendor(filename, data=nil, &block) + log :vendor, filename + create_file("vendor/#{filename}", data, verbose: false, &block) + end + + # Create a new file in the lib/ directory. Code can be specified + # in a block or a data string can be given. + # + # lib("crypto.rb") do + # "crypted_special_value = '#{rand}--#{Time.now}--#{rand(1337)}--'" + # end + # + # lib("foreign.rb", "# Foreign code is fun") + def lib(filename, data=nil, &block) + log :lib, filename + create_file("lib/#{filename}", data, verbose: false, &block) + end + + # Create a new +Rakefile+ with the provided code (either in a block or a string). + # + # rakefile("bootstrap.rake") do + # project = ask("What is the UNIX name of your project?") + # + # <<-TASK + # namespace :#{project} do + # task :bootstrap do + # puts "I like boots!" + # end + # end + # TASK + # end + # + # rakefile('seed.rake', 'puts "Planting seeds"') + def rakefile(filename, data=nil, &block) + log :rakefile, filename + create_file("lib/tasks/#{filename}", data, verbose: false, &block) + end + + # Create a new initializer with the provided code (either in a block or a string). + # + # initializer("globals.rb") do + # data = "" + # + # ['MY_WORK', 'ADMINS', 'BEST_COMPANY_EVAR'].each do |const| + # data << "#{const} = :entp\n" + # end + # + # data + # end + # + # initializer("api.rb", "API_KEY = '123456'") + def initializer(filename, data=nil, &block) + log :initializer, filename + create_file("config/initializers/#{filename}", data, verbose: false, &block) + end + + # Generate something using a generator from Rails or a plugin. + # The second parameter is the argument string that is passed to + # the generator or an Array that is joined. + # + # generate(:authenticated, "user session") + def generate(what, *args) + log :generate, what + argument = args.flat_map(&:to_s).join(" ") + + in_root { run_ruby_script("bin/rails generate #{what} #{argument}", verbose: false) } + end + + # Runs the supplied rake task + # + # rake("db:migrate") + # rake("db:migrate", env: "production") + # rake("gems:install", sudo: true) + def rake(command, options={}) + log :rake, command + env = options[:env] || ENV["RAILS_ENV"] || 'development' + sudo = options[:sudo] && RbConfig::CONFIG['host_os'] !~ /mswin|mingw/ ? 'sudo ' : '' + in_root { run("#{sudo}#{extify(:rake)} #{command} RAILS_ENV=#{env}", verbose: false) } + end + + # Just run the capify command in root + # + # capify! + def capify! + log :capify, "" + in_root { run("#{extify(:capify)} .", verbose: false) } + end + + # Make an entry in Rails routing file <tt>config/routes.rb</tt> + # + # route "root 'welcome#index'" + def route(routing_code) + log :route, routing_code + sentinel = /\.routes\.draw do\s*\n/m + + in_root do + inject_into_file 'config/routes.rb', " #{routing_code}\n", { after: sentinel, verbose: false, force: false } + end + end + + # Reads the given file at the source root and prints it in the console. + # + # readme "README" + def readme(path) + log File.read(find_in_source_paths(path)) + end + + # Registers a callback to be executed after bundle and spring binstubs + # have run. + # + # after_bundle do + # git add: '.' + # end + def after_bundle(&block) + @after_bundle_callbacks << block + end + + protected + + # Define log for backwards compatibility. If just one argument is sent, + # invoke say, otherwise invoke say_status. Differently from say and + # similarly to say_status, this method respects the quiet? option given. + def log(*args) + if args.size == 1 + say args.first.to_s unless options.quiet? + else + args << (self.behavior == :invoke ? :green : :red) + say_status(*args) + end + end + + # Add an extension to the given name based on the platform. + def extify(name) + if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ + "#{name}.bat" + else + name + end + end + + # Surround string with single quotes if there is no quotes. + # Otherwise fall back to double quotes + def quote(value) + return value.inspect unless value.is_a? String + + if value.include?("'") + value.inspect + else + "'#{value}'" + end + end + end + end +end diff --git a/railties/lib/rails/generators/actions/create_migration.rb b/railties/lib/rails/generators/actions/create_migration.rb new file mode 100644 index 0000000000..cffdef6ec9 --- /dev/null +++ b/railties/lib/rails/generators/actions/create_migration.rb @@ -0,0 +1,69 @@ +require 'thor/actions' + +module Rails + module Generators + module Actions + class CreateMigration < Thor::Actions::CreateFile + + def migration_dir + File.dirname(@destination) + end + + def migration_file_name + @base.migration_file_name + end + + def identical? + exists? && File.binread(existing_migration) == render + end + + def revoke! + say_destination = exists? ? relative_existing_migration : relative_destination + say_status :remove, :red, say_destination + return unless exists? + ::FileUtils.rm_rf(existing_migration) unless pretend? + existing_migration + end + + def relative_existing_migration + base.relative_to_original_destination_root(existing_migration) + end + + def existing_migration + @existing_migration ||= begin + @base.class.migration_exists?(migration_dir, migration_file_name) || + File.exist?(@destination) && @destination + end + end + alias :exists? :existing_migration + + protected + + def on_conflict_behavior + options = base.options.merge(config) + if identical? + say_status :identical, :blue, relative_existing_migration + elsif options[:force] + say_status :remove, :green, relative_existing_migration + say_status :create, :green + unless pretend? + ::FileUtils.rm_rf(existing_migration) + yield + end + elsif options[:skip] + say_status :skip, :yellow + else + say_status :conflict, :red + raise Error, "Another migration is already named #{migration_file_name}: " + + "#{existing_migration}. Use --force to replace this migration " + + "or --skip to ignore conflicted file." + end + end + + def say_status(status, color, message = relative_destination) + base.shell.say_status(status, message, color) if config[:verbose] + end + end + end + end +end diff --git a/railties/lib/rails/generators/active_model.rb b/railties/lib/rails/generators/active_model.rb new file mode 100644 index 0000000000..6183944bb0 --- /dev/null +++ b/railties/lib/rails/generators/active_model.rb @@ -0,0 +1,78 @@ +module Rails + module Generators + # ActiveModel is a class to be implemented by each ORM to allow Rails to + # generate customized controller code. + # + # The API has the same methods as ActiveRecord, but each method returns a + # string that matches the ORM API. + # + # For example: + # + # ActiveRecord::Generators::ActiveModel.find(Foo, "params[:id]") + # # => "Foo.find(params[:id])" + # + # DataMapper::Generators::ActiveModel.find(Foo, "params[:id]") + # # => "Foo.get(params[:id])" + # + # On initialization, the ActiveModel accepts the instance name that will + # receive the calls: + # + # builder = ActiveRecord::Generators::ActiveModel.new "@foo" + # builder.save # => "@foo.save" + # + # The only exception in ActiveModel for ActiveRecord is the use of self.build + # instead of self.new. + # + class ActiveModel + attr_reader :name + + def initialize(name) + @name = name + end + + # GET index + def self.all(klass) + "#{klass}.all" + end + + # GET show + # GET edit + # PATCH/PUT update + # DELETE destroy + def self.find(klass, params=nil) + "#{klass}.find(#{params})" + end + + # GET new + # POST create + def self.build(klass, params=nil) + if params + "#{klass}.new(#{params})" + else + "#{klass}.new" + end + end + + # POST create + def save + "#{name}.save" + end + + # PATCH/PUT update + def update(params=nil) + "#{name}.update(#{params})" + end + + # POST create + # PATCH/PUT update + def errors + "#{name}.errors" + end + + # DELETE destroy + def destroy + "#{name}.destroy" + end + end + end +end diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb new file mode 100644 index 0000000000..56c9d3e354 --- /dev/null +++ b/railties/lib/rails/generators/app_base.rb @@ -0,0 +1,382 @@ +require 'digest/md5' +require 'active_support/core_ext/string/strip' +require 'rails/version' unless defined?(Rails::VERSION) +require 'open-uri' +require 'uri' +require 'rails/generators' +require 'active_support/core_ext/array/extract_options' + +module Rails + module Generators + class AppBase < Base # :nodoc: + DATABASES = %w( mysql oracle postgresql sqlite3 frontbase ibm_db sqlserver ) + JDBC_DATABASES = %w( jdbcmysql jdbcsqlite3 jdbcpostgresql jdbc ) + DATABASES.concat(JDBC_DATABASES) + + attr_accessor :rails_template + add_shebang_option! + + argument :app_path, type: :string + + def self.strict_args_position + false + end + + def self.add_shared_options_for(name) + class_option :template, type: :string, aliases: '-m', + desc: "Path to some #{name} template (can be a filesystem path or URL)" + + class_option :skip_gemfile, type: :boolean, default: false, + desc: "Don't create a Gemfile" + + class_option :skip_bundle, type: :boolean, aliases: '-B', default: false, + desc: "Don't run bundle install" + + class_option :skip_git, type: :boolean, aliases: '-G', default: false, + desc: 'Skip .gitignore file' + + class_option :skip_keeps, type: :boolean, default: false, + desc: 'Skip source control .keep files' + + class_option :skip_action_mailer, type: :boolean, aliases: "-M", + default: false, + desc: "Skip Action Mailer files" + + class_option :skip_active_record, type: :boolean, aliases: '-O', default: false, + desc: 'Skip Active Record files' + + class_option :skip_sprockets, type: :boolean, aliases: '-S', default: false, + desc: 'Skip Sprockets files' + + class_option :skip_spring, type: :boolean, default: false, + desc: "Don't install Spring application preloader" + + class_option :database, type: :string, aliases: '-d', default: 'sqlite3', + desc: "Preconfigure for selected database (options: #{DATABASES.join('/')})" + + class_option :javascript, type: :string, aliases: '-j', default: 'jquery', + desc: 'Preconfigure for selected JavaScript library' + + class_option :skip_javascript, type: :boolean, aliases: '-J', default: false, + desc: 'Skip JavaScript files' + + class_option :dev, type: :boolean, default: false, + desc: "Setup the #{name} with Gemfile pointing to your Rails checkout" + + class_option :edge, type: :boolean, default: false, + desc: "Setup the #{name} with Gemfile pointing to Rails repository" + + class_option :skip_turbolinks, type: :boolean, default: false, + desc: 'Skip turbolinks gem' + + class_option :skip_test, type: :boolean, aliases: '-T', default: false, + desc: 'Skip test files' + + class_option :rc, type: :string, default: false, + desc: "Path to file containing extra configuration options for rails command" + + class_option :no_rc, type: :boolean, default: false, + desc: 'Skip loading of extra configuration options from .railsrc file' + + class_option :help, type: :boolean, aliases: '-h', group: :rails, + desc: 'Show this help message and quit' + end + + def initialize(*args) + @gem_filter = lambda { |gem| true } + @extra_entries = [] + super + convert_database_option_for_jruby + end + + protected + + def gemfile_entry(name, *args) + options = args.extract_options! + version = args.first + github = options[:github] + path = options[:path] + + if github + @extra_entries << GemfileEntry.github(name, github) + elsif path + @extra_entries << GemfileEntry.path(name, path) + else + @extra_entries << GemfileEntry.version(name, version) + end + self + end + + def gemfile_entries + [rails_gemfile_entry, + database_gemfile_entry, + assets_gemfile_entry, + javascript_gemfile_entry, + jbuilder_gemfile_entry, + psych_gemfile_entry, + @extra_entries].flatten.find_all(&@gem_filter) + end + + def add_gem_entry_filter + @gem_filter = lambda { |next_filter, entry| + yield(entry) && next_filter.call(entry) + }.curry[@gem_filter] + end + + def builder + @builder ||= begin + builder_class = get_builder_class + builder_class.include(ActionMethods) + builder_class.new(self) + end + end + + def build(meth, *args) + builder.send(meth, *args) if builder.respond_to?(meth) + end + + def create_root + valid_const? + + empty_directory '.' + FileUtils.cd(destination_root) unless options[:pretend] + end + + def apply_rails_template + apply rails_template if rails_template + rescue Thor::Error, LoadError, Errno::ENOENT => e + raise Error, "The template [#{rails_template}] could not be loaded. Error: #{e}" + end + + def set_default_accessors! + self.destination_root = File.expand_path(app_path, destination_root) + self.rails_template = case options[:template] + when /^https?:\/\// + options[:template] + when String + File.expand_path(options[:template], Dir.pwd) + else + options[:template] + end + end + + def database_gemfile_entry + return [] if options[:skip_active_record] + gem_name, gem_version = gem_for_database + GemfileEntry.version gem_name, gem_version, + "Use #{options[:database]} as the database for Active Record" + end + + def include_all_railties? + options.values_at(:skip_active_record, :skip_action_mailer, :skip_test, :skip_sprockets).none? + end + + def comment_if(value) + options[value] ? '# ' : '' + end + + def keeps? + !options[:skip_keeps] + end + + def sqlite3? + !options[:skip_active_record] && options[:database] == 'sqlite3' + end + + class GemfileEntry < Struct.new(:name, :version, :comment, :options, :commented_out) + def initialize(name, version, comment, options = {}, commented_out = false) + super + end + + def self.github(name, github, branch = nil, comment = nil) + if branch + new(name, nil, comment, github: github, branch: branch) + else + new(name, nil, comment, github: github) + end + end + + def self.version(name, version, comment = nil) + new(name, version, comment) + end + + def self.path(name, path, comment = nil) + new(name, nil, comment, path: path) + end + + def version + version = super + + if version.is_a?(Array) + version.join("', '") + else + version + end + end + end + + def rails_gemfile_entry + dev_edge_common = [ + GemfileEntry.github('sprockets-rails', 'rails/sprockets-rails'), + GemfileEntry.github('sprockets', 'rails/sprockets'), + GemfileEntry.github('sass-rails', 'rails/sass-rails'), + GemfileEntry.github('arel', 'rails/arel'), + GemfileEntry.github('rack', 'rack/rack') + ] + if options.dev? + [ + GemfileEntry.path('rails', Rails::Generators::RAILS_DEV_PATH) + ] + dev_edge_common + elsif options.edge? + [ + GemfileEntry.github('rails', 'rails/rails') + ] + dev_edge_common + else + [GemfileEntry.version('rails', + Rails::VERSION::STRING, + "Bundle edge Rails instead: gem 'rails', github: 'rails/rails'")] + end + end + + def gem_for_database + # %w( mysql oracle postgresql sqlite3 frontbase ibm_db sqlserver jdbcmysql jdbcsqlite3 jdbcpostgresql ) + case options[:database] + when "oracle" then ["ruby-oci8", nil] + when "postgresql" then ["pg", ["~> 0.18"]] + when "frontbase" then ["ruby-frontbase", nil] + when "mysql" then ["mysql2", [">= 0.3.18", "< 0.5"]] + when "sqlserver" then ["activerecord-sqlserver-adapter", nil] + when "jdbcmysql" then ["activerecord-jdbcmysql-adapter", nil] + when "jdbcsqlite3" then ["activerecord-jdbcsqlite3-adapter", nil] + when "jdbcpostgresql" then ["activerecord-jdbcpostgresql-adapter", nil] + when "jdbc" then ["activerecord-jdbc-adapter", nil] + else [options[:database], nil] + end + end + + def convert_database_option_for_jruby + if defined?(JRUBY_VERSION) + case options[:database] + when "oracle" then options[:database].replace "jdbc" + when "postgresql" then options[:database].replace "jdbcpostgresql" + when "mysql" then options[:database].replace "jdbcmysql" + when "sqlite3" then options[:database].replace "jdbcsqlite3" + end + end + end + + def assets_gemfile_entry + return [] if options[:skip_sprockets] + + gems = [] + + gems << GemfileEntry.version('uglifier', + '>= 1.3.0', + 'Use Uglifier as compressor for JavaScript assets') + + gems + 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 + + def coffee_gemfile_entry + comment = 'Use CoffeeScript for .coffee assets and views' + if options.dev? || options.edge? + GemfileEntry.github 'coffee-rails', 'rails/coffee-rails', nil, comment + else + GemfileEntry.version 'coffee-rails', '~> 4.1.0', comment + end + end + + def javascript_gemfile_entry + if options[:skip_javascript] + [] + else + gems = [coffee_gemfile_entry, javascript_runtime_gemfile_entry] + gems << GemfileEntry.version("#{options[:javascript]}-rails", nil, + "Use #{options[:javascript]} as the JavaScript library") + + unless options[:skip_turbolinks] + gems << GemfileEntry.version("turbolinks", nil, + "Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks") + end + + gems + end + end + + def javascript_runtime_gemfile_entry + comment = 'See https://github.com/rails/execjs#readme for more supported runtimes' + if defined?(JRUBY_VERSION) + GemfileEntry.version 'therubyrhino', nil, comment + else + GemfileEntry.new 'therubyracer', nil, comment, { platforms: :ruby }, true + end + end + + def psych_gemfile_entry + return [] unless defined?(Rubinius) + + comment = 'Use Psych as the YAML engine, instead of Syck, so serialized ' \ + 'data can be read safely from different rubies (see http://git.io/uuLVag)' + GemfileEntry.new('psych', '~> 2.0', comment, platforms: :rbx) + end + + def bundle_command(command) + say_status :run, "bundle #{command}" + + # We are going to shell out rather than invoking Bundler::CLI.new(command) + # because `rails new` loads the Thor gem and on the other hand bundler uses + # its own vendored Thor, which could be a different version. Running both + # things in the same process is a recipe for a night with paracetamol. + # + # We unset temporary bundler variables to load proper bundler and Gemfile. + # + # Thanks to James Tucker for the Gem tricks involved in this call. + _bundle_command = Gem.bin_path('bundler', 'bundle') + + require 'bundler' + Bundler.with_clean_env do + full_command = %Q["#{Gem.ruby}" "#{_bundle_command}" #{command}] + if options[:quiet] + system(full_command, out: File::NULL) + else + system(full_command) + end + end + end + + def bundle_install? + !(options[:skip_gemfile] || options[:skip_bundle] || options[:pretend]) + end + + def spring_install? + !options[:skip_spring] && !options.dev? && Process.respond_to?(:fork) && !RUBY_PLATFORM.include?("cygwin") + end + + def run_bundle + bundle_command('install') if bundle_install? + end + + def generate_spring_binstubs + if bundle_install? && spring_install? + bundle_command("exec spring binstub --all") + end + end + + def empty_directory_with_keep_file(destination, config = {}) + empty_directory(destination, config) + keep_file(destination) + end + + def keep_file(destination) + create_file("#{destination}/.keep") if keeps? + end + end + end +end diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb new file mode 100644 index 0000000000..c72ec400a0 --- /dev/null +++ b/railties/lib/rails/generators/base.rb @@ -0,0 +1,379 @@ +begin + require 'thor/group' +rescue LoadError + puts "Thor is not available.\nIf you ran this command from a git checkout " \ + "of Rails, please make sure thor is installed,\nand run this command " \ + "as `ruby #{$0} #{(ARGV | ['--dev']).join(" ")}`" + exit +end + +module Rails + module Generators + class Error < Thor::Error # :nodoc: + end + + class Base < Thor::Group + include Thor::Actions + include Rails::Generators::Actions + + add_runtime_options! + strict_args_position! + + # Returns the source root for this generator using default_source_root as default. + def self.source_root(path=nil) + @_source_root = path if path + @_source_root ||= default_source_root + end + + # Tries to get the description from a USAGE file one folder above the source + # root otherwise uses a default description. + def self.desc(description=nil) + return super if description + + @desc ||= if usage_path + ERB.new(File.read(usage_path)).result(binding) + else + "Description:\n Create #{base_name.humanize.downcase} files for #{generator_name} generator." + end + end + + # Convenience method to get the namespace from the class name. It's the + # same as Thor default except that the Generator at the end of the class + # is removed. + def self.namespace(name=nil) + return super if name + @namespace ||= super.sub(/_generator$/, '').sub(/:generators:/, ':') + end + + # Convenience method to hide this generator from the available ones when + # running rails generator command. + def self.hide! + Rails::Generators.hide_namespace self.namespace + end + + # Invoke a generator based on the value supplied by the user to the + # given option named "name". A class option is created when this method + # is invoked and you can set a hash to customize it. + # + # ==== Examples + # + # module Rails::Generators + # class ControllerGenerator < Base + # hook_for :test_framework, aliases: "-t" + # end + # end + # + # The example above will create a test framework option and will invoke + # a generator based on the user supplied value. + # + # For example, if the user invoke the controller generator as: + # + # rails generate controller Account --test-framework=test_unit + # + # The controller generator will then try to invoke the following generators: + # + # "rails:test_unit", "test_unit:controller", "test_unit" + # + # Notice that "rails:generators:test_unit" could be loaded as well, what + # Rails looks for is the first and last parts of the namespace. This is what + # allows any test framework to hook into Rails as long as it provides any + # of the hooks above. + # + # ==== Options + # + # The first and last part used to find the generator to be invoked are + # guessed based on class invokes hook_for, as noticed in the example above. + # This can be customized with two options: :in and :as. + # + # Let's suppose you are creating a generator that needs to invoke the + # controller generator from test unit. Your first attempt is: + # + # class AwesomeGenerator < Rails::Generators::Base + # hook_for :test_framework + # end + # + # The lookup in this case for test_unit as input is: + # + # "test_unit:awesome", "test_unit" + # + # Which is not the desired lookup. You can change it by providing the + # :as option: + # + # class AwesomeGenerator < Rails::Generators::Base + # hook_for :test_framework, as: :controller + # end + # + # And now it will look up at: + # + # "test_unit:controller", "test_unit" + # + # Similarly, if you want it to also look up in the rails namespace, you + # just need to provide the :in value: + # + # class AwesomeGenerator < Rails::Generators::Base + # hook_for :test_framework, in: :rails, as: :controller + # end + # + # And the lookup is exactly the same as previously: + # + # "rails:test_unit", "test_unit:controller", "test_unit" + # + # ==== Switches + # + # All hooks come with switches for user interface. If you do not want + # to use any test framework, you can do: + # + # rails generate controller Account --skip-test-framework + # + # Or similarly: + # + # rails generate controller Account --no-test-framework + # + # ==== Boolean hooks + # + # In some cases, you may want to provide a boolean hook. For example, webrat + # developers might want to have webrat available on controller generator. + # This can be achieved as: + # + # Rails::Generators::ControllerGenerator.hook_for :webrat, type: :boolean + # + # Then, if you want webrat to be invoked, just supply: + # + # rails generate controller Account --webrat + # + # The hooks lookup is similar as above: + # + # "rails:generators:webrat", "webrat:generators:controller", "webrat" + # + # ==== Custom invocations + # + # You can also supply a block to hook_for to customize how the hook is + # going to be invoked. The block receives two arguments, an instance + # of the current class and the class to be invoked. + # + # For example, in the resource generator, the controller should be invoked + # with a pluralized class name. But by default it is invoked with the same + # name as the resource generator, which is singular. To change this, we + # can give a block to customize how the controller can be invoked. + # + # hook_for :resource_controller do |instance, controller| + # instance.invoke controller, [ instance.name.pluralize ] + # end + # + def self.hook_for(*names, &block) + options = names.extract_options! + in_base = options.delete(:in) || base_name + as_hook = options.delete(:as) || generator_name + + names.each do |name| + unless class_options.key?(name) + defaults = if options[:type] == :boolean + { } + elsif [true, false].include?(default_value_for_option(name, options)) + { banner: "" } + else + { desc: "#{name.to_s.humanize} to be invoked", banner: "NAME" } + end + + class_option(name, defaults.merge!(options)) + end + + hooks[name] = [ in_base, as_hook ] + invoke_from_option(name, options, &block) + end + end + + # Remove a previously added hook. + # + # remove_hook_for :orm + def self.remove_hook_for(*names) + remove_invocation(*names) + + names.each do |name| + hooks.delete(name) + end + end + + # Make class option aware of Rails::Generators.options and Rails::Generators.aliases. + def self.class_option(name, options={}) #:nodoc: + options[:desc] = "Indicates when to generate #{name.to_s.humanize.downcase}" unless options.key?(:desc) + options[:aliases] = default_aliases_for_option(name, options) + options[:default] = default_value_for_option(name, options) + super(name, options) + end + + # Returns the default source root for a given generator. This is used internally + # by rails to set its generators source root. If you want to customize your source + # root, you should use source_root. + def self.default_source_root + return unless base_name && generator_name + return unless default_generator_root + path = File.join(default_generator_root, 'templates') + path if File.exist?(path) + end + + # Returns the base root for a common set of generators. This is used to dynamically + # guess the default source root. + def self.base_root + File.dirname(__FILE__) + end + + # Cache source root and add lib/generators/base/generator/templates to + # source paths. + def self.inherited(base) #:nodoc: + super + + # Invoke source_root so the default_source_root is set. + base.source_root + + if base.name && base.name !~ /Base$/ + Rails::Generators.subclasses << base + + Rails::Generators.templates_path.each do |path| + if base.name.include?('::') + base.source_paths << File.join(path, base.base_name, base.generator_name) + else + base.source_paths << File.join(path, base.generator_name) + end + end + end + end + + protected + + # Check whether the given class names are already taken by user + # application or Ruby on Rails. + def class_collisions(*class_names) #:nodoc: + return unless behavior == :invoke + + class_names.flatten.each do |class_name| + class_name = class_name.to_s + next if class_name.strip.empty? + + # Split the class from its module nesting + nesting = class_name.split('::') + last_name = nesting.pop + last = extract_last_module(nesting) + + if last && last.const_defined?(last_name.camelize, false) + raise Error, "The name '#{class_name}' is either already used in your application " << + "or reserved by Ruby on Rails. Please choose an alternative and run " << + "this generator again." + end + end + end + + # Takes in an array of nested modules and extracts the last module + def extract_last_module(nesting) + nesting.inject(Object) do |last_module, nest| + break unless last_module.const_defined?(nest, false) + last_module.const_get(nest) + end + end + + # Use Rails default banner. + def self.banner + "rails generate #{namespace.sub(/^rails:/,'')} #{self.arguments.map(&:usage).join(' ')} [options]".gsub(/\s+/, ' ') + end + + # Sets the base_name taking into account the current class namespace. + def self.base_name + @base_name ||= begin + if base = name.to_s.split('::').first + base.underscore + end + end + end + + # Removes the namespaces and get the generator name. For example, + # Rails::Generators::ModelGenerator will return "model" as generator name. + def self.generator_name + @generator_name ||= begin + if generator = name.to_s.split('::').last + generator.sub!(/Generator$/, '') + generator.underscore + end + end + end + + # Returns the default value for the option name given doing a lookup in + # Rails::Generators.options. + def self.default_value_for_option(name, options) + default_for_option(Rails::Generators.options, name, options, options[:default]) + end + + # Returns default aliases for the option name given doing a lookup in + # Rails::Generators.aliases. + def self.default_aliases_for_option(name, options) + default_for_option(Rails::Generators.aliases, name, options, options[:aliases]) + end + + # Returns default for the option name given doing a lookup in config. + def self.default_for_option(config, name, options, default) + if generator_name and c = config[generator_name.to_sym] and c.key?(name) + c[name] + elsif base_name and c = config[base_name.to_sym] and c.key?(name) + c[name] + elsif config[:rails].key?(name) + config[:rails][name] + else + default + end + end + + # Keep hooks configuration that are used on prepare_for_invocation. + def self.hooks #:nodoc: + @hooks ||= from_superclass(:hooks, {}) + end + + # Prepare class invocation to search on Rails namespace if a previous + # added hook is being used. + def self.prepare_for_invocation(name, value) #:nodoc: + return super unless value.is_a?(String) || value.is_a?(Symbol) + + if value && constants = self.hooks[name] + value = name if TrueClass === value + Rails::Generators.find_by_namespace(value, *constants) + elsif klass = Rails::Generators.find_by_namespace(value) + klass + else + super + end + end + + # Small macro to add ruby as an option to the generator with proper + # default value plus an instance helper method called shebang. + def self.add_shebang_option! + class_option :ruby, type: :string, aliases: "-r", default: Thor::Util.ruby_command, + desc: "Path to the Ruby binary of your choice", banner: "PATH" + + no_tasks { + define_method :shebang do + @shebang ||= begin + command = if options[:ruby] == Thor::Util.ruby_command + "/usr/bin/env #{File.basename(Thor::Util.ruby_command)}" + else + options[:ruby] + end + "#!#{command}" + end + end + } + end + + def self.usage_path + paths = [ + source_root && File.expand_path("../USAGE", source_root), + default_generator_root && File.join(default_generator_root, "USAGE") + ] + paths.compact.detect { |path| File.exist? path } + end + + def self.default_generator_root + path = File.expand_path(File.join(base_name, generator_name), base_root) + path if File.exist?(path) + end + + end + end +end diff --git a/railties/lib/rails/generators/css/assets/assets_generator.rb b/railties/lib/rails/generators/css/assets/assets_generator.rb new file mode 100644 index 0000000000..e4a305f4b3 --- /dev/null +++ b/railties/lib/rails/generators/css/assets/assets_generator.rb @@ -0,0 +1,13 @@ +require "rails/generators/named_base" + +module Css # :nodoc: + module Generators # :nodoc: + class AssetsGenerator < Rails::Generators::NamedBase # :nodoc: + source_root File.expand_path("../templates", __FILE__) + + def copy_stylesheet + copy_file "stylesheet.css", File.join('app/assets/stylesheets', class_path, "#{file_name}.css") + end + end + end +end diff --git a/railties/lib/rails/generators/css/assets/templates/stylesheet.css b/railties/lib/rails/generators/css/assets/templates/stylesheet.css new file mode 100644 index 0000000000..afad32db02 --- /dev/null +++ b/railties/lib/rails/generators/css/assets/templates/stylesheet.css @@ -0,0 +1,4 @@ +/* + Place all the styles related to the matching controller here. + They will automatically be included in application.css. +*/ diff --git a/railties/lib/rails/generators/css/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/css/scaffold/scaffold_generator.rb new file mode 100644 index 0000000000..cf534030f9 --- /dev/null +++ b/railties/lib/rails/generators/css/scaffold/scaffold_generator.rb @@ -0,0 +1,16 @@ +require "rails/generators/named_base" + +module Css # :nodoc: + module Generators # :nodoc: + class ScaffoldGenerator < Rails::Generators::NamedBase # :nodoc: + # In order to allow the Sass generators to pick up the default Rails CSS and + # transform it, we leave it in a standard location for the CSS stylesheet + # generators to handle. For the simple, default case, just copy it over. + def copy_stylesheet + dir = Rails::Generators::ScaffoldGenerator.source_root + file = File.join(dir, "scaffold.css") + create_file "app/assets/stylesheets/scaffold.css", File.read(file) + end + end + end +end diff --git a/railties/lib/rails/generators/erb.rb b/railties/lib/rails/generators/erb.rb new file mode 100644 index 0000000000..0755ac335c --- /dev/null +++ b/railties/lib/rails/generators/erb.rb @@ -0,0 +1,25 @@ +require 'rails/generators/named_base' + +module Erb # :nodoc: + module Generators # :nodoc: + class Base < Rails::Generators::NamedBase #:nodoc: + protected + + def formats + [format] + end + + def format + :html + end + + def handler + :erb + end + + def filename_with_extensions(name, format = self.format) + [name, format, handler].compact.join(".") + end + end + end +end diff --git a/railties/lib/rails/generators/erb/controller/controller_generator.rb b/railties/lib/rails/generators/erb/controller/controller_generator.rb new file mode 100644 index 0000000000..94c1b835d1 --- /dev/null +++ b/railties/lib/rails/generators/erb/controller/controller_generator.rb @@ -0,0 +1,22 @@ +require 'rails/generators/erb' + +module Erb # :nodoc: + module Generators # :nodoc: + class ControllerGenerator < Base # :nodoc: + argument :actions, type: :array, default: [], banner: "action action" + + def copy_view_files + base_path = File.join("app/views", class_path, file_name) + empty_directory base_path + + actions.each do |action| + @action = action + formats.each do |format| + @path = File.join(base_path, filename_with_extensions(action, format)) + template filename_with_extensions(:view, format), @path + end + end + end + end + end +end diff --git a/railties/lib/rails/generators/erb/controller/templates/view.html.erb b/railties/lib/rails/generators/erb/controller/templates/view.html.erb new file mode 100644 index 0000000000..cd54d13d83 --- /dev/null +++ b/railties/lib/rails/generators/erb/controller/templates/view.html.erb @@ -0,0 +1,2 @@ +<h1><%= class_name %>#<%= @action %></h1> +<p>Find me in <%= @path %></p> diff --git a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb new file mode 100644 index 0000000000..65563aa6db --- /dev/null +++ b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb @@ -0,0 +1,40 @@ +require 'rails/generators/erb' + +module Erb # :nodoc: + module Generators # :nodoc: + class MailerGenerator < Base # :nodoc: + argument :actions, type: :array, default: [], banner: "method method" + + def copy_view_files + view_base_path = File.join("app/views", class_path, file_name + '_mailer') + empty_directory view_base_path + + if self.behavior == :invoke + formats.each do |format| + layout_path = File.join("app/views/layouts", filename_with_extensions("mailer", format)) + template filename_with_extensions(:layout, format), layout_path + end + end + + actions.each do |action| + @action = action + + formats.each do |format| + @path = File.join(view_base_path, filename_with_extensions(action, format)) + template filename_with_extensions(:view, format), @path + end + end + end + + protected + + def formats + [:text, :html] + end + + def file_name + @_file_name ||= super.gsub(/\_mailer/i, '') + end + end + end +end diff --git a/railties/lib/rails/generators/erb/mailer/templates/layout.html.erb b/railties/lib/rails/generators/erb/mailer/templates/layout.html.erb new file mode 100644 index 0000000000..93110e74ad --- /dev/null +++ b/railties/lib/rails/generators/erb/mailer/templates/layout.html.erb @@ -0,0 +1,5 @@ +<html> + <body> + <%%= yield %> + </body> +</html> diff --git a/railties/lib/rails/generators/erb/mailer/templates/layout.text.erb b/railties/lib/rails/generators/erb/mailer/templates/layout.text.erb new file mode 100644 index 0000000000..6363733e6e --- /dev/null +++ b/railties/lib/rails/generators/erb/mailer/templates/layout.text.erb @@ -0,0 +1 @@ +<%%= yield %> diff --git a/railties/lib/rails/generators/erb/mailer/templates/view.html.erb b/railties/lib/rails/generators/erb/mailer/templates/view.html.erb new file mode 100644 index 0000000000..b5045671b3 --- /dev/null +++ b/railties/lib/rails/generators/erb/mailer/templates/view.html.erb @@ -0,0 +1,5 @@ +<h1><%= class_name %>#<%= @action %></h1> + +<p> + <%%= @greeting %>, find me in <%= @path %> +</p> diff --git a/railties/lib/rails/generators/erb/mailer/templates/view.text.erb b/railties/lib/rails/generators/erb/mailer/templates/view.text.erb new file mode 100644 index 0000000000..342285df19 --- /dev/null +++ b/railties/lib/rails/generators/erb/mailer/templates/view.text.erb @@ -0,0 +1,3 @@ +<%= class_name %>#<%= @action %> + +<%%= @greeting %>, find me in <%= @path %> diff --git a/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb new file mode 100644 index 0000000000..c94829a0ae --- /dev/null +++ b/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb @@ -0,0 +1,31 @@ +require 'rails/generators/erb' +require 'rails/generators/resource_helpers' + +module Erb # :nodoc: + module Generators # :nodoc: + class ScaffoldGenerator < Base # :nodoc: + include Rails::Generators::ResourceHelpers + + argument :attributes, type: :array, default: [], banner: "field:type field:type" + + def create_root_folder + empty_directory File.join("app/views", controller_file_path) + end + + def copy_view_files + available_views.each do |view| + formats.each do |format| + filename = filename_with_extensions(view, format) + template filename, File.join("app/views", controller_file_path, filename) + end + end + end + + protected + + def available_views + %w(index edit show new _form) + end + end + end +end diff --git a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb new file mode 100644 index 0000000000..519b6c8603 --- /dev/null +++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb @@ -0,0 +1,34 @@ +<%%= form_for(<%= singular_table_name %>) do |f| %> + <%% if <%= singular_table_name %>.errors.any? %> + <div id="error_explanation"> + <h2><%%= pluralize(<%= singular_table_name %>.errors.count, "error") %> prohibited this <%= singular_table_name %> from being saved:</h2> + + <ul> + <%% <%= singular_table_name %>.errors.full_messages.each do |message| %> + <li><%%= message %></li> + <%% end %> + </ul> + </div> + <%% end %> + +<% attributes.each do |attribute| -%> + <div class="field"> +<% if attribute.password_digest? -%> + <%%= f.label :password %> + <%%= f.password_field :password %> + </div> + + <div class="field"> + <%%= f.label :password_confirmation %> + <%%= f.password_field :password_confirmation %> +<% else -%> + <%%= f.label :<%= attribute.column_name %> %> + <%%= f.<%= attribute.field_type %> :<%= attribute.column_name %> %> +<% end -%> + </div> + +<% end -%> + <div class="actions"> + <%%= f.submit %> + </div> +<%% end %> diff --git a/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb new file mode 100644 index 0000000000..81329473d9 --- /dev/null +++ b/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb @@ -0,0 +1,6 @@ +<h1>Editing <%= singular_table_name.titleize %></h1> + +<%%= render 'form', <%= singular_table_name %>: @<%= singular_table_name %> %> + +<%%= link_to 'Show', @<%= singular_table_name %> %> | +<%%= link_to 'Back', <%= index_helper %>_path %> diff --git a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb new file mode 100644 index 0000000000..5f4904fee1 --- /dev/null +++ b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb @@ -0,0 +1,31 @@ +<p id="notice"><%%= notice %></p> + +<h1><%= plural_table_name.titleize %></h1> + +<table> + <thead> + <tr> +<% attributes.reject(&:password_digest?).each do |attribute| -%> + <th><%= attribute.human_name %></th> +<% end -%> + <th colspan="3"></th> + </tr> + </thead> + + <tbody> + <%% @<%= plural_table_name %>.each do |<%= singular_table_name %>| %> + <tr> +<% attributes.reject(&:password_digest?).each do |attribute| -%> + <td><%%= <%= singular_table_name %>.<%= attribute.name %> %></td> +<% end -%> + <td><%%= link_to 'Show', <%= singular_table_name %> %></td> + <td><%%= link_to 'Edit', edit_<%= singular_table_name %>_path(<%= singular_table_name %>) %></td> + <td><%%= link_to 'Destroy', <%= singular_table_name %>, method: :delete, data: { confirm: 'Are you sure?' } %></td> + </tr> + <%% end %> + </tbody> +</table> + +<br> + +<%%= link_to 'New <%= singular_table_name.titleize %>', new_<%= singular_table_name %>_path %> diff --git a/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb new file mode 100644 index 0000000000..9b2b2f4875 --- /dev/null +++ b/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb @@ -0,0 +1,5 @@ +<h1>New <%= singular_table_name.titleize %></h1> + +<%%= render 'form', <%= singular_table_name %>: @<%= singular_table_name %> %> + +<%%= link_to 'Back', <%= index_helper %>_path %> diff --git a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb new file mode 100644 index 0000000000..5e634153be --- /dev/null +++ b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb @@ -0,0 +1,11 @@ +<p id="notice"><%%= notice %></p> + +<% attributes.reject(&:password_digest?).each do |attribute| -%> +<p> + <strong><%= attribute.human_name %>:</strong> + <%%= @<%= singular_table_name %>.<%= attribute.name %> %> +</p> + +<% end -%> +<%%= link_to 'Edit', edit_<%= singular_table_name %>_path(@<%= singular_table_name %>) %> | +<%%= link_to 'Back', <%= index_helper %>_path %> diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb new file mode 100644 index 0000000000..8145a26e22 --- /dev/null +++ b/railties/lib/rails/generators/generated_attribute.rb @@ -0,0 +1,174 @@ +require 'active_support/time' + +module Rails + module Generators + class GeneratedAttribute # :nodoc: + INDEX_OPTIONS = %w(index uniq) + UNIQ_INDEX_OPTIONS = %w(uniq) + + attr_accessor :name, :type + attr_reader :attr_options + attr_writer :index_name + + class << self + def parse(column_definition) + name, type, has_index = column_definition.split(':') + + # if user provided "name:index" instead of "name:string:index" + # type should be set blank so GeneratedAttribute's constructor + # could set it to :string + has_index, type = type, nil if INDEX_OPTIONS.include?(type) + + type, attr_options = *parse_type_and_options(type) + type = type.to_sym if type + + if type && reference?(type) + references_index = UNIQ_INDEX_OPTIONS.include?(has_index) ? { unique: true } : true + attr_options[:index] = references_index + end + + new(name, type, has_index, attr_options) + end + + def reference?(type) + [:references, :belongs_to].include? type + end + + private + + # parse possible attribute options like :limit for string/text/binary/integer, :precision/:scale for decimals or :polymorphic for references/belongs_to + # when declaring options curly brackets should be used + def parse_type_and_options(type) + case type + when /(string|text|binary|integer)\{(\d+)\}/ + return $1, limit: $2.to_i + when /decimal\{(\d+)[,.-](\d+)\}/ + return :decimal, precision: $1.to_i, scale: $2.to_i + when /(references|belongs_to)\{(.+)\}/ + type = $1 + provided_options = $2.split(/[,.-]/) + options = Hash[provided_options.map { |opt| [opt.to_sym, true] }] + return type, options + else + return type, {} + end + end + end + + def initialize(name, type=nil, index_type=false, attr_options={}) + @name = name + @type = type || :string + @has_index = INDEX_OPTIONS.include?(index_type) + @has_uniq_index = UNIQ_INDEX_OPTIONS.include?(index_type) + @attr_options = attr_options + end + + def field_type + @field_type ||= case type + when :integer then :number_field + when :float, :decimal then :text_field + when :time then :time_select + when :datetime, :timestamp then :datetime_select + when :date then :date_select + when :text then :text_area + when :boolean then :check_box + else + :text_field + end + end + + def default + @default ||= case type + when :integer then 1 + when :float then 1.5 + when :decimal then "9.99" + when :datetime, :timestamp, :time then Time.now.to_s(:db) + when :date then Date.today.to_s(:db) + when :string then name == "type" ? "" : "MyString" + when :text then "MyText" + when :boolean then false + when :references, :belongs_to then nil + else + "" + end + end + + def plural_name + name.sub(/_id$/, '').pluralize + end + + def singular_name + name.sub(/_id$/, '').singularize + end + + def human_name + name.humanize + end + + def index_name + @index_name ||= if polymorphic? + %w(id type).map { |t| "#{name}_#{t}" } + else + column_name + end + end + + def column_name + @column_name ||= reference? ? "#{name}_id" : name + end + + def foreign_key? + !!(name =~ /_id$/) + end + + def reference? + self.class.reference?(type) + end + + def polymorphic? + self.attr_options[:polymorphic] + end + + def required? + self.attr_options[:required] + end + + def has_index? + @has_index + end + + def has_uniq_index? + @has_uniq_index + end + + def password_digest? + name == 'password' && type == :digest + end + + def token? + type == :token + end + + def inject_options + "".tap { |s| options_for_migration.each { |k,v| s << ", #{k}: #{v.inspect}" } } + end + + def inject_index_options + has_uniq_index? ? ", unique: true" : "" + end + + def options_for_migration + @attr_options.dup.tap do |options| + if required? + options.delete(:required) + options[:null] = false + end + + if reference? && !polymorphic? + options[:foreign_key] = true + end + end + end + end + end +end diff --git a/railties/lib/rails/generators/js/assets/assets_generator.rb b/railties/lib/rails/generators/js/assets/assets_generator.rb new file mode 100644 index 0000000000..1e925b2cd2 --- /dev/null +++ b/railties/lib/rails/generators/js/assets/assets_generator.rb @@ -0,0 +1,13 @@ +require "rails/generators/named_base" + +module Js # :nodoc: + module Generators # :nodoc: + class AssetsGenerator < Rails::Generators::NamedBase # :nodoc: + source_root File.expand_path("../templates", __FILE__) + + def copy_javascript + copy_file "javascript.js", File.join('app/assets/javascripts', class_path, "#{file_name}.js") + end + end + end +end diff --git a/railties/lib/rails/generators/js/assets/templates/javascript.js b/railties/lib/rails/generators/js/assets/templates/javascript.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/railties/lib/rails/generators/js/assets/templates/javascript.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/railties/lib/rails/generators/migration.rb b/railties/lib/rails/generators/migration.rb new file mode 100644 index 0000000000..87f2e1d42b --- /dev/null +++ b/railties/lib/rails/generators/migration.rb @@ -0,0 +1,69 @@ +require 'active_support/concern' +require 'rails/generators/actions/create_migration' + +module Rails + module Generators + # Holds common methods for migrations. It assumes that migrations have the + # [0-9]*_name format and can be used by other frameworks (like Sequel) + # just by implementing the next migration version method. + module Migration + extend ActiveSupport::Concern + attr_reader :migration_number, :migration_file_name, :migration_class_name + + module ClassMethods #:nodoc: + def migration_lookup_at(dirname) + Dir.glob("#{dirname}/[0-9]*_*.rb") + end + + def migration_exists?(dirname, file_name) + migration_lookup_at(dirname).grep(/\d+_#{file_name}.rb$/).first + end + + def current_migration_number(dirname) + migration_lookup_at(dirname).collect do |file| + File.basename(file).split("_").first.to_i + end.max.to_i + end + + def next_migration_number(dirname) + raise NotImplementedError + end + end + + def create_migration(destination, data, config = {}, &block) + action Rails::Generators::Actions::CreateMigration.new(self, destination, block || data.to_s, config) + end + + def set_migration_assigns!(destination) + destination = File.expand_path(destination, self.destination_root) + + migration_dir = File.dirname(destination) + @migration_number = self.class.next_migration_number(migration_dir) + @migration_file_name = File.basename(destination, '.rb') + @migration_class_name = @migration_file_name.camelize + end + + # Creates a migration template at the given destination. The difference + # to the default template method is that the migration version is appended + # to the destination file name. + # + # The migration version, migration file name, migration class name are + # available as instance variables in the template to be rendered. + # + # migration_template "migration.rb", "db/migrate/add_foo_to_bar.rb" + def migration_template(source, destination, config = {}) + source = File.expand_path(find_in_source_paths(source.to_s)) + + set_migration_assigns!(destination) + context = instance_eval('binding') + + dir, base = File.split(destination) + numbered_destination = File.join(dir, ["%migration_number%", base].join('_')) + + create_migration numbered_destination, nil, config do + ERB.new(::File.binread(source), nil, '-', '@output_buffer').result(context) + end + end + end + end +end diff --git a/railties/lib/rails/generators/model_helpers.rb b/railties/lib/rails/generators/model_helpers.rb new file mode 100644 index 0000000000..42c646543e --- /dev/null +++ b/railties/lib/rails/generators/model_helpers.rb @@ -0,0 +1,28 @@ +require 'rails/generators/active_model' + +module Rails + module Generators + module ModelHelpers # :nodoc: + PLURAL_MODEL_NAME_WARN_MESSAGE = "[WARNING] The model name '%s' was recognized as a plural, using the singular '%s' instead. " \ + "Override with --force-plural or setup custom inflection rules for this noun before running the generator." + mattr_accessor :skip_warn + + def self.included(base) #:nodoc: + base.class_option :force_plural, type: :boolean, default: false, desc: 'Forces the use of the given model name' + end + + def initialize(args, *_options) + super + if name == name.pluralize && name.singularize != name.pluralize && !options[:force_plural] + singular = name.singularize + unless ModelHelpers.skip_warn + say PLURAL_MODEL_NAME_WARN_MESSAGE % [name, singular] + ModelHelpers.skip_warn = true + end + name.replace singular + assign_names!(name) + end + end + end + end +end diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb new file mode 100644 index 0000000000..243694f38e --- /dev/null +++ b/railties/lib/rails/generators/named_base.rb @@ -0,0 +1,213 @@ +require 'active_support/core_ext/module/introspection' +require 'rails/generators/base' +require 'rails/generators/generated_attribute' + +module Rails + module Generators + class NamedBase < Base + argument :name, type: :string + class_option :skip_namespace, type: :boolean, default: false, + desc: "Skip namespace (affects only isolated applications)" + + def initialize(args, *options) #:nodoc: + @inside_template = nil + # Unfreeze name in case it's given as a frozen string + args[0] = args[0].dup if args[0].is_a?(String) && args[0].frozen? + super + assign_names!(self.name) + parse_attributes! if respond_to?(:attributes) + end + + # 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 + super + end + end + end + + protected + attr_reader :file_name + + # FIXME: We are avoiding to use alias because a bug on thor that make + # this method public and add it to the task list. + def singular_name + file_name + end + + # Wrap block with namespace of current application + # if namespace exists and is not skipped + def module_namespacing(&block) + content = capture(&block) + content = wrap_with_namespace(content) if namespaced? + concat(content) + end + + def indent(content, multiplier = 2) + spaces = " " * multiplier + content.each_line.map {|line| line.blank? ? line : "#{spaces}#{line}" }.join + end + + def wrap_with_namespace(content) + content = indent(content).chomp + "module #{namespace.name}\n#{content}\nend\n" + end + + def inside_template + @inside_template = true + yield + ensure + @inside_template = false + end + + def inside_template? + @inside_template + end + + def namespace + Rails::Generators.namespace + end + + def namespaced? + !options[:skip_namespace] && namespace + end + + def file_path + @file_path ||= (class_path + [file_name]).join('/') + end + + def class_path + inside_template? || !namespaced? ? regular_class_path : namespaced_class_path + end + + def regular_class_path + @class_path + end + + def namespaced_file_path + @namespaced_file_path ||= namespaced_class_path.join("/") + end + + def namespaced_class_path + @namespaced_class_path ||= [namespaced_path] + @class_path + end + + def namespaced_path + @namespaced_path ||= namespace.name.split("::").first.underscore + end + + def class_name + (class_path + [file_name]).map!(&:camelize).join('::') + end + + def human_name + @human_name ||= singular_name.humanize + end + + def plural_name + @plural_name ||= singular_name.pluralize + end + + def i18n_scope + @i18n_scope ||= file_path.tr('/', '.') + end + + def table_name + @table_name ||= begin + base = pluralize_table_names? ? plural_name : singular_name + (class_path + [base]).join('_') + end + end + + def uncountable? + singular_name == plural_name + end + + def index_helper + uncountable? ? "#{plural_table_name}_index" : plural_table_name + end + + def singular_table_name + @singular_table_name ||= (pluralize_table_names? ? table_name.singularize : table_name) + end + + def plural_table_name + @plural_table_name ||= (pluralize_table_names? ? table_name : table_name.pluralize) + end + + def plural_file_name + @plural_file_name ||= file_name.pluralize + end + + def fixture_file_name + @fixture_file_name ||= (pluralize_table_names? ? plural_file_name : file_name) + end + + def route_url + @route_url ||= class_path.collect {|dname| "/" + dname }.join + "/" + plural_file_name + end + + # Tries to retrieve the application name or simply return application. + def application_name + if defined?(Rails) && Rails.application + Rails.application.class.name.split('::').first.underscore + else + "application" + end + end + + def assign_names!(name) #:nodoc: + @class_path = name.include?('/') ? name.split('/') : name.split('::') + @class_path.map!(&:underscore) + @file_name = @class_path.pop + end + + # Convert attributes array into GeneratedAttribute objects. + def parse_attributes! #:nodoc: + self.attributes = (attributes || []).map do |attr| + Rails::Generators::GeneratedAttribute.parse(attr) + end + end + + def attributes_names + @attributes_names ||= attributes.each_with_object([]) do |a, names| + names << a.column_name + names << 'password_confirmation' if a.password_digest? + names << "#{a.name}_type" if a.polymorphic? + end + end + + def pluralize_table_names? + !defined?(ActiveRecord::Base) || ActiveRecord::Base.pluralize_table_names + end + + def mountable_engine? + defined?(ENGINE_ROOT) && namespaced? + end + + # Add a class collisions name to be checked on class initialization. You + # can supply a hash with a :prefix or :suffix to be tested. + # + # ==== Examples + # + # check_class_collision suffix: "Decorator" + # + # If the generator is invoked with class name Admin, it will check for + # the presence of "AdminDecorator". + # + def self.check_class_collision(options={}) + define_method :check_class_collision do + name = if self.respond_to?(:controller_class_name) # for ScaffoldBase + controller_class_name + else + class_name + end + + class_collisions "#{options[:prefix]}#{name}#{options[:suffix]}" + end + end + end + end +end diff --git a/railties/lib/rails/generators/rails/app/USAGE b/railties/lib/rails/generators/rails/app/USAGE new file mode 100644 index 0000000000..691095f33f --- /dev/null +++ b/railties/lib/rails/generators/rails/app/USAGE @@ -0,0 +1,15 @@ +Description: + The 'rails new' command creates a new Rails application with a default + directory structure and configuration at the path you specify. + + You can specify extra command-line arguments to be used every time + 'rails new' runs in the .railsrc configuration file in your home directory. + + Note that the arguments specified in the .railsrc file don't affect the + defaults values shown above in this help message. + +Example: + rails new ~/Code/Ruby/weblog + + This generates a skeletal Rails installation in ~/Code/Ruby/weblog. + See the README in the newly created application to get going. diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb new file mode 100644 index 0000000000..889154494d --- /dev/null +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -0,0 +1,462 @@ +require 'rails/generators/app_base' + +module Rails + module ActionMethods # :nodoc: + attr_reader :options + + def initialize(generator) + @generator = generator + @options = generator.options + end + + private + %w(template copy_file directory empty_directory inside + empty_directory_with_keep_file create_file chmod shebang).each do |method| + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{method}(*args, &block) + @generator.send(:#{method}, *args, &block) + end + RUBY + end + + # TODO: Remove once this is fully in place + def method_missing(meth, *args, &block) + @generator.send(meth, *args, &block) + end + end + + # The application builder allows you to override elements of the application + # generator without being forced to reverse the operations of the default + # generator. + # + # This allows you to override entire operations, like the creation of the + # Gemfile, README, or JavaScript files, without needing to know exactly + # what those operations do so you can create another template action. + class AppBuilder + def rakefile + template "Rakefile" + end + + def readme + copy_file "README.md", "README.md" + end + + def gemfile + template "Gemfile" + end + + def configru + template "config.ru" + end + + def gitignore + template "gitignore", ".gitignore" + end + + def app + directory 'app' + + keep_file 'app/assets/images' + keep_file 'app/mailers' + keep_file 'app/models' + + keep_file 'app/controllers/concerns' + keep_file 'app/models/concerns' + end + + def bin + directory "bin" do |content| + "#{shebang}\n" + content + end + chmod "bin", 0755 & ~File.umask, verbose: false + end + + def config + empty_directory "config" + + inside "config" do + template "routes.rb" + template "application.rb" + template "environment.rb" + template "secrets.yml" + + directory "environments" + directory "initializers" + directory "locales" + end + end + + def config_when_updating + cookie_serializer_config_exist = File.exist?('config/initializers/cookies_serializer.rb') + callback_terminator_config_exist = File.exist?('config/initializers/callback_terminator.rb') + active_record_belongs_to_required_by_default_config_exist = File.exist?('config/initializers/active_record_belongs_to_required_by_default.rb') + + config + + unless callback_terminator_config_exist + remove_file 'config/initializers/callback_terminator.rb' + end + + unless cookie_serializer_config_exist + gsub_file 'config/initializers/cookies_serializer.rb', /json/, 'marshal' + end + + unless active_record_belongs_to_required_by_default_config_exist + remove_file 'config/initializers/active_record_belongs_to_required_by_default.rb' + end + end + + def database_yml + template "config/databases/#{options[:database]}.yml", "config/database.yml" + end + + def db + directory "db" + end + + def lib + empty_directory 'lib' + empty_directory_with_keep_file 'lib/tasks' + empty_directory_with_keep_file 'lib/assets' + end + + def log + empty_directory_with_keep_file 'log' + end + + def public_directory + directory "public", "public", recursive: false + end + + def test + empty_directory_with_keep_file 'test/fixtures' + empty_directory_with_keep_file 'test/fixtures/files' + empty_directory_with_keep_file 'test/controllers' + empty_directory_with_keep_file 'test/mailers' + empty_directory_with_keep_file 'test/models' + empty_directory_with_keep_file 'test/helpers' + empty_directory_with_keep_file 'test/integration' + + template 'test/test_helper.rb' + end + + def tmp + empty_directory_with_keep_file "tmp" + empty_directory "tmp/cache" + empty_directory "tmp/cache/assets" + end + + def vendor + vendor_javascripts + vendor_stylesheets + end + + def vendor_javascripts + unless options[:skip_javascript] + empty_directory_with_keep_file 'vendor/assets/javascripts' + end + end + + def vendor_stylesheets + empty_directory_with_keep_file 'vendor/assets/stylesheets' + end + end + + module Generators + # We need to store the RAILS_DEV_PATH in a constant, otherwise the path + # can change in Ruby 1.8.7 when we FileUtils.cd. + RAILS_DEV_PATH = File.expand_path("../../../../../..", File.dirname(__FILE__)) + RESERVED_NAMES = %w[application destroy plugin runner test] + + class AppGenerator < AppBase # :nodoc: + add_shared_options_for "application" + + # Add bin/rails options + 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 + + unless app_path + raise Error, "Application name should be provided in arguments. For details run: rails --help" + end + + 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! + public_task :create_root + + def create_root_files + build(:readme) + build(:rakefile) + build(:configru) + build(:gitignore) unless options[:skip_git] + build(:gemfile) unless options[:skip_gemfile] + end + + def create_app_files + build(:app) + end + + def create_bin_files + build(:bin) + end + + def create_config_files + build(:config) + end + + def update_config_files + build(:config_when_updating) + end + remove_task :update_config_files + + def create_boot_file + template "config/boot.rb" + end + + def create_active_record_files + return if options[:skip_active_record] + build(:database_yml) + end + + def create_db_files + build(:db) + end + + def create_lib_files + build(:lib) + end + + def create_log_files + build(:log) + end + + def create_public_files + build(:public_directory) + end + + def create_test_files + build(:test) unless options[:skip_test] + end + + def create_tmp_files + build(:tmp) + end + + def create_vendor_files + 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' + end + end + + def delete_assets_initializer_skipping_sprockets + if options[:skip_sprockets] + remove_file 'config/initializers/assets.rb' + end + end + + def delete_active_record_initializers_skipping_active_record + if options[:skip_active_record] + remove_file 'config/initializers/active_record_belongs_to_required_by_default.rb' + end + end + + def delete_non_api_initializers_if_api_option + if options[:api] + remove_file 'config/initializers/session_store.rb' + remove_file 'config/initializers/cookies_serializer.rb' + remove_file 'config/initializers/request_forgery_protection.rb' + end + end + + def finish_template + build(:leftovers) + end + + public_task :apply_rails_template, :run_bundle + public_task :generate_spring_binstubs + + def run_after_bundle_callbacks + @after_bundle_callbacks.each(&:call) + end + + protected + + def self.banner + "rails new #{self.arguments.map(&:usage).join(' ')} [options]" + end + + # Define file as an alias to create_file for backwards compatibility. + def file(*args, &block) + create_file(*args, &block) + end + + def app_name + @app_name ||= (defined_app_const_base? ? defined_app_name : File.basename(destination_root)).tr('\\', '').tr(". ", "_") + end + + def defined_app_name + defined_app_const_base.underscore + end + + def defined_app_const_base + Rails.respond_to?(:application) && defined?(Rails::Application) && + Rails.application.is_a?(Rails::Application) && Rails.application.class.name.sub(/::Application$/, "") + end + + alias :defined_app_const_base? :defined_app_const_base + + def app_const_base + @app_const_base ||= defined_app_const_base || app_name.gsub(/\W/, '_').squeeze('_').camelize + end + alias :camelized :app_const_base + + def app_const + @app_const ||= "#{app_const_base}::Application" + end + + def valid_const? + if app_const =~ /^\d/ + raise Error, "Invalid application name #{app_name}. Please give a name which does not start with numbers." + elsif RESERVED_NAMES.include?(app_name) + raise Error, "Invalid application name #{app_name}. Please give a " \ + "name which does not match one of the reserved rails " \ + "words: #{RESERVED_NAMES.join(", ")}" + elsif Object.const_defined?(app_const_base) + raise Error, "Invalid application name #{app_name}, constant #{app_const_base} is already in use. Please choose another application name." + end + end + + def app_secret + SecureRandom.hex(64) + end + + def mysql_socket + @mysql_socket ||= [ + "/tmp/mysql.sock", # default + "/var/run/mysqld/mysqld.sock", # debian/gentoo + "/var/tmp/mysql.sock", # freebsd + "/var/lib/mysql/mysql.sock", # fedora + "/opt/local/lib/mysql/mysql.sock", # fedora + "/opt/local/var/run/mysqld/mysqld.sock", # mac + darwinports + mysql + "/opt/local/var/run/mysql4/mysqld.sock", # mac + darwinports + mysql4 + "/opt/local/var/run/mysql5/mysqld.sock", # mac + darwinports + mysql5 + "/opt/lampp/var/mysql/mysql.sock" # xampp for linux + ].find { |f| File.exist?(f) } unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ + end + + def get_builder_class + defined?(::AppBuilder) ? ::AppBuilder : Rails::AppBuilder + end + end + + # This class handles preparation of the arguments before the AppGenerator is + # called. The class provides version or help information if they were + # requested, and also constructs the railsrc file (used for extra configuration + # options). + # + # This class should be called before the AppGenerator is required and started + # since it configures and mutates ARGV correctly. + class ARGVScrubber # :nodoc: + def initialize(argv = ARGV) + @argv = argv + end + + def prepare! + handle_version_request!(@argv.first) + handle_invalid_command!(@argv.first, @argv) do + handle_rails_rc!(@argv.drop(1)) + end + end + + def self.default_rc_file + File.expand_path('~/.railsrc') + end + + private + + def handle_version_request!(argument) + if ['--version', '-v'].include?(argument) + require 'rails/version' + puts "Rails #{Rails::VERSION::STRING}" + exit(0) + end + end + + def handle_invalid_command!(argument, argv) + if argument == "new" + yield + else + ['--help'] + argv.drop(1) + end + end + + def handle_rails_rc!(argv) + if argv.find { |arg| arg == '--no-rc' } + argv.reject { |arg| arg == '--no-rc' } + else + railsrc(argv) { |rc_argv, rc| insert_railsrc_into_argv!(rc_argv, rc) } + end + end + + def railsrc(argv) + if (customrc = argv.index{ |x| x.include?("--rc=") }) + fname = File.expand_path(argv[customrc].gsub(/--rc=/, "")) + yield(argv.take(customrc) + argv.drop(customrc + 1), fname) + else + yield argv, self.class.default_rc_file + end + end + + def read_rc_file(railsrc) + extra_args = File.readlines(railsrc).flat_map(&:split) + puts "Using #{extra_args.join(" ")} from #{railsrc}" + extra_args + end + + def insert_railsrc_into_argv!(argv, railsrc) + return argv unless File.exist?(railsrc) + extra_args = read_rc_file railsrc + argv.take(1) + extra_args + argv.drop(1) + end + end + end +end diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile new file mode 100644 index 0000000000..2f7ffc055d --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -0,0 +1,55 @@ +source 'https://rubygems.org' + +<% gemfile_entries.each do |gem| -%> +<% if gem.comment -%> + +# <%= gem.comment %> +<% end -%> +<%= gem.commented_out ? '# ' : '' %>gem '<%= gem.name %>'<%= %(, '#{gem.version}') if gem.version -%> +<% if gem.options.any? -%> +, <%= gem.options.map { |k,v| + "#{k}: #{v.inspect}" }.join(', ') %> +<% end -%> +<% end -%> + +# Use ActiveModel has_secure_password +# gem 'bcrypt', '~> 3.1.7' + +# Use Unicorn as the app server +# gem 'unicorn' + +# 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.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 +<%- unless options.api? -%> + # 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' + <%- else -%> + gem 'web-console', '~> 3.0' + <%- end -%> +<%- end -%> +<% if spring_install? -%> + # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring + gem 'spring' +<% end -%> +end +<% end -%> + +# Windows does not include zoneinfo files, so bundle the tzinfo-data gem +gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] diff --git a/railties/lib/rails/generators/rails/app/templates/README.md b/railties/lib/rails/generators/rails/app/templates/README.md new file mode 100644 index 0000000000..55e144da18 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/README.md @@ -0,0 +1,24 @@ +## README + +This README would normally document whatever steps are necessary to get the +application up and running. + +Things you may want to cover: + +* Ruby version + +* System dependencies + +* Configuration + +* Database creation + +* Database initialization + +* How to run the test suite + +* Services (job queues, cache servers, search engines, etc.) + +* Deployment instructions + +* ... diff --git a/railties/lib/rails/generators/rails/app/templates/Rakefile b/railties/lib/rails/generators/rails/app/templates/Rakefile new file mode 100644 index 0000000000..ba6b733dd2 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require File.expand_path('../config/application', __FILE__) + +Rails.application.load_tasks diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/config/manifest.js.tt b/railties/lib/rails/generators/rails/app/templates/app/assets/config/manifest.js.tt new file mode 100644 index 0000000000..f80631bac6 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/app/assets/config/manifest.js.tt @@ -0,0 +1,8 @@ + +<% unless options.api? -%> +//= link_tree ../images +<% end -%> +<% unless options.skip_javascript -%> +//= link_directory ../javascripts .js +<% end -%> +//= link_directory ../stylesheets .css diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt new file mode 100644 index 0000000000..c88426ec06 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt @@ -0,0 +1,20 @@ +// This is a manifest file that'll be compiled into application.js, which will include all the files +// listed below. +// +// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, +// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. +// +// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the +// compiled file. JavaScript code in this file should be added after the last require_* statement. +// +// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details +// about supported directives. +// +<% unless options[:skip_javascript] -%> +//= require <%= options[:javascript] %> +//= require <%= options[:javascript] %>_ujs +<% if gemfile_entries.any? { |m| m.name == "turbolinks" } -%> +//= require turbolinks +<% end -%> +<% end -%> +//= require_tree . diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css new file mode 100644 index 0000000000..0ebd7fe829 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css @@ -0,0 +1,15 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, + * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS + * files in this directory. Styles in this file should be added after the last require_* statement. + * It is generally better to create a new file per style scope. + * + *= require_tree . + *= require_self + */ 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 new file mode 100644 index 0000000000..f726fd6305 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt @@ -0,0 +1,7 @@ +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/app/helpers/application_helper.rb b/railties/lib/rails/generators/rails/app/templates/app/helpers/application_helper.rb new file mode 100644 index 0000000000..de6be7945c --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/app/helpers/application_helper.rb @@ -0,0 +1,2 @@ +module ApplicationHelper +end diff --git a/railties/lib/rails/generators/rails/app/templates/app/jobs/application_job.rb b/railties/lib/rails/generators/rails/app/templates/app/jobs/application_job.rb new file mode 100644 index 0000000000..a009ace51c --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/app/jobs/application_job.rb @@ -0,0 +1,2 @@ +class ApplicationJob < ActiveJob::Base +end diff --git a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt new file mode 100644 index 0000000000..8bb4440f55 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<html> + <head> + <title><%= camelized %></title> + <%- if options[:skip_javascript] -%> + <%%= stylesheet_link_tag 'application', media: 'all' %> + <%- else -%> + <%- if gemfile_entries.any? { |m| m.name == 'turbolinks' } -%> + <%%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> + <%%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> + <%- else -%> + <%%= stylesheet_link_tag 'application', media: 'all' %> + <%%= javascript_include_tag 'application' %> + <%- end -%> + <%- end -%> + <%%= csrf_meta_tags %> + </head> + + <body> + + <%%= yield %> + + </body> +</html> diff --git a/railties/lib/rails/generators/rails/app/templates/bin/bundle b/railties/lib/rails/generators/rails/app/templates/bin/bundle new file mode 100644 index 0000000000..1123dcf501 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/bin/bundle @@ -0,0 +1,2 @@ +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) +load Gem.bin_path('bundler', 'bundle') diff --git a/railties/lib/rails/generators/rails/app/templates/bin/rails b/railties/lib/rails/generators/rails/app/templates/bin/rails new file mode 100644 index 0000000000..80ec8080ab --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/bin/rails @@ -0,0 +1,3 @@ +APP_PATH = File.expand_path('../../config/application', __FILE__) +require_relative '../config/boot' +require 'rails/commands' diff --git a/railties/lib/rails/generators/rails/app/templates/bin/rake b/railties/lib/rails/generators/rails/app/templates/bin/rake new file mode 100644 index 0000000000..d14fc8395b --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/bin/rake @@ -0,0 +1,3 @@ +require_relative '../config/boot' +require 'rake' +Rake.application.run diff --git a/railties/lib/rails/generators/rails/app/templates/bin/setup b/railties/lib/rails/generators/rails/app/templates/bin/setup new file mode 100644 index 0000000000..0c8b179827 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/bin/setup @@ -0,0 +1,33 @@ +require 'pathname' +require 'fileutils' +include FileUtils + +# path to your application root. +APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) + +def system!(*args) + system(*args) || abort("\n== Command #{args} failed ==") +end + +chdir APP_ROOT do + # This script is a starting point to setup your application. + # Add necessary setup steps to this file. + + puts '== Installing dependencies ==' + system! 'gem install bundler --conservative' + system('bundle check') or system!('bundle install') + + # puts "\n== Copying sample files ==" + # unless File.exist?('config/database.yml') + # cp 'config/database.yml.sample', 'config/database.yml' + # end + + puts "\n== Preparing database ==" + system! 'ruby bin/rake db:setup' + + puts "\n== Removing old logs and tempfiles ==" + system! 'ruby bin/rake log:clear tmp:clear' + + puts "\n== Restarting application server ==" + system! 'ruby bin/rake restart' +end diff --git a/railties/lib/rails/generators/rails/app/templates/bin/update b/railties/lib/rails/generators/rails/app/templates/bin/update new file mode 100644 index 0000000000..9830e6b29a --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/bin/update @@ -0,0 +1,28 @@ +require 'pathname' +require 'fileutils' +include FileUtils + +# path to your application root. +APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) + +def system!(*args) + system(*args) || abort("\n== Command #{args} failed ==") +end + +chdir APP_ROOT do + # This script is a way to update your development environment automatically. + # Add necessary update steps to this file. + + puts '== Installing dependencies ==' + system! 'gem install bundler --conservative' + system 'bundle check' or system! 'bundle install' + + puts "\n== Updating database ==" + system! 'bin/rake db:migrate' + + puts "\n== Removing old logs and tempfiles ==" + system! 'bin/rake log:clear tmp:clear' + + puts "\n== Restarting application server ==" + system! 'bin/rake restart' +end diff --git a/railties/lib/rails/generators/rails/app/templates/config.ru b/railties/lib/rails/generators/rails/app/templates/config.ru new file mode 100644 index 0000000000..bd83b25412 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config.ru @@ -0,0 +1,4 @@ +# This file is used by Rack-based servers to start the application. + +require ::File.expand_path('../config/environment', __FILE__) +run Rails.application diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb new file mode 100644 index 0000000000..ddd0fcade1 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -0,0 +1,43 @@ +require File.expand_path('../boot', __FILE__) + +<% if include_all_railties? -%> +require 'rails/all' +<% else -%> +require "rails" +# Pick the frameworks you want: +require "active_model/railtie" +require "active_job/railtie" +<%= comment_if :skip_active_record %>require "active_record/railtie" +require "action_controller/railtie" +<%= comment_if :skip_action_mailer %>require "action_mailer/railtie" +require "action_view/railtie" +<%= comment_if :skip_sprockets %>require "sprockets/railtie" +<%= comment_if :skip_test %>require "rails/test_unit/railtie" +<% end -%> + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module <%= app_const_base %> + class Application < Rails::Application + # Settings in config/environments/* take precedence over those specified here. + # Application configuration should go into files in config/initializers + # -- all .rb files in that directory are automatically loaded. + + # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. + # Run "rake time:zones:all" for a time zone names list. Default is UTC. + # config.time_zone = 'Central Time (US & Canada)' + + # 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/boot.rb b/railties/lib/rails/generators/rails/app/templates/config/boot.rb new file mode 100644 index 0000000000..6b750f00b1 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/boot.rb @@ -0,0 +1,3 @@ +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) + +require 'bundler/setup' # Set up gems listed in the Gemfile. diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml new file mode 100644 index 0000000000..34fc0e3465 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml @@ -0,0 +1,49 @@ +# FrontBase versions 4.x +# +# Get the bindings: +# gem install ruby-frontbase +# +# Configure Using Gemfile +# gem 'ruby-frontbase' +# +default: &default + adapter: frontbase + host: localhost + username: <%= app_name %> + password: '' + +development: + <<: *default + database: <%= app_name %>_development + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: <%= app_name %>_test + +# As with config/secrets.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. +# +# Instead, provide the password as a unix environment variable when you boot +# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full rundown on how to provide these environment variables in a +# production deployment. +# +# On Heroku and other platform providers, you may have a full connection URL +# available as an environment variable. For example: +# +# DATABASE_URL="frontbase://myuser:mypass@localhost/somedatabase" +# +# You can use this database configuration with: +# +# production: +# url: <%%= ENV['DATABASE_URL'] %> +# +production: + <<: *default + database: <%= app_name %>_production + username: <%= app_name %> + password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml new file mode 100644 index 0000000000..187ff01bac --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml @@ -0,0 +1,85 @@ +# IBM Dataservers +# +# Home Page +# https://github.com/dparnell/ibm_db +# +# To install the ibm_db gem: +# +# On Linux: +# . /home/db2inst1/sqllib/db2profile +# export IBM_DB_INCLUDE=/opt/ibm/db2/V9.7/include +# export IBM_DB_LIB=/opt/ibm/db2/V9.7/lib32 +# gem install ibm_db +# +# On Mac OS X 10.5: +# . /home/db2inst1/sqllib/db2profile +# export IBM_DB_INCLUDE=/opt/ibm/db2/V9.7/include +# export IBM_DB_LIB=/opt/ibm/db2/V9.7/lib32 +# export ARCHFLAGS="-arch i386" +# gem install ibm_db +# +# On Mac OS X 10.6: +# . /home/db2inst1/sqllib/db2profile +# export IBM_DB_INCLUDE=/opt/ibm/db2/V9.7/include +# export IBM_DB_LIB=/opt/ibm/db2/V9.7/lib64 +# export ARCHFLAGS="-arch x86_64" +# gem install ibm_db +# +# On Windows: +# Issue the command: gem install ibm_db +# +# Configure Using Gemfile +# gem 'ibm_db' +# +# +default: &default + adapter: ibm_db + username: db2inst1 + password: + #schema: db2inst1 + #host: localhost + #port: 50000 + #account: my_account + #app_user: my_app_user + #application: my_application + #workstation: my_workstation + #security: SSL + #timeout: 10 + #authentication: SERVER + #parameterized: false + +development: + <<: *default + database: <%= app_name[0,4] %>_dev + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: <%= app_name[0,4] %>_tst + +# As with config/secrets.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. +# +# Instead, provide the password as a unix environment variable when you boot +# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full rundown on how to provide these environment variables in a +# production deployment. +# +# On Heroku and other platform providers, you may have a full connection URL +# available as an environment variable. For example: +# +# DATABASE_URL="ibm-db://myuser:mypass@localhost/somedatabase" +# +# You can use this database configuration with: +# +# production: +# url: <%%= ENV['DATABASE_URL'] %> +# +production: + <<: *default + database: <%= app_name %>_production + username: <%= app_name %> + password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml new file mode 100644 index 0000000000..db0a429753 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml @@ -0,0 +1,68 @@ +# If you are using mssql, derby, hsqldb, or h2 with one of the +# ActiveRecord JDBC adapters, install the appropriate driver, e.g.,: +# gem install activerecord-jdbcmssql-adapter +# +# Configure using Gemfile: +# gem 'activerecord-jdbcmssql-adapter' +# +# development: +# adapter: mssql +# username: <%= app_name %> +# password: +# host: localhost +# database: <%= app_name %>_development +# +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +# +# test: +# adapter: mssql +# username: <%= app_name %> +# password: +# host: localhost +# database: <%= app_name %>_test +# +# production: +# adapter: mssql +# username: <%= app_name %> +# password: +# host: localhost +# database: <%= app_name %>_production + +# If you are using oracle, db2, sybase, informix or prefer to use the plain +# JDBC adapter, configure your database setting as the example below (requires +# you to download and manually install the database vendor's JDBC driver .jar +# file). See your driver documentation for the appropriate driver class and +# connection string: + +default: &default + adapter: jdbc + username: <%= app_name %> + password: + driver: + +development: + <<: *default + url: jdbc:db://localhost/<%= app_name %>_development + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + url: jdbc:db://localhost/<%= app_name %>_test + +# As with config/secrets.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. +# +# Instead, provide the password as a unix environment variable when you boot +# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full rundown on how to provide these environment variables in a +# production deployment. +# +production: + url: jdbc:db://localhost/<%= app_name %>_production + username: <%= app_name %> + password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml new file mode 100644 index 0000000000..5ca549a8c8 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml @@ -0,0 +1,52 @@ +# MySQL. Versions 4.1 and 5.0 are recommended. +# +# Install the MySQL driver: +# gem install activerecord-jdbcmysql-adapter +# +# Configure Using Gemfile +# gem 'activerecord-jdbcmysql-adapter' +# +# And be sure to use new-style password hashing: +# http://dev.mysql.com/doc/refman/5.7/en/old-client.html +# +default: &default + adapter: mysql + username: root + password: + host: localhost + +development: + <<: *default + database: <%= app_name %>_development + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: <%= app_name %>_test + +# As with config/secrets.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. +# +# Instead, provide the password as a unix environment variable when you boot +# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full rundown on how to provide these environment variables in a +# production deployment. +# +# On Heroku and other platform providers, you may have a full connection URL +# available as an environment variable. For example: +# +# DATABASE_URL="mysql://myuser:mypass@localhost/somedatabase" +# +# You can use this database configuration with: +# +# production: +# url: <%%= ENV['DATABASE_URL'] %> +# +production: + <<: *default + database: <%= app_name %>_production + username: <%= app_name %> + password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml new file mode 100644 index 0000000000..9e99264d33 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml @@ -0,0 +1,68 @@ +# PostgreSQL. Versions 8.2 and up are supported. +# +# Configure Using Gemfile +# gem 'activerecord-jdbcpostgresql-adapter' +# +default: &default + adapter: postgresql + encoding: unicode + +development: + <<: *default + database: <%= app_name %>_development + + # The specified database role being used to connect to postgres. + # To create additional roles in postgres see `$ createuser --help`. + # When left blank, postgres will use the default role. This is + # the same name as the operating system user that initialized the database. + #username: <%= app_name %> + + # The password associated with the postgres role (username). + #password: + + # Connect on a TCP socket. Omitted by default since the client uses a + # domain socket that doesn't need configuration. Windows does not have + # domain sockets, so uncomment these lines. + #host: localhost + #port: 5432 + + # Schema search path. The server defaults to $user,public + #schema_search_path: myapp,sharedapp,public + + # Minimum log levels, in increasing order: + # debug5, debug4, debug3, debug2, debug1, + # log, notice, warning, error, fatal, and panic + # Defaults to warning. + #min_messages: notice + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: <%= app_name %>_test + +# As with config/secrets.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. +# +# Instead, provide the password as a unix environment variable when you boot +# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full rundown on how to provide these environment variables in a +# production deployment. +# +# On Heroku and other platform providers, you may have a full connection URL +# available as an environment variable. For example: +# +# DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" +# +# You can use this database configuration with: +# +# production: +# url: <%%= ENV['DATABASE_URL'] %> +# +production: + <<: *default + database: <%= app_name %>_production + username: <%= app_name %> + password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml new file mode 100644 index 0000000000..28c36eb82f --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml @@ -0,0 +1,23 @@ +# SQLite version 3.x +# gem 'activerecord-jdbcsqlite3-adapter' +# +# Configure Using Gemfile +# gem 'activerecord-jdbcsqlite3-adapter' +# +default: &default + adapter: sqlite3 + +development: + <<: *default + database: db/development.sqlite3 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: db/test.sqlite3 + +production: + <<: *default + database: db/production.sqlite3 diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml new file mode 100644 index 0000000000..119c2fe2c3 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml @@ -0,0 +1,58 @@ +# MySQL. Versions 5.0+ are recommended. +# +# Install the MySQL driver +# gem install mysql2 +# +# Ensure the MySQL gem is defined in your Gemfile +# gem 'mysql2' +# +# And be sure to use new-style password hashing: +# http://dev.mysql.com/doc/refman/5.7/en/old-client.html +# +default: &default + adapter: mysql2 + encoding: utf8 + pool: 5 + username: root + password: +<% if mysql_socket -%> + socket: <%= mysql_socket %> +<% else -%> + host: localhost +<% end -%> + +development: + <<: *default + database: <%= app_name %>_development + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: <%= app_name %>_test + +# As with config/secrets.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. +# +# Instead, provide the password as a unix environment variable when you boot +# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full rundown on how to provide these environment variables in a +# production deployment. +# +# On Heroku and other platform providers, you may have a full connection URL +# available as an environment variable. For example: +# +# DATABASE_URL="mysql2://myuser:mypass@localhost/somedatabase" +# +# You can use this database configuration with: +# +# production: +# url: <%%= ENV['DATABASE_URL'] %> +# +production: + <<: *default + database: <%= app_name %>_production + username: <%= app_name %> + password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml new file mode 100644 index 0000000000..9aedcc15cb --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml @@ -0,0 +1,58 @@ +# Oracle/OCI 8i, 9, 10g +# +# Requires Ruby/OCI8: +# https://github.com/kubo/ruby-oci8 +# +# Specify your database using any valid connection syntax, such as a +# tnsnames.ora service name, or an SQL connect string of the form: +# +# //host:[port][/service name] +# +# By default prefetch_rows (OCI_ATTR_PREFETCH_ROWS) is set to 100. And +# until true bind variables are supported, cursor_sharing is set by default +# to 'similar'. Both can be changed in the configuration below; the defaults +# are equivalent to specifying: +# +# prefetch_rows: 100 +# cursor_sharing: similar +# +default: &default + adapter: oracle + username: <%= app_name %> + password: + +development: + <<: *default + database: <%= app_name %>_development + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: <%= app_name %>_test + +# As with config/secrets.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. +# +# Instead, provide the password as a unix environment variable when you boot +# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full rundown on how to provide these environment variables in a +# production deployment. +# +# On Heroku and other platform providers, you may have a full connection URL +# available as an environment variable. For example: +# +# DATABASE_URL="oracle://myuser:mypass@localhost/somedatabase" +# +# You can use this database configuration with: +# +# production: +# url: <%%= ENV['DATABASE_URL'] %> +# +production: + <<: *default + database: <%= app_name %>_production + username: <%= app_name %> + password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml new file mode 100644 index 0000000000..feb25bbc6b --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml @@ -0,0 +1,85 @@ +# PostgreSQL. Versions 8.2 and up are supported. +# +# Install the pg driver: +# gem install pg +# On OS X with Homebrew: +# gem install pg -- --with-pg-config=/usr/local/bin/pg_config +# On OS X with MacPorts: +# gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config +# On Windows: +# gem install pg +# Choose the win32 build. +# Install PostgreSQL and put its /bin directory on your path. +# +# Configure Using Gemfile +# gem 'pg' +# +default: &default + adapter: postgresql + encoding: unicode + # For details on connection pooling, see rails configuration guide + # http://guides.rubyonrails.org/configuring.html#database-pooling + pool: 5 + +development: + <<: *default + database: <%= app_name %>_development + + # The specified database role being used to connect to postgres. + # To create additional roles in postgres see `$ createuser --help`. + # When left blank, postgres will use the default role. This is + # the same name as the operating system user that initialized the database. + #username: <%= app_name %> + + # The password associated with the postgres role (username). + #password: + + # Connect on a TCP socket. Omitted by default since the client uses a + # domain socket that doesn't need configuration. Windows does not have + # domain sockets, so uncomment these lines. + #host: localhost + + # The TCP port the server listens on. Defaults to 5432. + # If your server runs on a different port number, change accordingly. + #port: 5432 + + # Schema search path. The server defaults to $user,public + #schema_search_path: myapp,sharedapp,public + + # Minimum log levels, in increasing order: + # debug5, debug4, debug3, debug2, debug1, + # log, notice, warning, error, fatal, and panic + # Defaults to warning. + #min_messages: notice + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: <%= app_name %>_test + +# As with config/secrets.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. +# +# Instead, provide the password as a unix environment variable when you boot +# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full rundown on how to provide these environment variables in a +# production deployment. +# +# On Heroku and other platform providers, you may have a full connection URL +# available as an environment variable. For example: +# +# DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" +# +# You can use this database configuration with: +# +# production: +# url: <%%= ENV['DATABASE_URL'] %> +# +production: + <<: *default + database: <%= app_name %>_production + username: <%= app_name %> + password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml new file mode 100644 index 0000000000..1c1a37ca8d --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml @@ -0,0 +1,25 @@ +# SQLite version 3.x +# gem install sqlite3 +# +# Ensure the SQLite 3 gem is defined in your Gemfile +# gem 'sqlite3' +# +default: &default + adapter: sqlite3 + pool: 5 + timeout: 5000 + +development: + <<: *default + database: db/development.sqlite3 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: db/test.sqlite3 + +production: + <<: *default + database: db/production.sqlite3 diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml new file mode 100644 index 0000000000..30b0df34a8 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml @@ -0,0 +1,68 @@ +# SQL Server (2005 or higher recommended) +# +# Install the adapters and driver +# gem install tiny_tds +# gem install activerecord-sqlserver-adapter +# +# Ensure the activerecord adapter and db driver gems are defined in your Gemfile +# gem 'tiny_tds' +# gem 'activerecord-sqlserver-adapter' +# +# You should make sure freetds is configured correctly first. +# freetds.conf contains host/port/protocol_versions settings. +# http://freetds.schemamania.org/userguide/freetdsconf.htm +# +# A typical Microsoft server +# [mssql] +# host = mssqlserver.yourdomain.com +# port = 1433 +# tds version = 7.1 + +# If you can connect with "tsql -S servername", your basic FreeTDS installation is working. +# 'man tsql' for more info +# Set timeout to a larger number if valid queries against a live db fail +# +default: &default + adapter: sqlserver + encoding: utf8 + reconnect: false + username: <%= app_name %> + password: + timeout: 25 + dataserver: from_freetds.conf + +development: + <<: *default + database: <%= app_name %>_development + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: <%= app_name %>_test + +# As with config/secrets.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. +# +# Instead, provide the password as a unix environment variable when you boot +# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full rundown on how to provide these environment variables in a +# production deployment. +# +# On Heroku and other platform providers, you may have a full connection URL +# available as an environment variable. For example: +# +# DATABASE_URL="sqlserver://myuser:mypass@localhost/somedatabase" +# +# You can use this database configuration with: +# +# production: +# url: <%%= ENV['DATABASE_URL'] %> +# +production: + <<: *default + database: <%= app_name %>_production + username: <%= app_name %> + password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/environment.rb b/railties/lib/rails/generators/rails/app/templates/config/environment.rb new file mode 100644 index 0000000000..ee8d90dc65 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require File.expand_path('../application', __FILE__) + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt new file mode 100644 index 0000000000..65c6aeb694 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt @@ -0,0 +1,62 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # In the development environment your application's code is reloaded on + # every request. This slows down response time but is perfect for development + # since you don't have to restart the web server when you make code changes. + config.cache_classes = false + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable/disable caching. By default caching is disabled. + if Rails.root.join('tmp/caching-dev.txt').exist? + config.action_controller.perform_caching = true + config.cache_store = :memory_store + config.public_file_server.headers = { + 'Cache-Control' => 'public, max-age=172800' + } + else + config.action_controller.perform_caching = false + config.cache_store = :null_store + end + <%- unless options.skip_action_mailer? -%> + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + <%- end -%> + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + <%- unless options.skip_active_record? -%> + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + <%- end -%> + <%- unless options.skip_sprockets? -%> + # Debug mode disables concatenation and preprocessing of assets. + # This option may cause significant delays in view rendering with a large + # number of complex assets. + config.assets.debug = true + + # Asset digests allow you to set far-future HTTP expiration dates on all assets, + # yet still be able to expire them through the digest params. + config.assets.digest = true + + # Adds additional error checking when serving assets at runtime. + # Checks for improperly declared sprockets dependencies. + # Raises helpful error messages. + config.assets.raise_runtime_errors = true + <%- end -%> + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true + + # Use an evented file watcher to asynchronously detect changes in source code, + # routes, locales, etc. This feature depends on the listen gem. + # config.file_watcher = ActiveSupport::EventedFileUpdateChecker +end diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt new file mode 100644 index 0000000000..a5302550fa --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt @@ -0,0 +1,84 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.cache_classes = true + + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both threaded web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + + # Full error reports are disabled and caching is turned on. + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Disable serving static files from the `/public` folder by default since + # Apache or NGINX already handles this. + config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? + + <%- unless options.skip_sprockets? -%> + # Compress JavaScripts and CSS. + config.assets.js_compressor = :uglifier + # config.assets.css_compressor = :sass + + # Do not fallback to assets pipeline if a precompiled asset is missed. + config.assets.compile = false + + # Asset digests allow you to set far-future HTTP expiration dates on all assets, + # yet still be able to expire them through the digest params. + config.assets.digest = true + + # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb + <%- end -%> + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.action_controller.asset_host = 'http://assets.example.com' + + # Specifies the header that your server uses for sending files. + # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + # config.force_ssl = true + + # Use the lowest log level to ensure availability of diagnostic information + # when problems arise. + config.log_level = :debug + + # Prepend all log lines with the following tags. + # config.log_tags = [ :subdomain, :request_id ] + + # Use a different logger for distributed setups. + # require 'syslog/logger' + # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') + + # Use a different cache store in production. + # config.cache_store = :mem_cache_store + + # Use a real queuing backend for Active Job (and separate queues per environment) + # config.active_job.queue_adapter = :resque + # config.active_job.queue_name_prefix = "<%= app_name %>_#{Rails.env}" + <%- unless options.skip_action_mailer? -%> + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + <%- end -%> + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Send deprecation notices to registered listeners. + config.active_support.deprecation = :notify + + # Use default logging formatter so that PID and timestamp are not suppressed. + config.log_formatter = ::Logger::Formatter.new + <%- unless options.skip_active_record? -%> + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false + <%- end -%> +end diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt new file mode 100644 index 0000000000..8133917591 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt @@ -0,0 +1,46 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # The test environment is used exclusively to run your application's + # test suite. You never need to work with it otherwise. Remember that + # your test database is "scratch space" for the test suite and is wiped + # and recreated between test runs. Don't rely on the data there! + config.cache_classes = true + + # Do not eager load code on boot. This avoids loading your whole application + # just for the purpose of running a single test. If you are using a tool that + # preloads Rails for running tests, you may have to set it to true. + config.eager_load = false + + # Configure public file server for tests with Cache-Control for performance. + config.public_file_server.enabled = true + config.public_file_server.headers = { + 'Cache-Control' => 'public, max-age=3600' + } + + # Show full error reports and disable caching. + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + + # Raise exceptions instead of rendering exception templates. + config.action_dispatch.show_exceptions = false + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + <%- unless options.skip_action_mailer? -%> + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + <%- end -%> + + # Randomize the order test cases are executed. + config.active_support.test_order = :random + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true +end diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/active_record_belongs_to_required_by_default.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/active_record_belongs_to_required_by_default.rb new file mode 100644 index 0000000000..30c4f89792 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/active_record_belongs_to_required_by_default.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Require `belongs_to` associations by default. +Rails.application.config.active_record.belongs_to_required_by_default = true diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb new file mode 100644 index 0000000000..ea930f54da --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb @@ -0,0 +1,6 @@ +## Change renderer defaults here. +# +# ApplicationController.renderer.defaults.merge!( +# http_host: 'example.org', +# https: false +# ) diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt new file mode 100644 index 0000000000..01ef3e6630 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt @@ -0,0 +1,11 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = '1.0' + +# Add additional assets to the asset load path +# Rails.application.config.assets.paths << Emoji.images_path + +# Precompile additional assets. +# application.js, application.css, and all non-JS/CSS in app/assets folder are already added. +# Rails.application.config.assets.precompile += %w( search.js ) diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/backtrace_silencers.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/backtrace_silencers.rb new file mode 100644 index 0000000000..59385cdf37 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/backtrace_silencers.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. +# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } + +# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. +# Rails.backtrace_cleaner.remove_silencers! diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb new file mode 100644 index 0000000000..a70a1b9cde --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Do not halt callback chains when a callback returns false. +ActiveSupport.halt_callback_chains_on_return_false = false diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb new file mode 100644 index 0000000000..7f70458dee --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb @@ -0,0 +1,3 @@ +# Be sure to restart your server when you modify this file. + +Rails.application.config.action_dispatch.cookies_serializer = :json 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..3b1c1b5ed1 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/cors.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# 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/filter_parameter_logging.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb new file mode 100644 index 0000000000..4a994e1e7b --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Configure sensitive parameters which will be filtered from the log file. +Rails.application.config.filter_parameters += [:password] diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb new file mode 100644 index 0000000000..ac033bf9dc --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym 'RESTful' +# end diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb new file mode 100644 index 0000000000..dc1899682b --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/request_forgery_protection.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/request_forgery_protection.rb new file mode 100644 index 0000000000..3eab78a885 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/request_forgery_protection.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Enable origin-checking CSRF mitigation. +Rails.application.config.action_controller.forgery_protection_origin_check = true diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt new file mode 100644 index 0000000000..2bb9b82c61 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt @@ -0,0 +1,3 @@ +# Be sure to restart your server when you modify this file. + +Rails.application.config.session_store :cookie_store, key: <%= "'_#{app_name}_session'" %> 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 new file mode 100644 index 0000000000..cadc85cfac --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# This file contains settings for ActionController::ParamsWrapper which +# is enabled by default. + +# 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] +end +<%- unless options.skip_active_record? -%> + +# To enable root element in JSON for ActiveRecord objects. +# ActiveSupport.on_load(:active_record) do +# self.include_root_in_json = true +# end +<%- end -%> diff --git a/railties/lib/rails/generators/rails/app/templates/config/locales/en.yml b/railties/lib/rails/generators/rails/app/templates/config/locales/en.yml new file mode 100644 index 0000000000..0653957166 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/locales/en.yml @@ -0,0 +1,23 @@ +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t 'hello' +# +# In views, this is aliased to just `t`: +# +# <%= t('hello') %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# To learn more, please read the Rails Internationalization guide +# available at http://guides.rubyonrails.org/i18n.html. + +en: + hello: "Hello world" diff --git a/railties/lib/rails/generators/rails/app/templates/config/routes.rb b/railties/lib/rails/generators/rails/app/templates/config/routes.rb new file mode 100644 index 0000000000..787824f888 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb @@ -0,0 +1,3 @@ +Rails.application.routes.draw do + # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html +end diff --git a/railties/lib/rails/generators/rails/app/templates/config/secrets.yml b/railties/lib/rails/generators/rails/app/templates/config/secrets.yml new file mode 100644 index 0000000000..b2669a0f79 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/secrets.yml @@ -0,0 +1,22 @@ +# Be sure to restart your server when you modify this file. + +# Your secret key is used for verifying the integrity of signed cookies. +# If you change this key, all old signed cookies will become invalid! + +# Make sure the secret is at least 30 characters and all random, +# no regular words or you'll be exposed to dictionary attacks. +# You can use `rake secret` to generate a secure secret key. + +# Make sure the secrets in this file are kept private +# if you're sharing your code publicly. + +development: + secret_key_base: <%= app_secret %> + +test: + secret_key_base: <%= app_secret %> + +# Do not keep production secrets in the repository, +# instead read values from the environment. +production: + secret_key_base: <%%= ENV["SECRET_KEY_BASE"] %> diff --git a/railties/lib/rails/generators/rails/app/templates/db/seeds.rb.tt b/railties/lib/rails/generators/rails/app/templates/db/seeds.rb.tt new file mode 100644 index 0000000000..4edb1e857e --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/db/seeds.rb.tt @@ -0,0 +1,7 @@ +# This file should contain all the record creation needed to seed the database with its default values. +# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). +# +# Examples: +# +# cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) +# Mayor.create(name: 'Emanuel', city: cities.first) diff --git a/railties/lib/rails/generators/rails/app/templates/gitignore b/railties/lib/rails/generators/rails/app/templates/gitignore new file mode 100644 index 0000000000..1b8cf8a9fa --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/gitignore @@ -0,0 +1,22 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +<% if sqlite3? -%> +# Ignore the default SQLite database. +/db/*.sqlite3 +/db/*.sqlite3-journal + +<% end -%> +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +<% if keeps? -%> +!/log/.keep +!/tmp/.keep +<% end -%> diff --git a/railties/lib/rails/generators/rails/app/templates/public/404.html b/railties/lib/rails/generators/rails/app/templates/public/404.html new file mode 100644 index 0000000000..b612547fc2 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/public/404.html @@ -0,0 +1,67 @@ +<!DOCTYPE html> +<html> +<head> + <title>The page you were looking for doesn't exist (404)</title> + <meta name="viewport" content="width=device-width,initial-scale=1"> + <style> + body { + background-color: #EFEFEF; + color: #2E2F30; + text-align: center; + font-family: arial, sans-serif; + margin: 0; + } + + div.dialog { + width: 95%; + max-width: 33em; + margin: 4em auto 0; + } + + div.dialog > div { + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #BBB; + border-top: #B00100 solid 4px; + border-top-left-radius: 9px; + border-top-right-radius: 9px; + background-color: white; + padding: 7px 12% 0; + box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); + } + + h1 { + font-size: 100%; + color: #730E15; + line-height: 1.5em; + } + + div.dialog > p { + margin: 0 0 1em; + padding: 1em; + background-color: #F7F7F7; + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #999; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-top-color: #DADADA; + color: #666; + box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); + } + </style> +</head> + +<body> + <!-- This file lives in public/404.html --> + <div class="dialog"> + <div> + <h1>The page you were looking for doesn't exist.</h1> + <p>You may have mistyped the address or the page may have moved.</p> + </div> + <p>If you are the application owner check the logs for more information.</p> + </div> +</body> +</html> diff --git a/railties/lib/rails/generators/rails/app/templates/public/422.html b/railties/lib/rails/generators/rails/app/templates/public/422.html new file mode 100644 index 0000000000..a21f82b3bd --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/public/422.html @@ -0,0 +1,67 @@ +<!DOCTYPE html> +<html> +<head> + <title>The change you wanted was rejected (422)</title> + <meta name="viewport" content="width=device-width,initial-scale=1"> + <style> + body { + background-color: #EFEFEF; + color: #2E2F30; + text-align: center; + font-family: arial, sans-serif; + margin: 0; + } + + div.dialog { + width: 95%; + max-width: 33em; + margin: 4em auto 0; + } + + div.dialog > div { + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #BBB; + border-top: #B00100 solid 4px; + border-top-left-radius: 9px; + border-top-right-radius: 9px; + background-color: white; + padding: 7px 12% 0; + box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); + } + + h1 { + font-size: 100%; + color: #730E15; + line-height: 1.5em; + } + + div.dialog > p { + margin: 0 0 1em; + padding: 1em; + background-color: #F7F7F7; + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #999; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-top-color: #DADADA; + color: #666; + box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); + } + </style> +</head> + +<body> + <!-- This file lives in public/422.html --> + <div class="dialog"> + <div> + <h1>The change you wanted was rejected.</h1> + <p>Maybe you tried to change something you didn't have access to.</p> + </div> + <p>If you are the application owner check the logs for more information.</p> + </div> +</body> +</html> diff --git a/railties/lib/rails/generators/rails/app/templates/public/500.html b/railties/lib/rails/generators/rails/app/templates/public/500.html new file mode 100644 index 0000000000..061abc587d --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/public/500.html @@ -0,0 +1,66 @@ +<!DOCTYPE html> +<html> +<head> + <title>We're sorry, but something went wrong (500)</title> + <meta name="viewport" content="width=device-width,initial-scale=1"> + <style> + body { + background-color: #EFEFEF; + color: #2E2F30; + text-align: center; + font-family: arial, sans-serif; + margin: 0; + } + + div.dialog { + width: 95%; + max-width: 33em; + margin: 4em auto 0; + } + + div.dialog > div { + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #BBB; + border-top: #B00100 solid 4px; + border-top-left-radius: 9px; + border-top-right-radius: 9px; + background-color: white; + padding: 7px 12% 0; + box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); + } + + h1 { + font-size: 100%; + color: #730E15; + line-height: 1.5em; + } + + div.dialog > p { + margin: 0 0 1em; + padding: 1em; + background-color: #F7F7F7; + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #999; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-top-color: #DADADA; + color: #666; + box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); + } + </style> +</head> + +<body> + <!-- This file lives in public/500.html --> + <div class="dialog"> + <div> + <h1>We're sorry, but something went wrong.</h1> + </div> + <p>If you are the application owner check the logs for more information.</p> + </div> +</body> +</html> diff --git a/railties/lib/rails/generators/rails/app/templates/public/favicon.ico b/railties/lib/rails/generators/rails/app/templates/public/favicon.ico new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/public/favicon.ico diff --git a/railties/lib/rails/generators/rails/app/templates/public/robots.txt b/railties/lib/rails/generators/rails/app/templates/public/robots.txt new file mode 100644 index 0000000000..3c9c7c01f3 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/public/robots.txt @@ -0,0 +1,5 @@ +# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file +# +# To ban all spiders from the entire site uncomment the next two lines: +# User-agent: * +# Disallow: / diff --git a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb new file mode 100644 index 0000000000..87b8fe3516 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb @@ -0,0 +1,12 @@ +ENV['RAILS_ENV'] ||= 'test' +require File.expand_path('../../config/environment', __FILE__) +require 'rails/test_help' + +class ActiveSupport::TestCase +<% unless options[:skip_active_record] -%> + # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. + fixtures :all + +<% end -%> + # Add more helper methods to be used by all tests here... +end diff --git a/railties/lib/rails/generators/rails/assets/USAGE b/railties/lib/rails/generators/rails/assets/USAGE new file mode 100644 index 0000000000..d2e5ed4482 --- /dev/null +++ b/railties/lib/rails/generators/rails/assets/USAGE @@ -0,0 +1,20 @@ +Description: + Stubs out new asset placeholders. Pass the asset name, either CamelCased + or under_scored. + + To create an asset within a folder, specify the asset's name as a + path like 'parent/name'. + + This generates a JavaScript stub in app/assets/javascripts and a stylesheet + stub in app/assets/stylesheets. + + If CoffeeScript is available, JavaScripts will be generated with the .coffee extension. + If Sass 3 is available, stylesheets will be generated with the .scss extension. + +Example: + `rails generate assets posts` + + Posts assets. + JavaScript: app/assets/javascripts/posts.js + Stylesheet: app/assets/stylesheets/posts.css + diff --git a/railties/lib/rails/generators/rails/assets/assets_generator.rb b/railties/lib/rails/generators/rails/assets/assets_generator.rb new file mode 100644 index 0000000000..6f4b86e708 --- /dev/null +++ b/railties/lib/rails/generators/rails/assets/assets_generator.rb @@ -0,0 +1,25 @@ +module Rails + module Generators + class AssetsGenerator < NamedBase # :nodoc: + class_option :javascripts, type: :boolean, desc: "Generate JavaScripts" + class_option :stylesheets, type: :boolean, desc: "Generate Stylesheets" + + class_option :javascript_engine, desc: "Engine for JavaScripts" + class_option :stylesheet_engine, desc: "Engine for Stylesheets" + + protected + + def asset_name + file_name + end + + hook_for :javascript_engine do |javascript_engine| + invoke javascript_engine, [name] if options[:javascripts] + end + + hook_for :stylesheet_engine do |stylesheet_engine| + invoke stylesheet_engine, [name] if options[:stylesheets] + end + end + end +end diff --git a/railties/lib/rails/generators/rails/assets/templates/javascript.js b/railties/lib/rails/generators/rails/assets/templates/javascript.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/railties/lib/rails/generators/rails/assets/templates/javascript.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/railties/lib/rails/generators/rails/assets/templates/stylesheet.css b/railties/lib/rails/generators/rails/assets/templates/stylesheet.css new file mode 100644 index 0000000000..7594abf268 --- /dev/null +++ b/railties/lib/rails/generators/rails/assets/templates/stylesheet.css @@ -0,0 +1,4 @@ +/* + Place all the styles related to the matching controller here. + They will automatically be included in application.css. +*/ diff --git a/railties/lib/rails/generators/rails/controller/USAGE b/railties/lib/rails/generators/rails/controller/USAGE new file mode 100644 index 0000000000..64239ad599 --- /dev/null +++ b/railties/lib/rails/generators/rails/controller/USAGE @@ -0,0 +1,18 @@ +Description: + Stubs out a new controller and its views. Pass the controller name, either + CamelCased or under_scored, and a list of views as arguments. + + To create a controller within a module, specify the controller name as a + path like 'parent_module/controller_name'. + + This generates a controller class in app/controllers and invokes helper, + template engine, assets, and test framework generators. + +Example: + `rails generate controller CreditCards open debit credit close` + + CreditCards controller with URLs like /credit_cards/debit. + Controller: app/controllers/credit_cards_controller.rb + Test: test/controllers/credit_cards_controller_test.rb + Views: app/views/credit_cards/debit.html.erb [...] + Helper: app/helpers/credit_cards_helper.rb diff --git a/railties/lib/rails/generators/rails/controller/controller_generator.rb b/railties/lib/rails/generators/rails/controller/controller_generator.rb new file mode 100644 index 0000000000..0a4c509a31 --- /dev/null +++ b/railties/lib/rails/generators/rails/controller/controller_generator.rb @@ -0,0 +1,60 @@ +module Rails + module Generators + class ControllerGenerator < NamedBase # :nodoc: + argument :actions, type: :array, default: [], banner: "action action" + class_option :skip_routes, type: :boolean, desc: "Don't add routes to config/routes.rb." + + check_class_collision suffix: "Controller" + + def create_controller_files + template 'controller.rb', File.join('app/controllers', class_path, "#{file_name}_controller.rb") + end + + def add_routes + unless options[:skip_routes] + actions.reverse_each do |action| + # route prepends two spaces onto the front of the string that is passed, this corrects that. + route generate_routing_code(action)[2..-1] + end + end + end + + hook_for :template_engine, :test_framework + hook_for :helper, :assets, hide: true + + private + + # This method creates nested route entry for namespaced resources. + # For eg. rails g controller foo/bar/baz index + # Will generate - + # namespace :foo do + # namespace :bar do + # get 'baz/index' + # end + # end + def generate_routing_code(action) + depth = regular_class_path.length + # Create 'namespace' ladder + # namespace :foo do + # namespace :bar do + namespace_ladder = regular_class_path.each_with_index.map do |ns, i| + indent(" namespace :#{ns} do\n", i * 2) + end.join + + # Create route + # get 'baz/index' + route = indent(%{ get '#{file_name}/#{action}'\n}, depth * 2) + + # Create `end` ladder + # end + # end + end_ladder = (1..depth).reverse_each.map do |i| + indent("end\n", i * 2) + end.join + + # Combine the 3 parts to generate complete route entry + namespace_ladder + route + end_ladder + end + end + end +end diff --git a/railties/lib/rails/generators/rails/controller/templates/controller.rb b/railties/lib/rails/generators/rails/controller/templates/controller.rb new file mode 100644 index 0000000000..633e0b3177 --- /dev/null +++ b/railties/lib/rails/generators/rails/controller/templates/controller.rb @@ -0,0 +1,13 @@ +<% if namespaced? -%> +require_dependency "<%= namespaced_path %>/application_controller" + +<% end -%> +<% module_namespacing do -%> +class <%= class_name %>Controller < ApplicationController +<% actions.each do |action| -%> + def <%= action %> + end +<%= "\n" unless action == actions.last -%> +<% end -%> +end +<% end -%> diff --git a/railties/lib/rails/generators/rails/generator/USAGE b/railties/lib/rails/generators/rails/generator/USAGE new file mode 100644 index 0000000000..799383050c --- /dev/null +++ b/railties/lib/rails/generators/rails/generator/USAGE @@ -0,0 +1,13 @@ +Description: + Stubs out a new generator at lib/generators. Pass the generator name as an argument, + either CamelCased or snake_cased. + +Example: + `rails generate generator Awesome` + + creates a standard awesome generator: + lib/generators/awesome/ + lib/generators/awesome/awesome_generator.rb + lib/generators/awesome/USAGE + lib/generators/awesome/templates/ + test/lib/generators/awesome_generator_test.rb diff --git a/railties/lib/rails/generators/rails/generator/generator_generator.rb b/railties/lib/rails/generators/rails/generator/generator_generator.rb new file mode 100644 index 0000000000..15d88f06ac --- /dev/null +++ b/railties/lib/rails/generators/rails/generator/generator_generator.rb @@ -0,0 +1,27 @@ +module Rails + module Generators + class GeneratorGenerator < NamedBase # :nodoc: + check_class_collision suffix: "Generator" + + class_option :namespace, type: :boolean, default: true, + desc: "Namespace generator under lib/generators/name" + + def create_generator_files + directory '.', generator_dir + end + + hook_for :test_framework + + protected + + def generator_dir + if options[:namespace] + File.join("lib", "generators", regular_class_path, file_name) + else + File.join("lib", "generators", regular_class_path) + end + end + + end + end +end diff --git a/railties/lib/rails/generators/rails/generator/templates/%file_name%_generator.rb.tt b/railties/lib/rails/generators/rails/generator/templates/%file_name%_generator.rb.tt new file mode 100644 index 0000000000..d0575772bc --- /dev/null +++ b/railties/lib/rails/generators/rails/generator/templates/%file_name%_generator.rb.tt @@ -0,0 +1,3 @@ +class <%= class_name %>Generator < Rails::Generators::NamedBase + source_root File.expand_path('../templates', __FILE__) +end diff --git a/railties/lib/rails/generators/rails/generator/templates/USAGE.tt b/railties/lib/rails/generators/rails/generator/templates/USAGE.tt new file mode 100644 index 0000000000..1bb8df840d --- /dev/null +++ b/railties/lib/rails/generators/rails/generator/templates/USAGE.tt @@ -0,0 +1,8 @@ +Description: + Explain the generator + +Example: + rails generate <%= file_name %> Thing + + This will create: + what/will/it/create diff --git a/railties/lib/rails/generators/rails/generator/templates/templates/.empty_directory b/railties/lib/rails/generators/rails/generator/templates/templates/.empty_directory new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/railties/lib/rails/generators/rails/generator/templates/templates/.empty_directory diff --git a/railties/lib/rails/generators/rails/helper/USAGE b/railties/lib/rails/generators/rails/helper/USAGE new file mode 100644 index 0000000000..8855ef3b01 --- /dev/null +++ b/railties/lib/rails/generators/rails/helper/USAGE @@ -0,0 +1,13 @@ +Description: + Stubs out a new helper. Pass the helper name, either CamelCased + or under_scored. + + To create a helper within a module, specify the helper name as a + path like 'parent_module/helper_name'. + +Example: + `rails generate helper CreditCard` + + Credit card helper. + Helper: app/helpers/credit_card_helper.rb + diff --git a/railties/lib/rails/generators/rails/helper/helper_generator.rb b/railties/lib/rails/generators/rails/helper/helper_generator.rb new file mode 100644 index 0000000000..5ff38e4111 --- /dev/null +++ b/railties/lib/rails/generators/rails/helper/helper_generator.rb @@ -0,0 +1,13 @@ +module Rails + module Generators + class HelperGenerator < NamedBase # :nodoc: + check_class_collision suffix: "Helper" + + def create_helper_files + template 'helper.rb', File.join('app/helpers', class_path, "#{file_name}_helper.rb") + end + + hook_for :test_framework + end + end +end diff --git a/railties/lib/rails/generators/rails/helper/templates/helper.rb b/railties/lib/rails/generators/rails/helper/templates/helper.rb new file mode 100644 index 0000000000..b4173151b4 --- /dev/null +++ b/railties/lib/rails/generators/rails/helper/templates/helper.rb @@ -0,0 +1,4 @@ +<% module_namespacing do -%> +module <%= class_name %>Helper +end +<% end -%> diff --git a/railties/lib/rails/generators/rails/integration_test/USAGE b/railties/lib/rails/generators/rails/integration_test/USAGE new file mode 100644 index 0000000000..57ee3543e6 --- /dev/null +++ b/railties/lib/rails/generators/rails/integration_test/USAGE @@ -0,0 +1,10 @@ +Description: + Stubs out a new integration test. Pass the name of the test, either + CamelCased or under_scored, as an argument. + + This generator invokes the current integration tool, which defaults to + TestUnit. + +Example: + `rails generate integration_test GeneralStories` creates a GeneralStories + integration test in test/integration/general_stories_test.rb diff --git a/railties/lib/rails/generators/rails/integration_test/integration_test_generator.rb b/railties/lib/rails/generators/rails/integration_test/integration_test_generator.rb new file mode 100644 index 0000000000..70770ddcb8 --- /dev/null +++ b/railties/lib/rails/generators/rails/integration_test/integration_test_generator.rb @@ -0,0 +1,7 @@ +module Rails + module Generators + class IntegrationTestGenerator < NamedBase # :nodoc: + hook_for :integration_tool, as: :integration + end + end +end diff --git a/railties/lib/rails/generators/rails/migration/USAGE b/railties/lib/rails/generators/rails/migration/USAGE new file mode 100644 index 0000000000..baf3d9894f --- /dev/null +++ b/railties/lib/rails/generators/rails/migration/USAGE @@ -0,0 +1,35 @@ +Description: + Stubs out a new database migration. Pass the migration name, either + CamelCased or under_scored, and an optional list of attribute pairs as arguments. + + A migration class is generated in db/migrate prefixed by a timestamp of the current date and time. + + You can name your migration in either of these formats to generate add/remove + column lines from supplied attributes: AddColumnsToTable or RemoveColumnsFromTable + +Example: + `rails generate migration AddSslFlag` + + If the current date is May 14, 2008 and the current time 09:09:12, this creates the AddSslFlag migration + db/migrate/20080514090912_add_ssl_flag.rb + + `rails generate migration AddTitleBodyToPost title:string body:text published:boolean` + + This will create the AddTitleBodyToPost in db/migrate/20080514090912_add_title_body_to_post.rb with this in the Change migration: + + add_column :posts, :title, :string + add_column :posts, :body, :text + add_column :posts, :published, :boolean + +Migration names containing JoinTable will generate join tables for use with +has_and_belongs_to_many associations. + +Example: + `rails g migration CreateMediaJoinTable artists musics:uniq` + + will create the migration + + create_join_table :artists, :musics do |t| + # t.index [:artist_id, :music_id] + t.index [:music_id, :artist_id], unique: true + end diff --git a/railties/lib/rails/generators/rails/migration/migration_generator.rb b/railties/lib/rails/generators/rails/migration/migration_generator.rb new file mode 100644 index 0000000000..fca2a8fef4 --- /dev/null +++ b/railties/lib/rails/generators/rails/migration/migration_generator.rb @@ -0,0 +1,8 @@ +module Rails + module Generators + class MigrationGenerator < NamedBase # :nodoc: + argument :attributes, type: :array, default: [], banner: "field[:type][:index] field[:type][:index]" + hook_for :orm, required: true, desc: "ORM to be invoked" + end + end +end diff --git a/railties/lib/rails/generators/rails/model/USAGE b/railties/lib/rails/generators/rails/model/USAGE new file mode 100644 index 0000000000..11daa5c3cb --- /dev/null +++ b/railties/lib/rails/generators/rails/model/USAGE @@ -0,0 +1,114 @@ +Description: + Stubs out a new model. Pass the model name, either CamelCased or + under_scored, and an optional list of attribute pairs as arguments. + + Attribute pairs are field:type arguments specifying the + model's attributes. Timestamps are added by default, so you don't have to + specify them by hand as 'created_at:datetime updated_at:datetime'. + + As a special case, specifying 'password:digest' will generate a + password_digest field of string type, and configure your generated model and + tests for use with ActiveModel has_secure_password (assuming the default ORM + and test framework are being used). + + You don't have to think up every attribute up front, but it helps to + sketch out a few so you can start working with the model immediately. + + This generator invokes your configured ORM and test framework, which + defaults to ActiveRecord and TestUnit. + + Finally, if --parent option is given, it's used as superclass of the + created model. This allows you create Single Table Inheritance models. + + If you pass a namespaced model name (e.g. admin/account or Admin::Account) + then the generator will create a module with a table_name_prefix method + to prefix the model's table name with the module name (e.g. admin_accounts) + +Available field types: + + Just after the field name you can specify a type like text or boolean. + It will generate the column with the associated SQL type. For instance: + + `rails generate model post title:string body:text` + + will generate a title column with a varchar type and a body column with a text + type. If no type is specified the string type will be used by default. + You can use the following types: + + integer + primary_key + decimal + float + boolean + binary + string + text + date + time + datetime + + You can also consider `references` as a kind of type. For instance, if you run: + + `rails generate model photo title:string album:references` + + It will generate an `album_id` column. You should generate these kinds of fields when + you will use a `belongs_to` association, for instance. `references` also supports + polymorphism, you can enable polymorphism like this: + + `rails generate model product supplier:references{polymorphic}` + + For integer, string, text and binary fields, an integer in curly braces will + be set as the limit: + + `rails generate model user pseudo:string{30}` + + For decimal, two integers separated by a comma in curly braces will be used + for precision and scale: + + `rails generate model product 'price:decimal{10,2}'` + + You can add a `:uniq` or `:index` suffix for unique or standard indexes + respectively: + + `rails generate model user pseudo:string:uniq` + `rails generate model user pseudo:string:index` + + You can combine any single curly brace option with the index options: + + `rails generate model user username:string{30}:uniq` + `rails generate model product supplier:references{polymorphic}:index` + + If you require a `password_digest` string column for use with + has_secure_password, you can specify `password:digest`: + + `rails generate model user password:digest` + + If you require a `token` string column for use with + has_secure_token, you can specify `auth_token:token`: + + `rails generate model user auth_token:token` + +Examples: + `rails generate model account` + + For ActiveRecord and TestUnit it creates: + + Model: app/models/account.rb + Test: test/models/account_test.rb + Fixtures: test/fixtures/accounts.yml + Migration: db/migrate/XXX_create_accounts.rb + + `rails generate model post title:string body:text published:boolean` + + Creates a Post model with a string title, text body, and published flag. + + `rails generate model admin/account` + + For ActiveRecord and TestUnit it creates: + + Module: app/models/admin.rb + Model: app/models/admin/account.rb + Test: test/models/admin/account_test.rb + Fixtures: test/fixtures/admin/accounts.yml + Migration: db/migrate/XXX_create_admin_accounts.rb + diff --git a/railties/lib/rails/generators/rails/model/model_generator.rb b/railties/lib/rails/generators/rails/model/model_generator.rb new file mode 100644 index 0000000000..ec78fd855d --- /dev/null +++ b/railties/lib/rails/generators/rails/model/model_generator.rb @@ -0,0 +1,12 @@ +require 'rails/generators/model_helpers' + +module Rails + module Generators + class ModelGenerator < NamedBase # :nodoc: + include Rails::Generators::ModelHelpers + + argument :attributes, type: :array, default: [], banner: "field[:type][:index] field[:type][:index]" + hook_for :orm, required: true, desc: "ORM to be invoked" + end + end +end diff --git a/railties/lib/rails/generators/rails/plugin/USAGE b/railties/lib/rails/generators/rails/plugin/USAGE new file mode 100644 index 0000000000..9a7bf9f396 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/USAGE @@ -0,0 +1,10 @@ +Description: + The 'rails plugin new' command creates a skeleton for developing any + kind of Rails extension with ability to run tests using dummy Rails + application. + +Example: + rails plugin new ~/Code/Ruby/blog + + This generates a skeletal Rails plugin in ~/Code/Ruby/blog. + See the README in the newly created plugin to get going. diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb new file mode 100644 index 0000000000..776019a6a0 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb @@ -0,0 +1,446 @@ +require 'active_support/core_ext/hash/slice' +require "rails/generators/rails/app/app_generator" +require 'date' + +module Rails + # The plugin builder allows you to override elements of the plugin + # generator without being forced to reverse the operations of the default + # generator. + # + # This allows you to override entire operations, like the creation of the + # Gemfile, \README, or JavaScript files, without needing to know exactly + # what those operations do so you can create another template action. + class PluginBuilder + def rakefile + template "Rakefile" + end + + def app + if mountable? + if api? + directory 'app', exclude_pattern: %r{app/(views|helpers)} + else + directory 'app' + empty_directory_with_keep_file "app/assets/images/#{namespaced_name}" + end + elsif full? + empty_directory_with_keep_file 'app/models' + empty_directory_with_keep_file 'app/controllers' + empty_directory_with_keep_file 'app/mailers' + + unless api? + empty_directory_with_keep_file "app/assets/images/#{namespaced_name}" + empty_directory_with_keep_file 'app/helpers' + empty_directory_with_keep_file 'app/views' + end + end + end + + def readme + template "README.rdoc" + end + + def gemfile + template "Gemfile" + end + + def license + template "MIT-LICENSE" + end + + def gemspec + template "%name%.gemspec" + end + + def gitignore + template "gitignore", ".gitignore" + end + + def lib + template "lib/%namespaced_name%.rb" + template "lib/tasks/%namespaced_name%_tasks.rake" + template "lib/%namespaced_name%/version.rb" + template "lib/%namespaced_name%/engine.rb" if engine? + end + + def config + template "config/routes.rb" if engine? + end + + def test + template "test/test_helper.rb" + template "test/%namespaced_name%_test.rb" + append_file "Rakefile", <<-EOF +#{rakefile_test_tasks} + +task default: :test + EOF + if engine? + template "test/integration/navigation_test.rb" + end + end + + PASSTHROUGH_OPTIONS = [ + :skip_active_record, :skip_action_mailer, :skip_javascript, :database, + :javascript, :quiet, :pretend, :force, :skip + ] + + def generate_test_dummy(force = false) + opts = (options || {}).slice(*PASSTHROUGH_OPTIONS) + opts[:force] = force + opts[:skip_bundle] = true + opts[:api] = options.api? + + invoke Rails::Generators::AppGenerator, + [ File.expand_path(dummy_path, destination_root) ], opts + end + + def test_dummy_config + template "rails/boot.rb", "#{dummy_path}/config/boot.rb", force: true + template "rails/application.rb", "#{dummy_path}/config/application.rb", force: true + if mountable? + template "rails/routes.rb", "#{dummy_path}/config/routes.rb", force: true + end + end + + def test_dummy_assets + template "rails/javascripts.js", "#{dummy_path}/app/assets/javascripts/application.js", force: true + template "rails/stylesheets.css", "#{dummy_path}/app/assets/stylesheets/application.css", force: true + template "rails/dummy_manifest.js", "#{dummy_path}/app/assets/config/manifest.js", force: true + end + + def test_dummy_clean + inside dummy_path do + remove_file ".gitignore" + remove_file "db/seeds.rb" + remove_file "doc" + remove_file "Gemfile" + remove_file "lib/tasks" + remove_file "public/robots.txt" + remove_file "README.md" + remove_file "test" + remove_file "vendor" + end + end + + def assets_manifest + template "rails/engine_manifest.js", "app/assets/config/#{underscored_name}_manifest.js" + end + + def stylesheets + if mountable? + copy_file "rails/stylesheets.css", + "app/assets/stylesheets/#{namespaced_name}/application.css" + elsif full? + empty_directory_with_keep_file "app/assets/stylesheets/#{namespaced_name}" + end + end + + def javascripts + return if options.skip_javascript? + + if mountable? + template "rails/javascripts.js", + "app/assets/javascripts/#{namespaced_name}/application.js" + elsif full? + empty_directory_with_keep_file "app/assets/javascripts/#{namespaced_name}" + end + end + + def bin(force = false) + bin_file = engine? ? 'bin/rails.tt' : 'bin/test.tt' + template bin_file, force: force do |content| + "#{shebang}\n" + content + end + chmod "bin", 0755, verbose: false + end + + def gemfile_entry + return unless inside_application? + + gemfile_in_app_path = File.join(rails_app_path, "Gemfile") + if File.exist? gemfile_in_app_path + entry = "gem '#{name}', path: '#{relative_path}'" + append_file gemfile_in_app_path, entry + end + end + end + + module Generators + class PluginGenerator < AppBase # :nodoc: + add_shared_options_for "plugin" + + alias_method :plugin_path, :app_path + + class_option :dummy_path, type: :string, default: "test/dummy", + desc: "Create dummy application at given path" + + class_option :full, type: :boolean, default: false, + desc: "Generate a rails engine with bundled Rails application for testing" + + class_option :mountable, type: :boolean, default: false, + desc: "Generate mountable isolated application" + + class_option :skip_gemspec, type: :boolean, default: false, + desc: "Skip gemspec file" + + class_option :skip_gemfile_entry, type: :boolean, default: false, + desc: "If creating plugin in application's directory " + + "skip adding entry to Gemfile" + + class_option :api, type: :boolean, default: false, + desc: "Generate a smaller stack for API application plugins" + + def initialize(*args) + @dummy_path = nil + super + + unless plugin_path + raise Error, "Plugin name should be provided in arguments. For details run: rails plugin new --help" + end + end + + public_task :set_default_accessors! + public_task :create_root + + def create_root_files + build(:readme) + build(:rakefile) + build(:gemspec) unless options[:skip_gemspec] + build(:license) + build(:gitignore) unless options[:skip_git] + build(:gemfile) unless options[:skip_gemfile] + end + + def create_app_files + build(:app) + end + + def create_config_files + build(:config) + end + + def create_lib_files + build(:lib) + end + + def create_assets_manifest_file + build(:assets_manifest) if !api? && engine? + end + + def create_public_stylesheets_files + build(:stylesheets) unless api? + end + + def create_javascript_files + build(:javascripts) unless api? + end + + def create_bin_files + build(:bin) + end + + def create_test_files + build(:test) unless options[:skip_test] + end + + def create_test_dummy_files + return unless with_dummy_app? + create_dummy_app + end + + def update_gemfile + build(:gemfile_entry) unless options[:skip_gemfile_entry] + end + + def finish_template + build(:leftovers) + end + + public_task :apply_rails_template, :run_bundle + + def name + @name ||= begin + # same as ActiveSupport::Inflector#underscore except not replacing '-' + underscored = original_name.dup + underscored.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2') + underscored.gsub!(/([a-z\d])([A-Z])/,'\1_\2') + underscored.downcase! + + underscored + end + end + + def underscored_name + @underscored_name ||= original_name.underscore + end + + def namespaced_name + @namespaced_name ||= name.gsub('-', '/') + end + + protected + + def app_templates_dir + "../../app/templates" + end + + def create_dummy_app(path = nil) + dummy_path(path) if path + + say_status :vendor_app, dummy_path + mute do + build(:generate_test_dummy) + store_application_definition! + build(:test_dummy_config) + build(:test_dummy_assets) + build(:test_dummy_clean) + # ensure that bin/rails has proper dummy_path + build(:bin, true) + end + end + + def engine? + full? || mountable? + end + + def full? + options[:full] + end + + def mountable? + options[:mountable] + end + + def skip_git? + options[:skip_git] + end + + def with_dummy_app? + options[:skip_test].blank? || options[:dummy_path] != 'test/dummy' + end + + def api? + options[:api] + end + + def self.banner + "rails plugin new #{self.arguments.map(&:usage).join(' ')} [options]" + end + + def original_name + @original_name ||= File.basename(destination_root) + end + + def modules + @modules ||= namespaced_name.camelize.split("::") + end + + def wrap_in_modules(unwrapped_code) + unwrapped_code = "#{unwrapped_code}".strip.gsub(/\W$\n/, '') + modules.reverse.inject(unwrapped_code) do |content, mod| + str = "module #{mod}\n" + str += content.lines.map { |line| " #{line}" }.join + str += content.present? ? "\nend" : "end" + end + end + + def camelized_modules + @camelized_modules ||= namespaced_name.camelize + end + + def humanized + @humanized ||= original_name.underscore.humanize + end + + def camelized + @camelized ||= name.gsub(/\W/, '_').squeeze('_').camelize + end + + def author + default = "TODO: Write your name" + if skip_git? + @author = default + else + @author = `git config user.name`.chomp rescue default + end + end + + def email + default = "TODO: Write your email address" + if skip_git? + @email = default + else + @email = `git config user.email`.chomp rescue default + end + end + + def valid_const? + if original_name =~ /-\d/ + raise Error, "Invalid plugin name #{original_name}. Please give a name which does not contain a namespace starting with numeric characters." + elsif original_name =~ /[^\w-]+/ + raise Error, "Invalid plugin name #{original_name}. Please give a name which uses only alphabetic, numeric, \"_\" or \"-\" characters." + elsif camelized =~ /^\d/ + raise Error, "Invalid plugin name #{original_name}. Please give a name which does not start with numbers." + elsif RESERVED_NAMES.include?(name) + raise Error, "Invalid plugin name #{original_name}. Please give a " \ + "name which does not match one of the reserved rails " \ + "words: #{RESERVED_NAMES.join(", ")}" + elsif Object.const_defined?(camelized) + raise Error, "Invalid plugin name #{original_name}, constant #{camelized} is already in use. Please choose another plugin name." + end + end + + def application_definition + @application_definition ||= begin + + dummy_application_path = File.expand_path("#{dummy_path}/config/application.rb", destination_root) + unless options[:pretend] || !File.exist?(dummy_application_path) + contents = File.read(dummy_application_path) + contents[(contents.index(/module ([\w]+)\n(.*)class Application/m))..-1] + end + end + end + alias :store_application_definition! :application_definition + + def get_builder_class + defined?(::PluginBuilder) ? ::PluginBuilder : Rails::PluginBuilder + end + + def rakefile_test_tasks + <<-RUBY +require 'rake/testtask' + +Rake::TestTask.new(:test) do |t| + t.libs << 'lib' + t.libs << 'test' + t.pattern = 'test/**/*_test.rb' + t.verbose = false +end + RUBY + end + + def dummy_path(path = nil) + @dummy_path = path if path + @dummy_path || options[:dummy_path] + end + + def mute(&block) + shell.mute(&block) + end + + def rails_app_path + APP_PATH.sub("/config/application", "") if defined?(APP_PATH) + end + + def inside_application? + rails_app_path && app_path =~ /^#{rails_app_path}/ + end + + def relative_path + return unless inside_application? + app_path.sub(/^#{rails_app_path}\//, '') + end + end + end +end diff --git a/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec b/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec new file mode 100644 index 0000000000..3f5682b0c1 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec @@ -0,0 +1,24 @@ +$:.push File.expand_path("../lib", __FILE__) + +# Maintain your gem's version: +require "<%= namespaced_name %>/version" + +# Describe your gem and declare its dependencies: +Gem::Specification.new do |s| + s.name = "<%= name %>" + s.version = <%= camelized_modules %>::VERSION + s.authors = ["<%= author %>"] + s.email = ["<%= email %>"] + s.homepage = "TODO" + s.summary = "TODO: Summary of <%= camelized_modules %>." + s.description = "TODO: Description of <%= camelized_modules %>." + s.license = "MIT" + + s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.rdoc"] + + <%= '# ' if options.dev? || options.edge? -%>s.add_dependency "rails", "~> <%= Rails::VERSION::STRING %>" +<% unless options[:skip_active_record] -%> + + s.add_development_dependency "<%= gem_for_database[0] %>" +<% end -%> +end diff --git a/railties/lib/rails/generators/rails/plugin/templates/Gemfile b/railties/lib/rails/generators/rails/plugin/templates/Gemfile new file mode 100644 index 0000000000..f085a2577a --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/Gemfile @@ -0,0 +1,47 @@ +source 'https://rubygems.org' + +<% if options[:skip_gemspec] -%> +<%= '# ' if options.dev? || options.edge? -%>gem 'rails', '~> <%= Rails::VERSION::STRING %>' +<% else -%> +# Declare your gem's dependencies in <%= name %>.gemspec. +# Bundler will treat runtime dependencies like base dependencies, and +# development dependencies will be added by default to the :development group. +gemspec +<% end -%> + +<% if options[:skip_gemspec] -%> +group :development do + gem '<%= gem_for_database[0] %>' +end +<% else -%> +# Declare any dependencies that are still in development here instead of in +# your gemspec. These might include edge Rails or gems from your path or +# Git. Remember to move these dependencies to your gemspec before releasing +# your gem to rubygems.org. +<% end -%> + +<% if options.dev? || options.edge? -%> +# Your gem is dependent on dev or edge Rails. Once you can lock this +# dependency down to a specific version, move it to your gemspec. +<% max_width = gemfile_entries.map { |g| g.name.length }.max -%> +<% gemfile_entries.each do |gem| -%> +<% if gem.comment -%> + +# <%= gem.comment %> +<% end -%> +<%= gem.commented_out ? '# ' : '' %>gem '<%= gem.name %>'<%= %(, '#{gem.version}') if gem.version -%> +<% if gem.options.any? -%> +, <%= gem.options.map { |k,v| + "#{k}: #{v.inspect}" }.join(', ') %> +<% end -%> +<% end -%> + +<% end -%> +<% if RUBY_ENGINE == 'ruby' -%> +# To use a debugger +# gem 'byebug', group: [:development, :test] +<% end -%> +<% if RUBY_PLATFORM.match(/bccwin|cygwin|emx|mingw|mswin|wince|java/) -%> + +gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] +<% end -%> diff --git a/railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE b/railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE new file mode 100644 index 0000000000..ff2fb3ba4e --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE @@ -0,0 +1,20 @@ +Copyright <%= Date.today.year %> <%= author %> + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/railties/lib/rails/generators/rails/plugin/templates/README.rdoc b/railties/lib/rails/generators/rails/plugin/templates/README.rdoc new file mode 100644 index 0000000000..25983ca5da --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/README.rdoc @@ -0,0 +1,3 @@ += <%= camelized_modules %> + +This project rocks and uses MIT-LICENSE.
\ No newline at end of file diff --git a/railties/lib/rails/generators/rails/plugin/templates/Rakefile b/railties/lib/rails/generators/rails/plugin/templates/Rakefile new file mode 100644 index 0000000000..bda55bae29 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/Rakefile @@ -0,0 +1,29 @@ +begin + require 'bundler/setup' +rescue LoadError + puts 'You must `gem install bundler` and `bundle install` to run rake tasks' +end + +require 'rdoc/task' + +RDoc::Task.new(:rdoc) do |rdoc| + rdoc.rdoc_dir = 'rdoc' + rdoc.title = '<%= camelized_modules %>' + rdoc.options << '--line-numbers' + rdoc.rdoc_files.include('README.rdoc') + rdoc.rdoc_files.include('lib/**/*.rb') +end + +<% if engine? && !options[:skip_active_record] && with_dummy_app? -%> +APP_RAKEFILE = File.expand_path("../<%= dummy_path -%>/Rakefile", __FILE__) +load 'rails/tasks/engine.rake' +<% end %> + +<% if engine? -%> +load 'rails/tasks/statistics.rake' +<% end %> + +<% unless options[:skip_gemspec] -%> + +Bundler::GemHelper.install_tasks +<% end %> diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt new file mode 100644 index 0000000000..7fe4e5034d --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt @@ -0,0 +1,5 @@ +<%= wrap_in_modules <<-rb.strip_heredoc + class ApplicationController < ActionController::#{api? ? "API" : "Base"} + end +rb +%> diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/helpers/%namespaced_name%/application_helper.rb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/helpers/%namespaced_name%/application_helper.rb.tt new file mode 100644 index 0000000000..25d692732d --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/app/helpers/%namespaced_name%/application_helper.rb.tt @@ -0,0 +1,5 @@ +<%= wrap_in_modules <<-rb.strip_heredoc + module ApplicationHelper + end +rb +%> diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/jobs/%namespaced_name%/application_job.rb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/jobs/%namespaced_name%/application_job.rb.tt new file mode 100644 index 0000000000..bad1ff2d16 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/app/jobs/%namespaced_name%/application_job.rb.tt @@ -0,0 +1,5 @@ +<%= wrap_in_modules <<-rb.strip_heredoc + class ApplicationJob < ActiveJob::Base + end +rb +%> diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/mailers/.empty_directory b/railties/lib/rails/generators/rails/plugin/templates/app/mailers/.empty_directory new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/app/mailers/.empty_directory diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/models/.empty_directory b/railties/lib/rails/generators/rails/plugin/templates/app/models/.empty_directory new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/app/models/.empty_directory diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/views/layouts/%namespaced_name%/application.html.erb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/views/layouts/%namespaced_name%/application.html.erb.tt new file mode 100644 index 0000000000..6bc480161d --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/app/views/layouts/%namespaced_name%/application.html.erb.tt @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html> +<head> + <title><%= humanized %></title> + <%%= stylesheet_link_tag "<%= namespaced_name %>/application", media: "all" %> + <%%= javascript_include_tag "<%= namespaced_name %>/application" %> + <%%= csrf_meta_tags %> +</head> +<body> + +<%%= yield %> + +</body> +</html> diff --git a/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt b/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt new file mode 100644 index 0000000000..3edaac35c9 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt @@ -0,0 +1,11 @@ +# This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application. + +ENGINE_ROOT = File.expand_path('../..', __FILE__) +ENGINE_PATH = File.expand_path('../../lib/<%= namespaced_name -%>/engine', __FILE__) + +# Set up gems listed in the Gemfile. +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) +require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) + +require 'rails/all' +require 'rails/engine/commands' diff --git a/railties/lib/rails/generators/rails/plugin/templates/bin/test.tt b/railties/lib/rails/generators/rails/plugin/templates/bin/test.tt new file mode 100644 index 0000000000..62b94618fd --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/bin/test.tt @@ -0,0 +1,8 @@ +$: << File.expand_path(File.expand_path('../../test', __FILE__)) + +require 'bundler/setup' +require 'rails/test_unit/minitest_plugin' + +Rails::TestUnitReporter.executable = 'bin/test' + +exit Minitest.run(ARGV) diff --git a/railties/lib/rails/generators/rails/plugin/templates/config/routes.rb b/railties/lib/rails/generators/rails/plugin/templates/config/routes.rb new file mode 100644 index 0000000000..154452bfe5 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/config/routes.rb @@ -0,0 +1,6 @@ +<% if mountable? -%> +<%= camelized_modules %>::Engine.routes.draw do +<% else -%> +Rails.application.routes.draw do +<% end -%> +end diff --git a/railties/lib/rails/generators/rails/plugin/templates/gitignore b/railties/lib/rails/generators/rails/plugin/templates/gitignore new file mode 100644 index 0000000000..54c78d7927 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/gitignore @@ -0,0 +1,9 @@ +.bundle/ +log/*.log +pkg/ +<% unless options[:skip_test] && options[:dummy_path] == 'test/dummy' -%> +<%= dummy_path %>/db/*.sqlite3 +<%= dummy_path %>/db/*.sqlite3-journal +<%= dummy_path %>/log/*.log +<%= dummy_path %>/tmp/ +<% end -%> diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb new file mode 100644 index 0000000000..40b1c4cee7 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb @@ -0,0 +1,5 @@ +<% if engine? -%> +require "<%= namespaced_name %>/engine" + +<% end -%> +<%= wrap_in_modules "# Your code goes here..." %> diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb new file mode 100644 index 0000000000..8938770fc4 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb @@ -0,0 +1,7 @@ +<%= wrap_in_modules <<-rb.strip_heredoc + class Engine < ::Rails::Engine + #{mountable? ? ' isolate_namespace ' + camelized_modules : ' '} + #{api? ? " config.generators.api_only = true" : ' '} + end +rb +%> diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb new file mode 100644 index 0000000000..b08f4ef9ae --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb @@ -0,0 +1 @@ +<%= wrap_in_modules "VERSION = '0.1.0'" %> diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%namespaced_name%_tasks.rake b/railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%namespaced_name%_tasks.rake new file mode 100644 index 0000000000..88a2c4120f --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%namespaced_name%_tasks.rake @@ -0,0 +1,4 @@ +# desc "Explaining what the task does" +# task :<%= underscored_name %> do +# # Task goes here +# end diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb b/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb new file mode 100644 index 0000000000..b1038c839e --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb @@ -0,0 +1,18 @@ +require File.expand_path('../boot', __FILE__) + +<% if include_all_railties? -%> +require 'rails/all' +<% else -%> +# Pick the frameworks you want: +<%= comment_if :skip_active_record %>require "active_record/railtie" +require "action_controller/railtie" +<%= comment_if :skip_action_mailer %>require "action_mailer/railtie" +require "action_view/railtie" +<%= comment_if :skip_sprockets %>require "sprockets/railtie" +<%= comment_if :skip_test %>require "rails/test_unit/railtie" +<% end -%> + +Bundler.require(*Rails.groups) +require "<%= namespaced_name %>" + +<%= application_definition %> diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/boot.rb b/railties/lib/rails/generators/rails/plugin/templates/rails/boot.rb new file mode 100644 index 0000000000..6266cfc509 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/rails/boot.rb @@ -0,0 +1,5 @@ +# Set up gems listed in the Gemfile. +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__) + +require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) +$LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__) diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/dummy_manifest.js b/railties/lib/rails/generators/rails/plugin/templates/rails/dummy_manifest.js new file mode 100644 index 0000000000..8d21b2b6fb --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/rails/dummy_manifest.js @@ -0,0 +1,11 @@ + +<% unless api? -%> +//= link_tree ../images +<% end -%> +<% unless options.skip_javascript -%> +//= link_directory ../javascripts .js +<% end -%> +//= link_directory ../stylesheets .css +<% if mountable? && !api? -%> +//= link <%= underscored_name %>_manifest.js +<% end -%> diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/engine_manifest.js b/railties/lib/rails/generators/rails/plugin/templates/rails/engine_manifest.js new file mode 100644 index 0000000000..2f23844f5e --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/rails/engine_manifest.js @@ -0,0 +1,6 @@ +<% if mountable? -%> +<% if !options.skip_javascript -%> +//= link_directory ../javascripts/<%= namespaced_name %> .js +<% end -%> +//= link_directory ../stylesheets/<%= namespaced_name %> .css +<% end -%> diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js b/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js new file mode 100644 index 0000000000..e54c6461cc --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js @@ -0,0 +1,13 @@ +// This is a manifest file that'll be compiled into application.js, which will include all the files +// listed below. +// +// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, +// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. +// +// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the +// compiled file. JavaScript code in this file should be added after the last require_* statement. +// +// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details +// about supported directives. +// +//= require_tree . diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb b/railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb new file mode 100644 index 0000000000..694510edc0 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb @@ -0,0 +1,3 @@ +Rails.application.routes.draw do + mount <%= camelized_modules %>::Engine => "/<%= name %>" +end diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css b/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css new file mode 100644 index 0000000000..0ebd7fe829 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css @@ -0,0 +1,15 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, + * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS + * files in this directory. Styles in this file should be added after the last require_* statement. + * It is generally better to create a new file per style scope. + * + *= require_tree . + *= require_self + */ diff --git a/railties/lib/rails/generators/rails/plugin/templates/test/%namespaced_name%_test.rb b/railties/lib/rails/generators/rails/plugin/templates/test/%namespaced_name%_test.rb new file mode 100644 index 0000000000..1ee05d7871 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/test/%namespaced_name%_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class <%= camelized_modules %>::Test < ActiveSupport::TestCase + test "truth" do + assert_kind_of Module, <%= camelized_modules %> + end +end diff --git a/railties/lib/rails/generators/rails/plugin/templates/test/integration/navigation_test.rb b/railties/lib/rails/generators/rails/plugin/templates/test/integration/navigation_test.rb new file mode 100644 index 0000000000..f5d1ec2046 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/test/integration/navigation_test.rb @@ -0,0 +1,8 @@ +require 'test_helper' + +class NavigationTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end + diff --git a/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb new file mode 100644 index 0000000000..a0b00fc5c5 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb @@ -0,0 +1,27 @@ +# Configure Rails Environment +ENV["RAILS_ENV"] = "test" + +require File.expand_path("../../<%= options[:dummy_path] -%>/config/environment.rb", __FILE__) +<% unless options[:skip_active_record] -%> +ActiveRecord::Migrator.migrations_paths = [File.expand_path("../../<%= options[:dummy_path] -%>/db/migrate", __FILE__)] +<% if options[:mountable] -%> +ActiveRecord::Migrator.migrations_paths << File.expand_path('../../db/migrate', __FILE__) +<% end -%> +<% end -%> +require "rails/test_help" + +# Filter out Minitest backtrace while allowing backtrace from other libraries +# to be shown. +Minitest.backtrace_filter = Minitest::BacktraceFilter.new + +<% unless engine? -%> +Rails::TestUnitReporter.executable = 'bin/test' +<% end -%> + +# Load fixtures from the engine +if ActiveSupport::TestCase.respond_to?(:fixture_path=) + ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__) + ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path + ActiveSupport::TestCase.file_fixture_path = ActiveSupport::TestCase.fixture_path + "/files" + ActiveSupport::TestCase.fixtures :all +end diff --git a/railties/lib/rails/generators/rails/resource/USAGE b/railties/lib/rails/generators/rails/resource/USAGE new file mode 100644 index 0000000000..e359cd574f --- /dev/null +++ b/railties/lib/rails/generators/rails/resource/USAGE @@ -0,0 +1,23 @@ +Description: + Stubs out a new resource including an empty model and controller suitable + for a restful, resource-oriented application. Pass the singular model name, + either CamelCased or under_scored, as the first argument, and an optional + list of attribute pairs. + + Attribute pairs are field:type arguments specifying the + model's attributes. Timestamps are added by default, so you don't have to + specify them by hand as 'created_at:datetime updated_at:datetime'. + + You don't have to think up every attribute up front, but it helps to + sketch out a few so you can start working with the model immediately. + + This generator invokes your configured ORM and test framework, besides + creating helpers and add routes to config/routes.rb. + + Unlike the scaffold generator, the resource generator does not create + views or add any methods to the generated controller. + +Examples: + `rails generate resource post` # no attributes + `rails generate resource post title:string body:text published:boolean` + `rails generate resource purchase order_id:integer amount:decimal` diff --git a/railties/lib/rails/generators/rails/resource/resource_generator.rb b/railties/lib/rails/generators/rails/resource/resource_generator.rb new file mode 100644 index 0000000000..3acf21df13 --- /dev/null +++ b/railties/lib/rails/generators/rails/resource/resource_generator.rb @@ -0,0 +1,19 @@ +require 'rails/generators/resource_helpers' +require 'rails/generators/rails/model/model_generator' + +module Rails + module Generators + class ResourceGenerator < ModelGenerator # :nodoc: + include ResourceHelpers + + hook_for :resource_controller, required: true do |controller| + invoke controller, [ controller_name, options[:actions] ] + end + + class_option :actions, type: :array, banner: "ACTION ACTION", default: [], + desc: "Actions for the resource controller" + + hook_for :resource_route, required: true + end + end +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 new file mode 100644 index 0000000000..42705107ae --- /dev/null +++ b/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb @@ -0,0 +1,51 @@ +module Rails + module Generators + class ResourceRouteGenerator < NamedBase # :nodoc: + # Properly nests namespaces passed into a generator + # + # $ rails generate resource admin/users/products + # + # should give you + # + # namespace :admin do + # namespace :users do + # resources :products + # end + # end + def add_resource_route + return if options[:actions].present? + + # iterates over all namespaces and opens up blocks + regular_class_path.each_with_index do |namespace, index| + write("namespace :#{namespace} do", index + 1) + end + + # inserts the primary resource + write("resources :#{file_name.pluralize}", route_length + 1) + + # ends blocks + regular_class_path.each_index do |index| + write("end", route_length - index) + end + + # route prepends two spaces onto the front of the string that is passed, this corrects that. + # Also it adds a \n to the end of each line, as route already adds that + # we need to correct that too. + route route_string[2..-2] + end + + private + def route_string + @route_string ||= "" + end + + def write(str, indent) + route_string << "#{" " * indent}#{str}\n" + end + + def route_length + regular_class_path.length + end + end + end +end diff --git a/railties/lib/rails/generators/rails/scaffold/USAGE b/railties/lib/rails/generators/rails/scaffold/USAGE new file mode 100644 index 0000000000..d2e495758d --- /dev/null +++ b/railties/lib/rails/generators/rails/scaffold/USAGE @@ -0,0 +1,41 @@ +Description: + Scaffolds an entire resource, from model and migration to controller and + views, along with a full test suite. The resource is ready to use as a + starting point for your RESTful, resource-oriented application. + + Pass the name of the model (in singular form), either CamelCased or + under_scored, as the first argument, and an optional list of attribute + pairs. + + Attributes are field arguments specifying the model's attributes. You can + optionally pass the type and an index to each field. For instance: + 'title body:text tracking_id:integer:uniq' will generate a title field of + string type, a body with text type and a tracking_id as an integer with an + unique index. "index" could also be given instead of "uniq" if one desires + a non unique index. + + As a special case, specifying 'password:digest' will generate a + password_digest field of string type, and configure your generated model, + controller, views, and test suite for use with ActiveModel + has_secure_password (assuming they are using Rails defaults). + + Timestamps are added by default, so you don't have to specify them by hand + as 'created_at:datetime updated_at:datetime'. + + You don't have to think up every attribute up front, but it helps to + sketch out a few so you can start working with the resource immediately. + + For example, 'scaffold post title body:text published:boolean' gives + you a model with those three attributes, a controller that handles + the create/show/update/destroy, forms to create and edit your posts, and + an index that lists them all, as well as a resources :posts declaration + in config/routes.rb. + + If you want to remove all the generated files, run + 'rails destroy scaffold ModelName'. + +Examples: + `rails generate scaffold post` + `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 new file mode 100644 index 0000000000..17c32bfdb3 --- /dev/null +++ b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb @@ -0,0 +1,33 @@ +require 'rails/generators/rails/resource/resource_generator' + +module Rails + module Generators + class ScaffoldGenerator < ResourceGenerator # :nodoc: + remove_hook_for :resource_controller + remove_class_option :actions + + class_option :stylesheets, type: :boolean, desc: "Generate Stylesheets" + 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[:scaffold_stylesheet] + end + + hook_for :scaffold_controller, required: true + + hook_for :assets do |assets| + invoke assets, [controller_name] + end + + hook_for :stylesheet_engine do |stylesheet_engine| + if behavior == :invoke + invoke stylesheet_engine, [controller_name] + end + end + end + end +end diff --git a/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css b/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css new file mode 100644 index 0000000000..79f8b7f96f --- /dev/null +++ b/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css @@ -0,0 +1,84 @@ +body { + background-color: #fff; + color: #333; +} + +body, p, ol, ul, td { + font-family: verdana, arial, helvetica, sans-serif; + font-size: 13px; + line-height: 18px; + margin: 33px; +} + +pre { + background-color: #eee; + padding: 10px; + font-size: 11px; +} + +a { + color: #000; +} + +a:visited { + color: #666; +} + +a:hover { + color: #fff; + background-color: #000; +} + +th { + padding-bottom: 5px; +} + +td { + padding-bottom: 7px; + padding-left: 5px; + padding-right: 5px; +} + +div.field, +div.actions { + margin-bottom: 10px; +} + +#notice { + color: green; +} + +.field_with_errors { + padding: 2px; + background-color: red; + display: table; +} + +#error_explanation { + width: 450px; + border: 2px solid red; + padding: 7px; + padding-bottom: 0; + margin-bottom: 20px; + background-color: #f0f0f0; +} + +#error_explanation h2 { + text-align: left; + font-weight: bold; + padding: 5px 5px 5px 15px; + font-size: 12px; + margin: -7px; + margin-bottom: 0; + background-color: #c00; + color: #fff; +} + +#error_explanation ul li { + font-size: 12px; + list-style: square; +} + +label { + display: block; +} diff --git a/railties/lib/rails/generators/rails/scaffold_controller/USAGE b/railties/lib/rails/generators/rails/scaffold_controller/USAGE new file mode 100644 index 0000000000..8ba4c5ccbc --- /dev/null +++ b/railties/lib/rails/generators/rails/scaffold_controller/USAGE @@ -0,0 +1,19 @@ +Description: + Stubs out a scaffolded controller, its seven RESTful actions and related + views. Pass the model name, either CamelCased or under_scored. The + controller name is retrieved as a pluralized version of the model name. + + To create a controller within a module, specify the model name as a + path like 'parent_module/controller_name'. + + This generates a controller class in app/controllers and invokes helper, + template engine and test framework generators. + +Example: + `rails generate scaffold_controller CreditCard` + + Credit card controller with URLs like /credit_card/debit. + Controller: app/controllers/credit_cards_controller.rb + Test: test/controllers/credit_cards_controller_test.rb + Views: app/views/credit_cards/index.html.erb [...] + Helper: app/helpers/credit_cards_helper.rb 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 new file mode 100644 index 0000000000..d0b8cad896 --- /dev/null +++ b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb @@ -0,0 +1,31 @@ +require 'rails/generators/resource_helpers' + +module Rails + module Generators + class ScaffoldControllerGenerator < NamedBase # :nodoc: + include ResourceHelpers + + check_class_collision suffix: "Controller" + + 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_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 + + # Invoke the helper using the controller name (pluralized) + hook_for :helper, as: :scaffold do |invoked| + invoke invoked, [ controller_name ] + end + end + end +end 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..400afec6dc --- /dev/null +++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb @@ -0,0 +1,61 @@ +<% if namespaced? -%> +require_dependency "<%= namespaced_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.fetch(:<%= 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/rails/scaffold_controller/templates/controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb new file mode 100644 index 0000000000..42b9e34274 --- /dev/null +++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb @@ -0,0 +1,68 @@ +<% if namespaced? -%> +require_dependency "<%= namespaced_path %>/application_controller" + +<% end -%> +<% module_namespacing do -%> +class <%= controller_class_name %>Controller < ApplicationController + before_action :set_<%= singular_table_name %>, only: [:show, :edit, :update, :destroy] + + # GET <%= route_url %> + def index + @<%= plural_table_name %> = <%= orm_class.all(class_name) %> + end + + # GET <%= route_url %>/1 + def show + end + + # GET <%= route_url %>/new + def new + @<%= singular_table_name %> = <%= orm_class.build(class_name) %> + end + + # GET <%= route_url %>/1/edit + def edit + end + + # POST <%= route_url %> + def create + @<%= singular_table_name %> = <%= orm_class.build(class_name, "#{singular_table_name}_params") %> + + if @<%= orm_instance.save %> + redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully created.'" %> + else + render :new + end + end + + # PATCH/PUT <%= route_url %>/1 + def update + if @<%= orm_instance.update("#{singular_table_name}_params") %> + redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully updated.'" %> + else + render :edit + end + end + + # DELETE <%= route_url %>/1 + def destroy + @<%= orm_instance.destroy %> + redirect_to <%= index_helper %>_url, notice: <%= "'#{human_name} was successfully destroyed.'" %> + 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.fetch(:<%= 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/rails/task/USAGE b/railties/lib/rails/generators/rails/task/USAGE new file mode 100644 index 0000000000..dbe9bbaf08 --- /dev/null +++ b/railties/lib/rails/generators/rails/task/USAGE @@ -0,0 +1,9 @@ +Description: + Stubs out a new Rake task. Pass the namespace name, and a list of tasks as arguments. + + This generates a task file in lib/tasks. + +Example: + `rails generate task feeds fetch erase add` + + Task: lib/tasks/feeds.rake
\ No newline at end of file diff --git a/railties/lib/rails/generators/rails/task/task_generator.rb b/railties/lib/rails/generators/rails/task/task_generator.rb new file mode 100644 index 0000000000..754824ca0c --- /dev/null +++ b/railties/lib/rails/generators/rails/task/task_generator.rb @@ -0,0 +1,12 @@ +module Rails + module Generators + class TaskGenerator < NamedBase # :nodoc: + argument :actions, type: :array, default: [], banner: "action action" + + def create_task_files + template 'task.rb', File.join('lib/tasks', "#{file_name}.rake") + end + + end + end +end diff --git a/railties/lib/rails/generators/rails/task/templates/task.rb b/railties/lib/rails/generators/rails/task/templates/task.rb new file mode 100644 index 0000000000..1e3ed5f158 --- /dev/null +++ b/railties/lib/rails/generators/rails/task/templates/task.rb @@ -0,0 +1,8 @@ +namespace :<%= file_name %> do +<% actions.each do |action| -%> + desc "TODO" + task <%= action %>: :environment do + end + +<% end -%> +end diff --git a/railties/lib/rails/generators/resource_helpers.rb b/railties/lib/rails/generators/resource_helpers.rb new file mode 100644 index 0000000000..9c2037783e --- /dev/null +++ b/railties/lib/rails/generators/resource_helpers.rb @@ -0,0 +1,82 @@ +require 'rails/generators/active_model' +require 'rails/generators/model_helpers' + +module Rails + module Generators + # Deal with controller names on scaffold and add some helpers to deal with + # ActiveModel. + module ResourceHelpers # :nodoc: + + def self.included(base) #:nodoc: + base.include(Rails::Generators::ModelHelpers) + base.class_option :model_name, type: :string, desc: "ModelName to be used" + end + + # Set controller variables on initialization. + def initialize(*args) #:nodoc: + super + controller_name = name + if options[:model_name] + self.name = options[:model_name] + assign_names!(self.name) + end + + assign_controller_names!(controller_name.pluralize) + end + + protected + + attr_reader :controller_name, :controller_file_name + + def controller_class_path + if options[:model_name] + @controller_class_path + else + class_path + end + end + + def assign_controller_names!(name) + @controller_name = name + @controller_class_path = name.include?('/') ? name.split('/') : name.split('::') + @controller_class_path.map!(&:underscore) + @controller_file_name = @controller_class_path.pop + end + + def controller_file_path + @controller_file_path ||= (controller_class_path + [controller_file_name]).join('/') + end + + def controller_class_name + (controller_class_path + [controller_file_name]).map!(&:camelize).join('::') + end + + def controller_i18n_scope + @controller_i18n_scope ||= controller_file_path.tr('/', '.') + end + + # Loads the ORM::Generators::ActiveModel class. This class is responsible + # to tell scaffold entities how to generate an specific method for the + # ORM. Check Rails::Generators::ActiveModel for more information. + def orm_class + @orm_class ||= begin + # Raise an error if the class_option :orm was not defined. + unless self.class.class_options[:orm] + raise "You need to have :orm as class option to invoke orm_class and orm_instance" + end + + begin + "#{options[:orm].to_s.camelize}::Generators::ActiveModel".constantize + rescue NameError + Rails::Generators::ActiveModel + end + end + end + + # Initialize ORM::Generators::ActiveModel to access instance methods. + def orm_instance(name=singular_table_name) + @orm_instance ||= orm_class.new(name) + end + end + end +end diff --git a/railties/lib/rails/generators/test_case.rb b/railties/lib/rails/generators/test_case.rb new file mode 100644 index 0000000000..58592b4f8e --- /dev/null +++ b/railties/lib/rails/generators/test_case.rb @@ -0,0 +1,36 @@ +require 'rails/generators' +require 'rails/generators/testing/behaviour' +require 'rails/generators/testing/setup_and_teardown' +require 'rails/generators/testing/assertions' +require 'fileutils' + +module Rails + module Generators + # Disable color in output. Easier to debug. + no_color! + + # This class provides a TestCase for testing generators. To setup, you need + # just to configure the destination and set which generator is being tested: + # + # class AppGeneratorTest < Rails::Generators::TestCase + # tests AppGenerator + # destination File.expand_path("../tmp", File.dirname(__FILE__)) + # end + # + # If you want to ensure your destination root is clean before running each test, + # you can set a setup callback: + # + # class AppGeneratorTest < Rails::Generators::TestCase + # tests AppGenerator + # destination File.expand_path("../tmp", File.dirname(__FILE__)) + # setup :prepare_destination + # end + class TestCase < ActiveSupport::TestCase + include Rails::Generators::Testing::Behaviour + include Rails::Generators::Testing::SetupAndTeardown + include Rails::Generators::Testing::Assertions + include FileUtils + + end + end +end diff --git a/railties/lib/rails/generators/test_unit.rb b/railties/lib/rails/generators/test_unit.rb new file mode 100644 index 0000000000..fe45c9e15d --- /dev/null +++ b/railties/lib/rails/generators/test_unit.rb @@ -0,0 +1,8 @@ +require 'rails/generators/named_base' + +module TestUnit # :nodoc: + module Generators # :nodoc: + class Base < Rails::Generators::NamedBase # :nodoc: + end + end +end diff --git a/railties/lib/rails/generators/test_unit/controller/controller_generator.rb b/railties/lib/rails/generators/test_unit/controller/controller_generator.rb new file mode 100644 index 0000000000..b5aa581769 --- /dev/null +++ b/railties/lib/rails/generators/test_unit/controller/controller_generator.rb @@ -0,0 +1,15 @@ +require 'rails/generators/test_unit' + +module TestUnit # :nodoc: + module Generators # :nodoc: + class ControllerGenerator < Base # :nodoc: + argument :actions, type: :array, default: [], banner: "action action" + check_class_collision suffix: "ControllerTest" + + def create_test_files + template 'functional_test.rb', + File.join('test/controllers', class_path, "#{file_name}_controller_test.rb") + 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 new file mode 100644 index 0000000000..5a8a3ca5e0 --- /dev/null +++ b/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb @@ -0,0 +1,25 @@ +require 'test_helper' + +<% module_namespacing do -%> +class <%= class_name %>ControllerTest < ActionController::TestCase +<% if mountable_engine? -%> + setup do + @routes = Engine.routes + end + +<% end -%> +<% if actions.empty? -%> + # test "the truth" do + # assert true + # end +<% else -%> +<% actions.each do |action| -%> + test "should get <%= action %>" do + get :<%= action %> + assert_response :success + end + +<% end -%> +<% end -%> +end +<% end -%> diff --git a/railties/lib/rails/generators/test_unit/generator/generator_generator.rb b/railties/lib/rails/generators/test_unit/generator/generator_generator.rb new file mode 100644 index 0000000000..d7307398ce --- /dev/null +++ b/railties/lib/rails/generators/test_unit/generator/generator_generator.rb @@ -0,0 +1,26 @@ +require 'rails/generators/test_unit' + +module TestUnit # :nodoc: + module Generators # :nodoc: + class GeneratorGenerator < Base # :nodoc: + check_class_collision suffix: "GeneratorTest" + + class_option :namespace, type: :boolean, default: true, + desc: "Namespace generator under lib/generators/name" + + def create_generator_files + template 'generator_test.rb', File.join('test/lib/generators', class_path, "#{file_name}_generator_test.rb") + end + + protected + + def generator_path + if options[:namespace] + File.join("generators", regular_class_path, file_name, "#{file_name}_generator") + else + File.join("generators", regular_class_path, "#{file_name}_generator") + end + end + end + end +end diff --git a/railties/lib/rails/generators/test_unit/generator/templates/generator_test.rb b/railties/lib/rails/generators/test_unit/generator/templates/generator_test.rb new file mode 100644 index 0000000000..a7f1fc4fba --- /dev/null +++ b/railties/lib/rails/generators/test_unit/generator/templates/generator_test.rb @@ -0,0 +1,16 @@ +require 'test_helper' +require '<%= generator_path %>' + +<% module_namespacing do -%> +class <%= class_name %>GeneratorTest < Rails::Generators::TestCase + tests <%= class_name %>Generator + destination Rails.root.join('tmp/generators') + setup :prepare_destination + + # test "generator runs without errors" do + # assert_nothing_raised do + # run_generator ["arguments"] + # end + # end +end +<% end -%> diff --git a/railties/lib/rails/generators/test_unit/helper/helper_generator.rb b/railties/lib/rails/generators/test_unit/helper/helper_generator.rb new file mode 100644 index 0000000000..bde4e88915 --- /dev/null +++ b/railties/lib/rails/generators/test_unit/helper/helper_generator.rb @@ -0,0 +1,9 @@ +require 'rails/generators/test_unit' + +module TestUnit # :nodoc: + module Generators # :nodoc: + class HelperGenerator < Base # :nodoc: + # Rails does not generate anything here. + end + end +end diff --git a/railties/lib/rails/generators/test_unit/integration/integration_generator.rb b/railties/lib/rails/generators/test_unit/integration/integration_generator.rb new file mode 100644 index 0000000000..e004835bd5 --- /dev/null +++ b/railties/lib/rails/generators/test_unit/integration/integration_generator.rb @@ -0,0 +1,13 @@ +require 'rails/generators/test_unit' + +module TestUnit # :nodoc: + module Generators # :nodoc: + class IntegrationGenerator < Base # :nodoc: + check_class_collision suffix: "Test" + + def create_test_files + template 'integration_test.rb', File.join('test/integration', class_path, "#{file_name}_test.rb") + end + end + end +end diff --git a/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb b/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb new file mode 100644 index 0000000000..dea7e22196 --- /dev/null +++ b/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class <%= class_name %>Test < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end diff --git a/railties/lib/rails/generators/test_unit/job/job_generator.rb b/railties/lib/rails/generators/test_unit/job/job_generator.rb new file mode 100644 index 0000000000..566b61ca66 --- /dev/null +++ b/railties/lib/rails/generators/test_unit/job/job_generator.rb @@ -0,0 +1,13 @@ +require 'rails/generators/test_unit' + +module TestUnit # :nodoc: + module Generators # :nodoc: + class JobGenerator < Base # :nodoc: + check_class_collision suffix: 'JobTest' + + def create_test_file + template 'unit_test.rb.erb', File.join('test/jobs', class_path, "#{file_name}_job_test.rb") + end + end + end +end diff --git a/railties/lib/rails/generators/test_unit/job/templates/unit_test.rb.erb b/railties/lib/rails/generators/test_unit/job/templates/unit_test.rb.erb new file mode 100644 index 0000000000..f5351d0ec6 --- /dev/null +++ b/railties/lib/rails/generators/test_unit/job/templates/unit_test.rb.erb @@ -0,0 +1,9 @@ +require 'test_helper' + +<% module_namespacing do -%> +class <%= class_name %>JobTest < ActiveJob::TestCase + # test "the truth" do + # assert true + # end +end +<% end -%> diff --git a/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb b/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb new file mode 100644 index 0000000000..343c8a3949 --- /dev/null +++ b/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb @@ -0,0 +1,26 @@ +require 'rails/generators/test_unit' + +module TestUnit # :nodoc: + module Generators # :nodoc: + class MailerGenerator < Base # :nodoc: + argument :actions, type: :array, default: [], banner: "method method" + + def check_class_collision + class_collisions "#{class_name}MailerTest", "#{class_name}MailerPreview" + end + + def create_test_files + template "functional_test.rb", File.join('test/mailers', class_path, "#{file_name}_mailer_test.rb") + end + + def create_preview_files + template "preview.rb", File.join('test/mailers/previews', class_path, "#{file_name}_mailer_preview.rb") + end + + protected + def file_name + @_file_name ||= super.gsub(/\_mailer/i, '') + end + end + end +end diff --git a/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb new file mode 100644 index 0000000000..a2f2d30de5 --- /dev/null +++ b/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb @@ -0,0 +1,21 @@ +require 'test_helper' + +<% module_namespacing do -%> +class <%= class_name %>MailerTest < ActionMailer::TestCase +<% actions.each do |action| -%> + test "<%= action %>" do + mail = <%= class_name %>Mailer.<%= action %> + assert_equal <%= action.to_s.humanize.inspect %>, mail.subject + assert_equal ["to@example.org"], mail.to + assert_equal ["from@example.com"], mail.from + assert_match "Hi", mail.body.encoded + end + +<% end -%> +<% if actions.blank? -%> + # test "the truth" do + # assert true + # end +<% end -%> +end +<% end -%> diff --git a/railties/lib/rails/generators/test_unit/mailer/templates/preview.rb b/railties/lib/rails/generators/test_unit/mailer/templates/preview.rb new file mode 100644 index 0000000000..b063cbc47b --- /dev/null +++ b/railties/lib/rails/generators/test_unit/mailer/templates/preview.rb @@ -0,0 +1,13 @@ +<% module_namespacing do -%> +# Preview all emails at http://localhost:3000/rails/mailers/<%= file_path %>_mailer +class <%= class_name %>MailerPreview < ActionMailer::Preview +<% actions.each do |action| -%> + + # Preview this email at http://localhost:3000/rails/mailers/<%= file_path %>_mailer/<%= action %> + def <%= action %> + <%= class_name %>Mailer.<%= action %> + end +<% end -%> + +end +<% end -%> diff --git a/railties/lib/rails/generators/test_unit/model/model_generator.rb b/railties/lib/rails/generators/test_unit/model/model_generator.rb new file mode 100644 index 0000000000..086588750e --- /dev/null +++ b/railties/lib/rails/generators/test_unit/model/model_generator.rb @@ -0,0 +1,36 @@ +require 'rails/generators/test_unit' + +module TestUnit # :nodoc: + module Generators # :nodoc: + class ModelGenerator < Base # :nodoc: + + RESERVED_YAML_KEYWORDS = %w(y yes n no true false on off null) + + argument :attributes, type: :array, default: [], banner: "field:type field:type" + class_option :fixture, type: :boolean + + check_class_collision suffix: "Test" + + def create_test_file + template 'unit_test.rb', File.join('test/models', class_path, "#{file_name}_test.rb") + end + + hook_for :fixture_replacement + + def create_fixture_file + if options[:fixture] && options[:fixture_replacement].nil? + template 'fixtures.yml', File.join('test/fixtures', class_path, "#{fixture_file_name}.yml") + end + end + + private + def yaml_key_value(key, value) + if RESERVED_YAML_KEYWORDS.include?(key.downcase) + "'#{key}': #{value}" + else + "#{key}: #{value}" + end + end + end + end +end diff --git a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml new file mode 100644 index 0000000000..50ca61a35b --- /dev/null +++ b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml @@ -0,0 +1,29 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html +<% unless attributes.empty? -%> +<% %w(one two).each do |name| %> +<%= name %>: +<% attributes.each do |attribute| -%> + <%- if attribute.password_digest? -%> + password_digest: <%%= BCrypt::Password.create('secret') %> + <%- elsif attribute.reference? -%> + <%= yaml_key_value(attribute.column_name.sub(/_id$/, ''), attribute.default) %> + <%- else -%> + <%= yaml_key_value(attribute.column_name, attribute.default) %> + <%- end -%> + <%- if attribute.polymorphic? -%> + <%= yaml_key_value("#{attribute.name}_type", attribute.human_name) %> + <%- end -%> +<% end -%> +<% end -%> +<% else -%> + +# This model initially had no columns defined. If you add columns to the +# model remove the '{}' from the fixture names and add the columns immediately +# below each fixture, per the syntax in the comments below +# +one: {} +# column: value +# +two: {} +# column: value +<% end -%> diff --git a/railties/lib/rails/generators/test_unit/model/templates/unit_test.rb b/railties/lib/rails/generators/test_unit/model/templates/unit_test.rb new file mode 100644 index 0000000000..c9bc7d5b90 --- /dev/null +++ b/railties/lib/rails/generators/test_unit/model/templates/unit_test.rb @@ -0,0 +1,9 @@ +require 'test_helper' + +<% module_namespacing do -%> +class <%= class_name %>Test < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end +<% end -%> diff --git a/railties/lib/rails/generators/test_unit/plugin/plugin_generator.rb b/railties/lib/rails/generators/test_unit/plugin/plugin_generator.rb new file mode 100644 index 0000000000..b5d4f38444 --- /dev/null +++ b/railties/lib/rails/generators/test_unit/plugin/plugin_generator.rb @@ -0,0 +1,13 @@ +require 'rails/generators/test_unit' + +module TestUnit # :nodoc: + module Generators # :nodoc: + class PluginGenerator < Base # :nodoc: + check_class_collision suffix: "Test" + + def create_test_files + directory '.', 'test' + end + end + end +end diff --git a/railties/lib/rails/generators/test_unit/plugin/templates/%file_name%_test.rb.tt b/railties/lib/rails/generators/test_unit/plugin/templates/%file_name%_test.rb.tt new file mode 100644 index 0000000000..0cbae1120e --- /dev/null +++ b/railties/lib/rails/generators/test_unit/plugin/templates/%file_name%_test.rb.tt @@ -0,0 +1,7 @@ +require 'test_helper' + +class <%= class_name %>Test < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb b/railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb new file mode 100644 index 0000000000..30a861f09d --- /dev/null +++ b/railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb @@ -0,0 +1,2 @@ +require 'active_support/testing/autorun' +require 'active_support' diff --git a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb new file mode 100644 index 0000000000..0171da7cc7 --- /dev/null +++ b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb @@ -0,0 +1,46 @@ +require 'rails/generators/test_unit' +require 'rails/generators/resource_helpers' + +module TestUnit # :nodoc: + module Generators # :nodoc: + class ScaffoldGenerator < Base # :nodoc: + include Rails::Generators::ResourceHelpers + + 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_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 mountable_engine? + "%s_%s" % [namespaced_path, table_name] + else + table_name + end + end + + private + + def attributes_hash + return if attributes_names.empty? + + attributes_names.map do |name| + if %w(password password_confirmation).include?(name) && attributes.any?(&:password_digest?) + "#{name}: 'secret'" + else + "#{name}: @#{singular_table_name}.#{name}" + end + end.sort.join(', ') + end + end + end +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..f302cd6c3d --- /dev/null +++ b/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb @@ -0,0 +1,43 @@ +require 'test_helper' + +<% module_namespacing do -%> +class <%= controller_class_name %>ControllerTest < ActionController::TestCase + setup do + @<%= singular_table_name %> = <%= fixture_name %>(:one) +<% if mountable_engine? -%> + @routes = Engine.routes +<% end -%> + 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 new file mode 100644 index 0000000000..50b98b2631 --- /dev/null +++ b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb @@ -0,0 +1,53 @@ +require 'test_helper' + +<% module_namespacing do -%> +class <%= controller_class_name %>ControllerTest < ActionController::TestCase + setup do + @<%= singular_table_name %> = <%= fixture_name %>(:one) +<% if mountable_engine? -%> + @routes = Engine.routes +<% end -%> + end + + test "should get index" do + get :index + assert_response :success + end + + test "should get new" do + get :new + 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_redirected_to <%= singular_table_name %>_path(<%= class_name %>.last) + end + + test "should show <%= singular_table_name %>" do + get :show, params: { id: <%= "@#{singular_table_name}" %> } + assert_response :success + end + + test "should get edit" do + get :edit, 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_redirected_to <%= singular_table_name %>_path(<%= "@#{singular_table_name}" %>) + end + + test "should destroy <%= singular_table_name %>" do + assert_difference('<%= class_name %>.count', -1) do + delete :destroy, params: { id: <%= "@#{singular_table_name}" %> } + end + + assert_redirected_to <%= index_helper %>_path + end +end +<% end -%> diff --git a/railties/lib/rails/generators/testing/assertions.rb b/railties/lib/rails/generators/testing/assertions.rb new file mode 100644 index 0000000000..76758df86d --- /dev/null +++ b/railties/lib/rails/generators/testing/assertions.rb @@ -0,0 +1,121 @@ +module Rails + module Generators + module Testing + module Assertions + # Asserts a given file exists. You need to supply an absolute path or a path relative + # to the configured destination: + # + # assert_file "config/environment.rb" + # + # You can also give extra arguments. If the argument is a regexp, it will check if the + # regular expression matches the given file content. If it's a string, it compares the + # file with the given string: + # + # assert_file "config/environment.rb", /initialize/ + # + # Finally, when a block is given, it yields the file content: + # + # assert_file "app/controllers/products_controller.rb" do |controller| + # assert_instance_method :index, controller do |index| + # assert_match(/Product\.all/, index) + # end + # end + def assert_file(relative, *contents) + 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? + yield read if block_given? + + contents.each do |content| + case content + when String + assert_equal content, read + when Regexp + assert_match content, read + end + end + end + alias :assert_directory :assert_file + + # Asserts a given file does not exist. You need to supply an absolute path or a + # path relative to the configured destination: + # + # assert_no_file "config/random.rb" + def assert_no_file(relative) + absolute = File.expand_path(relative, destination_root) + assert !File.exist?(absolute), "Expected file #{relative.inspect} to not exist, but does" + end + alias :assert_no_directory :assert_no_file + + # Asserts a given migration exists. You need to supply an absolute path or a + # path relative to the configured destination: + # + # assert_migration "db/migrate/create_products.rb" + # + # This method manipulates the given path and tries to find any migration which + # matches the migration name. For example, the call above is converted to: + # + # assert_file "db/migrate/003_create_products.rb" + # + # Consequently, assert_migration accepts the same arguments has assert_file. + def assert_migration(relative, *contents, &block) + file_name = migration_file_name(relative) + assert file_name, "Expected migration #{relative} to exist, but was not found" + assert_file file_name, *contents, &block + end + + # Asserts a given migration does not exist. You need to supply an absolute path or a + # path relative to the configured destination: + # + # assert_no_migration "db/migrate/create_products.rb" + def assert_no_migration(relative) + file_name = migration_file_name(relative) + assert_nil file_name, "Expected migration #{relative} to not exist, but found #{file_name}" + end + + # Asserts the given class method exists in the given content. This method does not detect + # class methods inside (class << self), only class methods which starts with "self.". + # When a block is given, it yields the content of the method. + # + # assert_migration "db/migrate/create_products.rb" do |migration| + # assert_class_method :up, migration do |up| + # assert_match(/create_table/, up) + # end + # end + def assert_class_method(method, content, &block) + assert_instance_method "self.#{method}", content, &block + end + + # Asserts the given method exists in the given content. When a block is given, + # it yields the content of the method. + # + # assert_file "app/controllers/products_controller.rb" do |controller| + # assert_instance_method :index, controller do |index| + # assert_match(/Product\.all/, index) + # end + # end + def assert_instance_method(method, content) + assert content =~ /(\s+)def #{method}(\(.+\))?(.*?)\n\1end/m, "Expected to have method #{method}" + yield $3.strip if block_given? + end + alias :assert_method :assert_instance_method + + # Asserts the given attribute type gets translated to a field type + # properly: + # + # assert_field_type :date, :date_select + def assert_field_type(attribute_type, field_type) + assert_equal(field_type, create_generated_attribute(attribute_type).field_type) + end + + # Asserts the given attribute type gets a proper default value: + # + # assert_field_default_value :string, "MyString" + def assert_field_default_value(attribute_type, value) + assert_equal(value, create_generated_attribute(attribute_type).default) + end + end + end + end +end diff --git a/railties/lib/rails/generators/testing/behaviour.rb b/railties/lib/rails/generators/testing/behaviour.rb new file mode 100644 index 0000000000..94b5e52224 --- /dev/null +++ b/railties/lib/rails/generators/testing/behaviour.rb @@ -0,0 +1,110 @@ +require 'active_support/core_ext/class/attribute' +require 'active_support/core_ext/module/delegation' +require 'active_support/core_ext/hash/reverse_merge' +require 'active_support/core_ext/kernel/reporting' +require 'active_support/testing/stream' +require 'active_support/concern' +require 'rails/generators' + +module Rails + module Generators + module Testing + module Behaviour + extend ActiveSupport::Concern + include ActiveSupport::Testing::Stream + + included do + class_attribute :destination_root, :current_path, :generator_class, :default_arguments + + # Generators frequently change the current path using +FileUtils.cd+. + # So we need to store the path at file load and revert back to it after each test. + self.current_path = File.expand_path(Dir.pwd) + self.default_arguments = [] + end + + module ClassMethods + # Sets which generator should be tested: + # + # tests AppGenerator + def tests(klass) + self.generator_class = klass + end + + # Sets default arguments on generator invocation. This can be overwritten when + # invoking it. + # + # arguments %w(app_name --skip-active-record) + def arguments(array) + self.default_arguments = array + end + + # Sets the destination of generator files: + # + # destination File.expand_path("../tmp", File.dirname(__FILE__)) + def destination(path) + self.destination_root = path + end + end + + # Runs the generator configured for this class. The first argument is an array like + # command line arguments: + # + # class AppGeneratorTest < Rails::Generators::TestCase + # tests AppGenerator + # destination File.expand_path("../tmp", File.dirname(__FILE__)) + # setup :prepare_destination + # + # test "database.yml is not created when skipping Active Record" do + # run_generator %w(myapp --skip-active-record) + # assert_no_file "config/database.yml" + # end + # end + # + # You can provide a configuration hash as second argument. This method returns the output + # printed by the generator. + def run_generator(args=self.default_arguments, config={}) + capture(:stdout) do + args += ['--skip-bundle'] unless args.include? '--dev' + self.generator_class.start(args, config.reverse_merge(destination_root: destination_root)) + end + end + + # Instantiate the generator. + def generator(args=self.default_arguments, options={}, config={}) + @generator ||= self.generator_class.new(args, options, config.reverse_merge(destination_root: destination_root)) + end + + # Create a Rails::Generators::GeneratedAttribute by supplying the + # attribute type and, optionally, the attribute name: + # + # create_generated_attribute(:string, 'name') + def create_generated_attribute(attribute_type, name = 'test', index = nil) + Rails::Generators::GeneratedAttribute.parse([name, attribute_type, index].compact.join(':')) + end + + protected + + def destination_root_is_set? # :nodoc: + raise "You need to configure your Rails::Generators::TestCase destination root." unless destination_root + end + + def ensure_current_path # :nodoc: + cd current_path + end + + # Clears all files and directories in destination. + def prepare_destination + rm_rf(destination_root) + mkdir_p(destination_root) + end + + def migration_file_name(relative) # :nodoc: + absolute = File.expand_path(relative, destination_root) + dirname, file_name = File.dirname(absolute), File.basename(absolute).sub(/\.rb$/, '') + Dir.glob("#{dirname}/[0-9]*_*.rb").grep(/\d+_#{file_name}.rb$/).first + end + + end + end + end +end diff --git a/railties/lib/rails/generators/testing/setup_and_teardown.rb b/railties/lib/rails/generators/testing/setup_and_teardown.rb new file mode 100644 index 0000000000..73102a283f --- /dev/null +++ b/railties/lib/rails/generators/testing/setup_and_teardown.rb @@ -0,0 +1,18 @@ +module Rails + module Generators + module Testing + module SetupAndTeardown + def setup # :nodoc: + destination_root_is_set? + ensure_current_path + super + end + + def teardown # :nodoc: + ensure_current_path + super + end + end + end + end +end diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb new file mode 100644 index 0000000000..5909446b66 --- /dev/null +++ b/railties/lib/rails/info.rb @@ -0,0 +1,102 @@ +require "cgi" + +module Rails + # This module helps build the runtime properties used to display in the + # Rails::InfoController responses. Including the active Rails version, Ruby + # version, Rack version, and so on. + module Info + mattr_accessor :properties + class << (@@properties = []) + def names + map(&:first) + end + + def value_for(property_name) + if property = assoc(property_name) + property.last + end + end + end + + class << self #:nodoc: + def property(name, value = nil) + value ||= yield + properties << [name, value] if value + rescue Exception + end + + def to_s + column_width = properties.names.map(&:length).max + info = properties.map do |name, value| + value = value.join(", ") if value.is_a?(Array) + "%-#{column_width}s %s" % [name, value] + end + info.unshift "About your application's environment" + info * "\n" + end + + alias inspect to_s + + def to_html + '<table>'.tap do |table| + properties.each do |(name, value)| + table << %(<tr><td class="name">#{CGI.escapeHTML(name.to_s)}</td>) + formatted_value = if value.kind_of?(Array) + "<ul>" + value.map { |v| "<li>#{CGI.escapeHTML(v.to_s)}</li>" }.join + "</ul>" + else + CGI.escapeHTML(value.to_s) + end + table << %(<td class="value">#{formatted_value}</td></tr>) + end + table << '</table>' + end + end + end + + # The Rails version. + property 'Rails version' do + Rails.version.to_s + end + + # The Ruby version and platform, e.g. "2.0.0-p247 (x86_64-darwin12.4.0)". + property 'Ruby version' do + "#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL} (#{RUBY_PLATFORM})" + end + + # The RubyGems version, if it's installed. + property 'RubyGems version' do + Gem::RubyGemsVersion + end + + property 'Rack version' do + ::Rack.release + end + + property 'JavaScript Runtime' do + ExecJS.runtime.name + end + + property 'Middleware' do + Rails.configuration.middleware.map(&:inspect) + end + + # The application's location on the filesystem. + property 'Application root' do + File.expand_path(Rails.root) + end + + # The current Rails environment (development, test, or production). + property 'Environment' do + Rails.env + end + + # The name of the database adapter for the current environment. + property 'Database adapter' do + ActiveRecord::Base.configurations[Rails.env]['adapter'] + end + + property 'Database schema version' do + ActiveRecord::Migrator.current_version rescue nil + end + end +end diff --git a/railties/lib/rails/info_controller.rb b/railties/lib/rails/info_controller.rb new file mode 100644 index 0000000000..778105c5f7 --- /dev/null +++ b/railties/lib/rails/info_controller.rb @@ -0,0 +1,44 @@ +require 'rails/application_controller' +require 'action_dispatch/routing/inspector' + +class Rails::InfoController < Rails::ApplicationController # :nodoc: + prepend_view_path ActionDispatch::DebugExceptions::RESCUES_TEMPLATE_PATH + layout -> { request.xhr? ? false : 'application' } + + before_action :require_local! + + def index + redirect_to action: :routes + end + + def properties + @info = Rails::Info.to_html + @page_title = 'Properties' + end + + def routes + if path = params[:path] + path = URI.parser.escape path + normalized_path = with_leading_slash path + render json: { + exact: match_route {|it| it.match normalized_path }, + fuzzy: match_route {|it| it.spec.to_s.match path } + } + else + @routes_inspector = ActionDispatch::Routing::RoutesInspector.new(_routes.routes) + @page_title = 'Routes' + end + end + + private + + def match_route + _routes.routes.select {|route| + yield route.path + }.map {|route| route.path.spec.to_s } + end + + def with_leading_slash(path) + ('/' + path).squeeze('/') + end +end diff --git a/railties/lib/rails/initializable.rb b/railties/lib/rails/initializable.rb new file mode 100644 index 0000000000..1a0b6d1e1a --- /dev/null +++ b/railties/lib/rails/initializable.rb @@ -0,0 +1,89 @@ +require 'tsort' + +module Rails + module Initializable + def self.included(base) #:nodoc: + base.extend ClassMethods + end + + class Initializer + attr_reader :name, :block + + def initialize(name, context, options, &block) + options[:group] ||= :default + @name, @context, @options, @block = name, context, options, block + end + + def before + @options[:before] + end + + def after + @options[:after] + end + + def belongs_to?(group) + @options[:group] == group || @options[:group] == :all + end + + def run(*args) + @context.instance_exec(*args, &block) + end + + def bind(context) + return self if @context + Initializer.new(@name, context, @options, &block) + end + end + + class Collection < Array + include TSort + + alias :tsort_each_node :each + def tsort_each_child(initializer, &block) + select { |i| i.before == initializer.name || i.name == initializer.after }.each(&block) + end + + def +(other) + Collection.new(to_a + other.to_a) + end + end + + def run_initializers(group=:default, *args) + return if instance_variable_defined?(:@ran) + initializers.tsort_each do |initializer| + initializer.run(*args) if initializer.belongs_to?(group) + end + @ran = true + end + + def initializers + @initializers ||= self.class.initializers_for(self) + end + + module ClassMethods + def initializers + @initializers ||= Collection.new + end + + def initializers_chain + initializers = Collection.new + ancestors.reverse_each do |klass| + next unless klass.respond_to?(:initializers) + initializers = initializers + klass.initializers + end + initializers + end + + def initializers_for(binding) + Collection.new(initializers_chain.map { |i| i.bind(binding) }) + end + + def initializer(name, opts = {}, &blk) + raise ArgumentError, "A block must be passed when defining an initializer" unless blk + opts[:after] ||= initializers.last.name unless initializers.empty? || initializers.find { |i| i.name == opts[:before] } + initializers << Initializer.new(name, nil, opts, &blk) + end + end + end +end diff --git a/railties/lib/rails/mailers_controller.rb b/railties/lib/rails/mailers_controller.rb new file mode 100644 index 0000000000..6143cf2dd9 --- /dev/null +++ b/railties/lib/rails/mailers_controller.rb @@ -0,0 +1,79 @@ +require 'rails/application_controller' + +class Rails::MailersController < Rails::ApplicationController # :nodoc: + prepend_view_path ActionDispatch::DebugExceptions::RESCUES_TEMPLATE_PATH + + before_action :require_local!, unless: :show_previews? + before_action :find_preview, only: :preview + + def index + @previews = ActionMailer::Preview.all + @page_title = "Mailer Previews" + end + + def preview + if params[:path] == @preview.preview_name + @page_title = "Mailer Previews for #{@preview.preview_name}" + render action: 'mailer' + else + @email_action = File.basename(params[:path]) + + if @preview.email_exists?(@email_action) + @email = @preview.call(@email_action) + + if params[:part] + part_type = Mime::Type.lookup(params[:part]) + + if part = find_part(part_type) + response.content_type = part_type + render plain: part.respond_to?(:decoded) ? part.decoded : part + else + raise AbstractController::ActionNotFound, "Email part '#{part_type}' not found in #{@preview.name}##{@email_action}" + end + else + @part = find_preferred_part(request.format, Mime[:html], Mime[:text]) + render action: 'email', layout: false, formats: %w[html] + end + else + raise AbstractController::ActionNotFound, "Email '#{@email_action}' not found in #{@preview.name}" + end + end + end + + protected + def show_previews? + ActionMailer::Base.show_previews + end + + def find_preview + candidates = [] + params[:path].to_s.scan(%r{/|$}){ candidates << $` } + preview = candidates.detect{ |candidate| ActionMailer::Preview.exists?(candidate) } + + if preview + @preview = ActionMailer::Preview.find(preview) + else + raise AbstractController::ActionNotFound, "Mailer preview '#{params[:path]}' not found" + end + end + + def find_preferred_part(*formats) + formats.each do |format| + if part = @email.find_first_mime_type(format) + return part + end + end + + if formats.any?{ |f| @email.mime_type == f } + @email + end + end + + def find_part(format) + if part = @email.find_first_mime_type(format) + part + elsif @email.mime_type == format + @email + end + end +end diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb new file mode 100644 index 0000000000..e47616a87f --- /dev/null +++ b/railties/lib/rails/paths.rb @@ -0,0 +1,219 @@ +module Rails + module Paths + # This object is an extended hash that behaves as root of the <tt>Rails::Paths</tt> system. + # It allows you to collect information about how you want to structure your application + # paths by a Hash like API. It requires you to give a physical path on initialization. + # + # root = Root.new "/rails" + # root.add "app/controllers", eager_load: true + # + # The command above creates a new root object and adds "app/controllers" as a path. + # This means we can get a <tt>Rails::Paths::Path</tt> object back like below: + # + # path = root["app/controllers"] + # path.eager_load? # => true + # path.is_a?(Rails::Paths::Path) # => true + # + # The +Path+ object is simply an enumerable and allows you to easily add extra paths: + # + # path.is_a?(Enumerable) # => true + # path.to_ary.inspect # => ["app/controllers"] + # + # path << "lib/controllers" + # path.to_ary.inspect # => ["app/controllers", "lib/controllers"] + # + # Notice that when you add a path using +add+, the path object created already + # contains the path with the same path value given to +add+. In some situations, + # you may not want this behavior, so you can give <tt>:with</tt> as option. + # + # root.add "config/routes", with: "config/routes.rb" + # root["config/routes"].inspect # => ["config/routes.rb"] + # + # The +add+ method accepts the following options as arguments: + # eager_load, autoload, autoload_once and glob. + # + # Finally, the +Path+ object also provides a few helpers: + # + # root = Root.new "/rails" + # root.add "app/controllers" + # + # root["app/controllers"].expanded # => ["/rails/app/controllers"] + # root["app/controllers"].existent # => ["/rails/app/controllers"] + # + # Check the <tt>Rails::Paths::Path</tt> documentation for more information. + class Root + attr_accessor :path + + def initialize(path) + @current = nil + @path = path + @root = {} + end + + def []=(path, value) + glob = self[path] ? self[path].glob : nil + add(path, with: value, glob: glob) + end + + def add(path, options = {}) + with = Array(options.fetch(:with, path)) + @root[path] = Path.new(self, path, with, options) + end + + def [](path) + @root[path] + end + + def values + @root.values + end + + def keys + @root.keys + end + + def values_at(*list) + @root.values_at(*list) + end + + def all_paths + values.tap(&:uniq!) + end + + def autoload_once + filter_by(&:autoload_once?) + end + + def eager_load + filter_by(&:eager_load?) + end + + def autoload_paths + filter_by(&:autoload?) + end + + def load_paths + filter_by(&:load_path?) + end + + private + + def filter_by(&block) + all_paths.find_all(&block).flat_map { |path| + paths = path.existent + paths - path.children.flat_map { |p| yield(p) ? [] : p.existent } + }.uniq + end + end + + class Path + include Enumerable + + attr_accessor :glob + + def initialize(root, current, paths, options = {}) + @paths = paths + @current = current + @root = root + @glob = options[:glob] + + options[:autoload_once] ? autoload_once! : skip_autoload_once! + options[:eager_load] ? eager_load! : skip_eager_load! + options[:autoload] ? autoload! : skip_autoload! + options[:load_path] ? load_path! : skip_load_path! + end + + def absolute_current # :nodoc: + File.expand_path(@current, @root.path) + end + + def children + keys = @root.keys.find_all { |k| + k.start_with?(@current) && k != @current + } + @root.values_at(*keys.sort) + end + + def first + expanded.first + end + + def last + expanded.last + end + + %w(autoload_once eager_load autoload load_path).each do |m| + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{m}! # def eager_load! + @#{m} = true # @eager_load = true + end # end + # + def skip_#{m}! # def skip_eager_load! + @#{m} = false # @eager_load = false + end # end + # + def #{m}? # def eager_load? + @#{m} # @eager_load + end # end + RUBY + end + + def each(&block) + @paths.each(&block) + end + + def <<(path) + @paths << path + end + alias :push :<< + + def concat(paths) + @paths.concat paths + end + + def unshift(*paths) + @paths.unshift(*paths) + end + + def to_ary + @paths + end + + def extensions # :nodoc: + $1.split(',') if @glob =~ /\{([\S]+)\}/ + end + + # Expands all paths against the root and return all unique values. + def expanded + raise "You need to set a path root" unless @root.path + result = [] + + each do |p| + path = File.expand_path(p, @root.path) + + if @glob && File.directory?(path) + Dir.chdir(path) do + result.concat(Dir.glob(@glob).map { |file| File.join path, file }.sort) + end + else + result << path + end + end + + result.uniq! + result + end + + # Returns all expanded paths but only if they exist in the filesystem. + def existent + expanded.select { |f| File.exist?(f) } + end + + def existent_directories + expanded.select { |d| File.directory?(d) } + end + + alias to_a expanded + end + end +end diff --git a/railties/lib/rails/rack.rb b/railties/lib/rails/rack.rb new file mode 100644 index 0000000000..a4c4527a72 --- /dev/null +++ b/railties/lib/rails/rack.rb @@ -0,0 +1,5 @@ +module Rails + module Rack + autoload :Logger, "rails/rack/logger" + end +end diff --git a/railties/lib/rails/rack/debugger.rb b/railties/lib/rails/rack/debugger.rb new file mode 100644 index 0000000000..1fde3db070 --- /dev/null +++ b/railties/lib/rails/rack/debugger.rb @@ -0,0 +1,3 @@ +require 'active_support/deprecation' + +ActiveSupport::Deprecation.warn("This file is deprecated and will be removed in Rails 5.1 with no replacement.") diff --git a/railties/lib/rails/rack/logger.rb b/railties/lib/rails/rack/logger.rb new file mode 100644 index 0000000000..12676b18bc --- /dev/null +++ b/railties/lib/rails/rack/logger.rb @@ -0,0 +1,90 @@ +require 'active_support/core_ext/time/conversions' +require 'active_support/core_ext/object/blank' +require 'active_support/log_subscriber' +require 'action_dispatch/http/request' +require 'rack/body_proxy' + +module Rails + module Rack + # Sets log tags, logs the request, calls the app, and flushes the logs. + # + # Log tags (+taggers+) can be an Array containing: methods that the +request+ + # object responds to, objects that respond to +to_s+ or Proc objects that accept + # an instance of the +request+ object. + class Logger < ActiveSupport::LogSubscriber + def initialize(app, taggers = nil) + @app = app + @taggers = taggers || [] + end + + def call(env) + request = ActionDispatch::Request.new(env) + + if logger.respond_to?(:tagged) + logger.tagged(compute_tags(request)) { call_app(request, env) } + else + call_app(request, env) + end + end + + protected + + def call_app(request, env) + # Put some space between requests in development logs. + if development? + logger.debug '' + logger.debug '' + end + + instrumenter = ActiveSupport::Notifications.instrumenter + instrumenter.start 'request.action_dispatch', request: request + logger.info { started_request_message(request) } + resp = @app.call(env) + resp[2] = ::Rack::BodyProxy.new(resp[2]) { finish(request) } + resp + rescue Exception + finish(request) + raise + ensure + ActiveSupport::LogSubscriber.flush_all! + end + + # Started GET "/session/new" for 127.0.0.1 at 2012-09-26 14:51:42 -0700 + def started_request_message(request) + 'Started %s "%s" for %s at %s' % [ + request.request_method, + request.filtered_path, + request.ip, + Time.now.to_default_s ] + end + + def compute_tags(request) + @taggers.collect do |tag| + case tag + when Proc + tag.call(request) + when Symbol + request.send(tag) + else + tag + end + end + end + + private + + def finish(request) + instrumenter = ActiveSupport::Notifications.instrumenter + instrumenter.finish 'request.action_dispatch', request: request + end + + def development? + Rails.env.development? + end + + def logger + Rails.logger + end + end + end +end diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb new file mode 100644 index 0000000000..8c24d1d56d --- /dev/null +++ b/railties/lib/rails/railtie.rb @@ -0,0 +1,250 @@ +require 'rails/initializable' +require 'rails/configuration' +require 'active_support/inflector' +require 'active_support/core_ext/module/introspection' +require 'active_support/core_ext/module/delegation' + +module Rails + # Railtie is the core of the Rails framework and provides several hooks to extend + # Rails and/or modify the initialization process. + # + # Every major component of Rails (Action Mailer, Action Controller, + # Action View and Active Record) is a Railtie. Each of + # them is responsible for their own initialization. This makes Rails itself + # absent of any component hooks, allowing other components to be used in + # place of any of the Rails defaults. + # + # Developing a Rails extension does _not_ require any implementation of + # Railtie, but if you need to interact with the Rails framework during + # or after boot, then Railtie is needed. + # + # For example, an extension doing any of the following would require Railtie: + # + # * creating initializers + # * configuring a Rails framework for the application, like setting a generator + # * adding <tt>config.*</tt> keys to the environment + # * setting up a subscriber with ActiveSupport::Notifications + # * adding rake tasks + # + # == Creating your Railtie + # + # To extend Rails using Railtie, create a Railtie class which inherits + # from Rails::Railtie within your extension's namespace. This class must be + # loaded during the Rails boot process. + # + # The following example demonstrates an extension which can be used with or without Rails. + # + # # lib/my_gem/railtie.rb + # module MyGem + # class Railtie < Rails::Railtie + # end + # end + # + # # lib/my_gem.rb + # require 'my_gem/railtie' if defined?(Rails) + # + # == Initializers + # + # To add an initialization step from your Railtie to Rails boot process, you just need + # to create an initializer block: + # + # class MyRailtie < Rails::Railtie + # initializer "my_railtie.configure_rails_initialization" do + # # some initialization behavior + # end + # end + # + # If specified, the block can also receive the application object, in case you + # need to access some application specific configuration, like middleware: + # + # class MyRailtie < Rails::Railtie + # initializer "my_railtie.configure_rails_initialization" do |app| + # app.middleware.use MyRailtie::Middleware + # end + # end + # + # Finally, you can also pass <tt>:before</tt> and <tt>:after</tt> as option to initializer, + # in case you want to couple it with a specific step in the initialization process. + # + # == Configuration + # + # Inside the Railtie class, you can access a config object which contains configuration + # shared by all railties and the application: + # + # class MyRailtie < Rails::Railtie + # # Customize the ORM + # config.app_generators.orm :my_railtie_orm + # + # # Add a to_prepare block which is executed once in production + # # and before each request in development + # config.to_prepare do + # MyRailtie.setup! + # end + # end + # + # == Loading rake tasks and generators + # + # If your railtie has rake tasks, you can tell Rails to load them through the method + # rake_tasks: + # + # class MyRailtie < Rails::Railtie + # rake_tasks do + # load "path/to/my_railtie.tasks" + # end + # end + # + # By default, Rails loads generators from your load path. However, if you want to place + # your generators at a different location, you can specify in your Railtie a block which + # will load them during normal generators lookup: + # + # class MyRailtie < Rails::Railtie + # generators do + # require "path/to/my_railtie_generator" + # end + # end + # + # == Application and Engine + # + # A Rails::Engine is nothing more than a Railtie with some initializers already set. + # And since Rails::Application is an engine, the same configuration described here + # can be used in both. + # + # Be sure to look at the documentation of those specific classes for more information. + # + class Railtie + autoload :Configuration, "rails/railtie/configuration" + + include Initializable + + ABSTRACT_RAILTIES = %w(Rails::Railtie Rails::Engine Rails::Application) + + class << self + private :new + delegate :config, to: :instance + + def subclasses + @subclasses ||= [] + end + + def inherited(base) + unless base.abstract_railtie? + subclasses << base + end + end + + def rake_tasks(&blk) + @rake_tasks ||= [] + @rake_tasks << blk if blk + @rake_tasks + end + + def console(&blk) + @load_console ||= [] + @load_console << blk if blk + @load_console + end + + def runner(&blk) + @load_runner ||= [] + @load_runner << blk if blk + @load_runner + end + + def generators(&blk) + @generators ||= [] + @generators << blk if blk + @generators + end + + def abstract_railtie? + ABSTRACT_RAILTIES.include?(name) + end + + def railtie_name(name = nil) + @railtie_name = name.to_s if name + @railtie_name ||= generate_railtie_name(self.name) + end + + # Since Rails::Railtie cannot be instantiated, any methods that call + # +instance+ are intended to be called only on subclasses of a Railtie. + def instance + @instance ||= new + end + + def respond_to_missing?(*args) + instance.respond_to?(*args) || super + end + + # Allows you to configure the railtie. This is the same method seen in + # Railtie::Configurable, but this module is no longer required for all + # subclasses of Railtie so we provide the class method here. + def configure(&block) + instance.configure(&block) + end + + protected + def generate_railtie_name(string) + ActiveSupport::Inflector.underscore(string).tr("/", "_") + end + + # If the class method does not have a method, then send the method call + # to the Railtie instance. + def method_missing(name, *args, &block) + if instance.respond_to?(name) + instance.public_send(name, *args, &block) + else + super + end + end + end + + delegate :railtie_name, to: :class + + def initialize + if self.class.abstract_railtie? + raise "#{self.class.name} is abstract, you cannot instantiate it directly." + end + end + + def configure(&block) + instance_eval(&block) + end + + def config + @config ||= Railtie::Configuration.new + end + + def railtie_namespace + @railtie_namespace ||= self.class.parents.detect { |n| n.respond_to?(:railtie_namespace) } + end + + protected + + def run_console_blocks(app) #:nodoc: + each_registered_block(:console) { |block| block.call(app) } + end + + def run_generators_blocks(app) #:nodoc: + each_registered_block(:generators) { |block| block.call(app) } + end + + def run_runner_blocks(app) #:nodoc: + each_registered_block(:runner) { |block| block.call(app) } + end + + def run_tasks_blocks(app) #:nodoc: + extend Rake::DSL + each_registered_block(:rake_tasks) { |block| instance_exec(app, &block) } + end + + private + + def each_registered_block(type, &block) + klass = self.class + while klass.respond_to?(type) + klass.public_send(type).each(&block) + klass = klass.superclass + end + end + end +end diff --git a/railties/lib/rails/railtie/configurable.rb b/railties/lib/rails/railtie/configurable.rb new file mode 100644 index 0000000000..1572af0b2a --- /dev/null +++ b/railties/lib/rails/railtie/configurable.rb @@ -0,0 +1,35 @@ +require 'active_support/concern' + +module Rails + class Railtie + module Configurable + extend ActiveSupport::Concern + + module ClassMethods + delegate :config, to: :instance + + def inherited(base) + raise "You cannot inherit from a #{self.superclass.name} child" + end + + def instance + @instance ||= new + end + + def respond_to?(*args) + super || instance.respond_to?(*args) + end + + def configure(&block) + class_eval(&block) + end + + protected + + def method_missing(*args, &block) + instance.send(*args, &block) + end + end + end + end +end diff --git a/railties/lib/rails/railtie/configuration.rb b/railties/lib/rails/railtie/configuration.rb new file mode 100644 index 0000000000..eb3b2d8ef4 --- /dev/null +++ b/railties/lib/rails/railtie/configuration.rb @@ -0,0 +1,100 @@ +require 'rails/configuration' + +module Rails + class Railtie + class Configuration + def initialize + @@options ||= {} + end + + # Expose the eager_load_namespaces at "module" level for convenience. + def self.eager_load_namespaces #:nodoc: + @@eager_load_namespaces ||= [] + end + + # All namespaces that are eager loaded + def eager_load_namespaces + @@eager_load_namespaces ||= [] + end + + # Add files that should be watched for change. + def watchable_files + @@watchable_files ||= [] + end + + # Add directories that should be watched for change. + # The key of the hashes should be directories and the values should + # be an array of extensions to match in each directory. + def watchable_dirs + @@watchable_dirs ||= {} + end + + # This allows you to modify the application's middlewares from Engines. + # + # All operations you run on the app_middleware will be replayed on the + # application once it is defined and the default_middlewares are + # created + def app_middleware + @@app_middleware ||= Rails::Configuration::MiddlewareStackProxy.new + end + + # This allows you to modify application's generators from Railties. + # + # Values set on app_generators will become defaults for application, unless + # application overwrites them. + def app_generators + @@app_generators ||= Rails::Configuration::Generators.new + yield(@@app_generators) if block_given? + @@app_generators + end + + # First configurable block to run. Called before any initializers are run. + def before_configuration(&block) + ActiveSupport.on_load(:before_configuration, yield: true, &block) + end + + # Third configurable block to run. Does not run if +config.cache_classes+ + # set to false. + def before_eager_load(&block) + ActiveSupport.on_load(:before_eager_load, yield: true, &block) + end + + # Second configurable block to run. Called before frameworks initialize. + def before_initialize(&block) + ActiveSupport.on_load(:before_initialize, yield: true, &block) + end + + # Last configurable block to run. Called after frameworks initialize. + def after_initialize(&block) + ActiveSupport.on_load(:after_initialize, yield: true, &block) + end + + # Array of callbacks defined by #to_prepare. + def to_prepare_blocks + @@to_prepare_blocks ||= [] + end + + # Defines generic callbacks to run before #after_initialize. Useful for + # Rails::Railtie subclasses. + def to_prepare(&blk) + to_prepare_blocks << blk if blk + end + + def respond_to?(name, include_private = false) + super || @@options.key?(name.to_sym) + end + + private + + def method_missing(name, *args, &blk) + if name.to_s =~ /=$/ + @@options[$`.to_sym] = args.first + elsif @@options.key?(name) + @@options[name] + else + super + end + end + end + end +end diff --git a/railties/lib/rails/ruby_version_check.rb b/railties/lib/rails/ruby_version_check.rb new file mode 100644 index 0000000000..67a19d8a94 --- /dev/null +++ b/railties/lib/rails/ruby_version_check.rb @@ -0,0 +1,13 @@ +if RUBY_VERSION < '2.2.2' && RUBY_ENGINE == 'ruby' + desc = defined?(RUBY_DESCRIPTION) ? RUBY_DESCRIPTION : "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE})" + abort <<-end_message + + Rails 5 requires Ruby 2.2.2 or newer. + + You're running + #{desc} + + Please upgrade to Ruby 2.2.2 or newer to continue. + + end_message +end diff --git a/railties/lib/rails/source_annotation_extractor.rb b/railties/lib/rails/source_annotation_extractor.rb new file mode 100644 index 0000000000..8dd87b6cc5 --- /dev/null +++ b/railties/lib/rails/source_annotation_extractor.rb @@ -0,0 +1,133 @@ +# Implements the logic behind the rake tasks for annotations like +# +# rake notes +# rake notes:optimize +# +# and friends. See <tt>rake -T notes</tt> and <tt>railties/lib/rails/tasks/annotations.rake</tt>. +# +# Annotation objects are triplets <tt>:line</tt>, <tt>:tag</tt>, <tt>:text</tt> that +# represent the line where the annotation lives, its tag, and its text. Note +# the filename is not stored. +# +# Annotations are looked for in comments and modulus whitespace they have to +# start with the tag optionally followed by a colon. Everything up to the end +# of the line (or closing ERB comment tag) is considered to be their text. +class SourceAnnotationExtractor + class Annotation < Struct.new(:line, :tag, :text) + def self.directories + @@directories ||= %w(app config db lib test) + (ENV['SOURCE_ANNOTATION_DIRECTORIES'] || '').split(',') + end + + def self.extensions + @@extensions ||= {} + end + + # Registers new Annotations File Extensions + # SourceAnnotationExtractor::Annotation.register_extensions("css", "scss", "sass", "less", "js") { |tag| /\/\/\s*(#{tag}):?\s*(.*)$/ } + def self.register_extensions(*exts, &block) + extensions[/\.(#{exts.join("|")})$/] = block + end + + register_extensions("builder", "rb", "rake", "yml", "yaml", "ruby") { |tag| /#\s*(#{tag}):?\s*(.*)$/ } + register_extensions("css", "js") { |tag| /\/\/\s*(#{tag}):?\s*(.*)$/ } + register_extensions("erb") { |tag| /<%\s*#\s*(#{tag}):?\s*(.*?)\s*%>/ } + + # Returns a representation of the annotation that looks like this: + # + # [126] [TODO] This algorithm is simple and clearly correct, make it faster. + # + # If +options+ has a flag <tt>:tag</tt> the tag is shown as in the example above. + # Otherwise the string contains just line and text. + def to_s(options={}) + s = "[#{line.to_s.rjust(options[:indent])}] " + s << "[#{tag}] " if options[:tag] + s << text + end + end + + # Prints all annotations with tag +tag+ under the root directories +app+, + # +config+, +db+, +lib+, and +test+ (recursively). + # + # Additional directories may be added using a comma-delimited list set using + # <tt>ENV['SOURCE_ANNOTATION_DIRECTORIES']</tt>. + # + # Directories may also be explicitly set using the <tt>:dirs</tt> key in +options+. + # + # SourceAnnotationExtractor.enumerate 'TODO|FIXME', dirs: %w(app lib), tag: true + # + # If +options+ has a <tt>:tag</tt> flag, it will be passed to each annotation's +to_s+. + # + # See <tt>#find_in</tt> for a list of file extensions that will be taken into account. + # + # This class method is the single entry point for the rake tasks. + def self.enumerate(tag, options={}) + extractor = new(tag) + dirs = options.delete(:dirs) || Annotation.directories + extractor.display(extractor.find(dirs), options) + end + + attr_reader :tag + + def initialize(tag) + @tag = tag + end + + # Returns a hash that maps filenames under +dirs+ (recursively) to arrays + # with their annotations. + def find(dirs) + dirs.inject({}) { |h, dir| h.update(find_in(dir)) } + end + + # Returns a hash that maps filenames under +dir+ (recursively) to arrays + # with their annotations. Only files with annotations are included. Files + # with extension +.builder+, +.rb+, +.rake+, +.yml+, +.yaml+, +.ruby+, + # +.css+, +.js+ and +.erb+ are taken into account. + def find_in(dir) + results = {} + + Dir.glob("#{dir}/*") do |item| + next if File.basename(item)[0] == ?. + + if File.directory?(item) + results.update(find_in(item)) + else + extension = Annotation.extensions.detect do |regexp, _block| + regexp.match(item) + end + + if extension + pattern = extension.last.call(tag) + results.update(extract_annotations_from(item, pattern)) if pattern + end + end + end + + results + end + + # If +file+ is the filename of a file that contains annotations this method returns + # a hash with a single entry that maps +file+ to an array of its annotations. + # Otherwise it returns an empty hash. + def extract_annotations_from(file, pattern) + lineno = 0 + result = File.readlines(file).inject([]) do |list, line| + lineno += 1 + next list unless line =~ pattern + list << Annotation.new(lineno, $1, $2) + end + result.empty? ? {} : { file => result } + end + + # Prints the mapping from filenames to annotations in +results+ ordered by filename. + # The +options+ hash is passed to each annotation's +to_s+. + def display(results, options={}) + options[:indent] = results.flat_map { |f, a| a.map(&:line) }.max.to_s.size + results.keys.sort.each do |file| + puts "#{file}:" + results[file].each do |note| + puts " * #{note.to_s(options)}" + end + puts + end + end +end diff --git a/railties/lib/rails/tasks.rb b/railties/lib/rails/tasks.rb new file mode 100644 index 0000000000..d60eaf6f4f --- /dev/null +++ b/railties/lib/rails/tasks.rb @@ -0,0 +1,18 @@ +require 'rake' + +# Load Rails Rakefile extensions +%w( + annotations + framework + initializers + log + middleware + misc + restart + routes + tmp +).tap { |arr| + arr << 'statistics' if Rake.application.current_scope.empty? +}.each do |task| + load "rails/tasks/#{task}.rake" +end diff --git a/railties/lib/rails/tasks/annotations.rake b/railties/lib/rails/tasks/annotations.rake new file mode 100644 index 0000000000..386ecf44be --- /dev/null +++ b/railties/lib/rails/tasks/annotations.rake @@ -0,0 +1,20 @@ +require 'rails/source_annotation_extractor' + +desc "Enumerate all annotations (use notes:optimize, :fixme, :todo for focus)" +task :notes do + SourceAnnotationExtractor.enumerate "OPTIMIZE|FIXME|TODO", tag: true +end + +namespace :notes do + ["OPTIMIZE", "FIXME", "TODO"].each do |annotation| + # desc "Enumerate all #{annotation} annotations" + task annotation.downcase.intern do + SourceAnnotationExtractor.enumerate annotation + end + end + + desc "Enumerate a custom annotation, specify with ANNOTATION=CUSTOM" + task :custom do + SourceAnnotationExtractor.enumerate ENV['ANNOTATION'] + end +end
\ No newline at end of file diff --git a/railties/lib/rails/tasks/engine.rake b/railties/lib/rails/tasks/engine.rake new file mode 100644 index 0000000000..c51524f8f6 --- /dev/null +++ b/railties/lib/rails/tasks/engine.rake @@ -0,0 +1,72 @@ +task "load_app" do + namespace :app do + load APP_RAKEFILE + end + task :environment => "app:environment" + + if !defined?(ENGINE_PATH) || !ENGINE_PATH + ENGINE_PATH = find_engine_path(APP_RAKEFILE) + end +end + +def app_task(name) + task name => [:load_app, "app:db:#{name}"] +end + +namespace :db do + app_task "reset" + + desc "Migrate the database (options: VERSION=x, VERBOSE=false)." + app_task "migrate" + app_task "migrate:up" + app_task "migrate:down" + app_task "migrate:redo" + app_task "migrate:reset" + + desc "Display status of migrations" + app_task "migrate:status" + + desc 'Create the database from config/database.yml for the current Rails.env (use db:create:all to create all databases in the config)' + app_task "create" + app_task "create:all" + + desc 'Drops the database for the current Rails.env (use db:drop:all to drop all databases)' + app_task "drop" + app_task "drop:all" + + desc "Load fixtures into the current environment's database." + app_task "fixtures:load" + + desc "Rolls the schema back to the previous version (specify steps w/ STEP=n)." + app_task "rollback" + + desc "Create a db/schema.rb file that can be portably used against any DB supported by Active Record" + app_task "schema:dump" + + desc "Load a schema.rb file into the database" + app_task "schema:load" + + desc "Load the seed data from db/seeds.rb" + app_task "seed" + + desc "Create the database, load the schema, and initialize with the seed data (use db:reset to also drop the db first)" + app_task "setup" + + desc "Dump the database structure to an SQL file" + app_task "structure:dump" + + desc "Retrieves the current schema version number" + app_task "version" +end + +def find_engine_path(path) + return File.expand_path(Dir.pwd) if path == "/" + + if Rails::Engine.find(path) + path + else + find_engine_path(File.expand_path('..', path)) + end +end + +Rake.application.invoke_task(:load_app) diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake new file mode 100644 index 0000000000..904b9d9ad6 --- /dev/null +++ b/railties/lib/rails/tasks/framework.rake @@ -0,0 +1,68 @@ +namespace :rails do + desc "Update configs and some other initially generated files (or use just update:configs or update:bin)" + task update: [ "update:configs", "update:bin" ] + + desc "Applies the template supplied by LOCATION=(/path/to/template) or URL" + task template: :environment do + template = ENV["LOCATION"] + raise "No LOCATION value given. Please set LOCATION either as path to a file or a URL" if template.blank? + template = File.expand_path(template) if template !~ %r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} + require 'rails/generators' + require 'rails/generators/rails/app/app_generator' + generator = Rails::Generators::AppGenerator.new [Rails.root], {}, destination_root: Rails.root + generator.apply template, verbose: false + end + + namespace :templates do + # desc "Copy all the templates from rails to the application directory for customization. Already existing local copies will be overwritten" + task :copy do + generators_lib = File.expand_path("../../generators", __FILE__) + project_templates = "#{Rails.root}/lib/templates" + + default_templates = { "erb" => %w{controller mailer scaffold}, + "rails" => %w{controller helper scaffold_controller assets} } + + default_templates.each do |type, names| + local_template_type_dir = File.join(project_templates, type) + FileUtils.mkdir_p local_template_type_dir + + names.each do |name| + dst_name = File.join(local_template_type_dir, name) + src_name = File.join(generators_lib, type, name, "templates") + FileUtils.cp_r src_name, dst_name + end + end + end + end + + namespace :update do + class RailsUpdate + def self.invoke_from_app_generator(method) + app_generator.send(method) + end + + 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 + 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 + RailsUpdate.invoke_from_app_generator :create_bin_files + end + end +end diff --git a/railties/lib/rails/tasks/initializers.rake b/railties/lib/rails/tasks/initializers.rake new file mode 100644 index 0000000000..2968b5cb53 --- /dev/null +++ b/railties/lib/rails/tasks/initializers.rake @@ -0,0 +1,6 @@ +desc "Print out all defined initializers in the order they are invoked by Rails." +task initializers: :environment do + Rails.application.initializers.tsort_each do |initializer| + puts initializer.name + end +end diff --git a/railties/lib/rails/tasks/log.rake b/railties/lib/rails/tasks/log.rake new file mode 100644 index 0000000000..877f175ef3 --- /dev/null +++ b/railties/lib/rails/tasks/log.rake @@ -0,0 +1,23 @@ +namespace :log do + desc "Truncates all *.log files in log/ to zero bytes (specify which logs with LOGS=test,development)" + task :clear do + log_files.each do |file| + clear_log_file(file) + end + end + + def log_files + if ENV['LOGS'] + ENV['LOGS'].split(',') + .map { |file| "log/#{file.strip}.log" } + .select { |file| File.exist?(file) } + else + FileList["log/*.log"] + end + end + + def clear_log_file(file) + f = File.open(file, "w") + f.close + end +end diff --git a/railties/lib/rails/tasks/middleware.rake b/railties/lib/rails/tasks/middleware.rake new file mode 100644 index 0000000000..31e961b483 --- /dev/null +++ b/railties/lib/rails/tasks/middleware.rake @@ -0,0 +1,7 @@ +desc 'Prints out your Rack middleware stack' +task middleware: :environment do + Rails.configuration.middleware.each do |middleware| + puts "use #{middleware.inspect}" + end + puts "run #{Rails.application.class.name}.routes" +end diff --git a/railties/lib/rails/tasks/misc.rake b/railties/lib/rails/tasks/misc.rake new file mode 100644 index 0000000000..4195106961 --- /dev/null +++ b/railties/lib/rails/tasks/misc.rake @@ -0,0 +1,60 @@ +desc 'Generate a cryptographically secure secret key (this is typically used to generate a secret for cookie sessions).' +task :secret do + require 'securerandom' + puts SecureRandom.hex(64) +end + +desc 'List versions of all Rails frameworks and the environment' +task about: :environment do + puts Rails::Info +end + +namespace :time do + namespace :zones do + desc 'Displays all time zones, also available: time:zones:us, time:zones:local -- filter with OFFSET parameter, e.g., OFFSET=-6' + task :all do + build_time_zone_list(:all) + end + + # desc 'Displays names of US time zones recognized by the Rails TimeZone class, grouped by offset. Results can be filtered with optional OFFSET parameter, e.g., OFFSET=-6' + task :us do + build_time_zone_list(:us_zones) + end + + # desc 'Displays names of time zones recognized by the Rails TimeZone class with the same offset as the system local time' + task :local do + require 'active_support' + require 'active_support/time' + jan_offset = Time.now.beginning_of_year.utc_offset + jul_offset = Time.now.beginning_of_year.change(month: 7).utc_offset + offset = jan_offset < jul_offset ? jan_offset : jul_offset + build_time_zone_list(:all, offset) + end + + # to find UTC -06:00 zones, OFFSET can be set to either -6, -6:00 or 21600 + def build_time_zone_list(method, offset = ENV['OFFSET']) + require 'active_support' + require 'active_support/time' + if offset + offset = if offset.to_s.match(/(\+|-)?(\d+):(\d+)/) + sign = $1 == '-' ? -1 : 1 + hours, minutes = $2.to_f, $3.to_f + ((hours * 3600) + (minutes.to_f * 60)) * sign + elsif offset.to_f.abs <= 13 + offset.to_f * 3600 + else + offset.to_f + end + end + previous_offset = nil + ActiveSupport::TimeZone.__send__(method).each do |zone| + if offset.nil? || offset == zone.utc_offset + puts "\n* UTC #{zone.formatted_offset} *" unless zone.utc_offset == previous_offset + puts zone.name + previous_offset = zone.utc_offset + end + end + puts "\n" + end + end +end diff --git a/railties/lib/rails/tasks/restart.rake b/railties/lib/rails/tasks/restart.rake new file mode 100644 index 0000000000..f36c86d81b --- /dev/null +++ b/railties/lib/rails/tasks/restart.rake @@ -0,0 +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/tasks/routes.rake b/railties/lib/rails/tasks/routes.rake new file mode 100644 index 0000000000..1815c2fdc7 --- /dev/null +++ b/railties/lib/rails/tasks/routes.rake @@ -0,0 +1,7 @@ +desc 'Print out all defined routes in match order, with names. Target specific controller with CONTROLLER=x.' +task routes: :environment do + all_routes = Rails.application.routes.routes + require 'action_dispatch/routing/inspector' + inspector = ActionDispatch::Routing::RoutesInspector.new(all_routes) + puts inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, ENV['CONTROLLER']) +end diff --git a/railties/lib/rails/tasks/statistics.rake b/railties/lib/rails/tasks/statistics.rake new file mode 100644 index 0000000000..a919d36939 --- /dev/null +++ b/railties/lib/rails/tasks/statistics.rake @@ -0,0 +1,28 @@ +# While global constants are bad, many 3rd party tools depend on this one (e.g +# rspec-rails & cucumber-rails). So a deprecation warning is needed if we want +# to remove it. +STATS_DIRECTORIES = [ + %w(Controllers app/controllers), + %w(Helpers app/helpers), + %w(Jobs app/jobs), + %w(Models app/models), + %w(Mailers app/mailers), + %w(Javascripts app/assets/javascripts), + %w(Libraries lib/), + %w(Tasks lib/tasks), + %w(APIs app/apis), + %w(Controller\ tests test/controllers), + %w(Helper\ tests test/helpers), + %w(Model\ tests test/models), + %w(Mailer\ tests test/mailers), + %w(Job\ tests test/jobs), + %w(Integration\ tests test/integration), +].collect do |name, dir| + [ name, "#{File.dirname(Rake.application.rakefile_location)}/#{dir}" ] +end.select { |name, dir| File.directory?(dir) } + +desc "Report code statistics (KLOCs, etc) from the application or engine" +task :stats do + require 'rails/code_statistics' + CodeStatistics.new(*STATS_DIRECTORIES).to_s +end diff --git a/railties/lib/rails/tasks/tmp.rake b/railties/lib/rails/tasks/tmp.rake new file mode 100644 index 0000000000..9162ef234a --- /dev/null +++ b/railties/lib/rails/tasks/tmp.rake @@ -0,0 +1,37 @@ +namespace :tmp do + desc "Clear cache and socket files from tmp/ (narrow w/ tmp:cache:clear, tmp:sockets:clear)" + task clear: ["tmp:cache:clear", "tmp:sockets:clear"] + + tmp_dirs = [ 'tmp/cache', + 'tmp/sockets', + 'tmp/pids', + 'tmp/cache/assets/development', + 'tmp/cache/assets/test', + 'tmp/cache/assets/production' ] + + tmp_dirs.each { |d| directory d } + + desc "Creates tmp directories for cache, sockets, and pids" + task create: tmp_dirs + + namespace :cache do + # desc "Clears all files and directories in tmp/cache" + task :clear do + FileUtils.rm_rf(Dir['tmp/cache/[^.]*']) + end + end + + namespace :sockets do + # desc "Clears all files in tmp/sockets" + task :clear do + FileUtils.rm(Dir['tmp/sockets/[^.]*']) + end + end + + namespace :pids do + # desc "Clears all files in tmp/pids" + task :clear do + FileUtils.rm(Dir['tmp/pids/[^.]*']) + end + end +end diff --git a/railties/lib/rails/templates/layouts/application.html.erb b/railties/lib/rails/templates/layouts/application.html.erb new file mode 100644 index 0000000000..5aecaa712a --- /dev/null +++ b/railties/lib/rails/templates/layouts/application.html.erb @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8" /> + <title><%= @page_title %></title> + <style> + body { background-color: #fff; color: #333; } + + body, p, ol, ul, td { + font-family: helvetica, verdana, arial, sans-serif; + font-size: 13px; + line-height: 18px; + } + + pre { + background-color: #eee; + padding: 10px; + font-size: 11px; + white-space: pre-wrap; + } + + a { color: #000; } + a:visited { color: #666; } + a:hover { color: #fff; background-color:#000; } + + h2 { padding-left: 10px; } + + <%= yield :style %> + </style> +</head> +<body> + +<%= yield %> + +</body> +</html> diff --git a/railties/lib/rails/templates/rails/info/properties.html.erb b/railties/lib/rails/templates/rails/info/properties.html.erb new file mode 100644 index 0000000000..d47cbab202 --- /dev/null +++ b/railties/lib/rails/templates/rails/info/properties.html.erb @@ -0,0 +1 @@ +<%= @info.html_safe %>
\ No newline at end of file diff --git a/railties/lib/rails/templates/rails/info/routes.html.erb b/railties/lib/rails/templates/rails/info/routes.html.erb new file mode 100644 index 0000000000..2d8a190986 --- /dev/null +++ b/railties/lib/rails/templates/rails/info/routes.html.erb @@ -0,0 +1,9 @@ +<h2> + Routes +</h2> + +<p> + Routes match in priority from top to bottom +</p> + +<%= @routes_inspector.format(ActionDispatch::Routing::HtmlTableFormatter.new(self)) %> diff --git a/railties/lib/rails/templates/rails/mailers/email.html.erb b/railties/lib/rails/templates/rails/mailers/email.html.erb new file mode 100644 index 0000000000..fed96fbc85 --- /dev/null +++ b/railties/lib/rails/templates/rails/mailers/email.html.erb @@ -0,0 +1,130 @@ +<!DOCTYPE html> +<html><head> +<meta name="viewport" content="width=device-width" /> +<style type="text/css"> + html, body, iframe { + height: 100%; + } + + body { + margin: 0; + } + + header { + width: 100%; + padding: 10px 0 0 0; + margin: 0; + background: white; + font: 12px "Lucida Grande", sans-serif; + border-bottom: 1px solid #dedede; + overflow: hidden; + } + + dl { + margin: 0 0 10px 0; + padding: 0; + } + + dt { + width: 80px; + padding: 1px; + float: left; + clear: left; + text-align: right; + color: #7f7f7f; + } + + dd { + margin-left: 90px; /* 80px + 10px */ + padding: 1px; + } + + dd:empty:before { + content: "\00a0"; // + } + + iframe { + border: 0; + width: 100%; + } +</style> +</head> + +<body> +<header> + <dl> + <% if @email.respond_to?(:smtp_envelope_from) && Array(@email.from) != Array(@email.smtp_envelope_from) %> + <dt>SMTP-From:</dt> + <dd><%= @email.smtp_envelope_from %></dd> + <% end %> + + <% if @email.respond_to?(:smtp_envelope_to) && @email.to != @email.smtp_envelope_to %> + <dt>SMTP-To:</dt> + <dd><%= @email.smtp_envelope_to %></dd> + <% end %> + + <dt>From:</dt> + <dd><%= @email.header['from'] %></dd> + + <% if @email.reply_to %> + <dt>Reply-To:</dt> + <dd><%= @email.header['reply-to'] %></dd> + <% end %> + + <dt>To:</dt> + <dd><%= @email.header['to'] %></dd> + + <% if @email.cc %> + <dt>CC:</dt> + <dd><%= @email.header['cc'] %></dd> + <% end %> + + <dt>Date:</dt> + <dd><%= Time.current.rfc2822 %></dd> + + <dt>Subject:</dt> + <dd><strong><%= @email.subject %></strong></dd> + + <% unless @email.attachments.nil? || @email.attachments.empty? %> + <dt>Attachments:</dt> + <dd> + <%= @email.attachments.map { |a| a.respond_to?(:original_filename) ? a.original_filename : a.filename }.join(', ') %> + </dd> + <% end %> + + <% if @email.multipart? %> + <dd> + <select onchange="formatChanged(this);"> + <option <%= request.format == Mime[:html] ? 'selected' : '' %> value="?part=text%2Fhtml">View as HTML email</option> + <option <%= request.format == Mime[:text] ? 'selected' : '' %> value="?part=text%2Fplain">View as plain-text email</option> + </select> + </dd> + <% end %> + </dl> +</header> + +<% if @part && @part.mime_type %> + <iframe seamless name="messageBody" src="?part=<%= Rack::Utils.escape(@part.mime_type) %>"></iframe> +<% else %> + <p> + You are trying to preview an email that does not have any content. + This is probably because the <em>mail</em> method has not been called in <em><%= @preview.preview_name %>#<%= @email_action %></em>. + </p> +<% end %> + +<script> + function formatChanged(form) { + var part_name = form.options[form.selectedIndex].value + var iframe =document.getElementsByName('messageBody')[0]; + iframe.contentWindow.location.replace(part_name); + + if (history.replaceState) { + var url = location.pathname.replace(/\.(txt|html)$/, ''); + var format = /html/.test(part_name) ? '.html' : '.txt'; + window.history.replaceState({}, '', url + format); + } + } +</script> + +</body> +</html> diff --git a/railties/lib/rails/templates/rails/mailers/index.html.erb b/railties/lib/rails/templates/rails/mailers/index.html.erb new file mode 100644 index 0000000000..000930c039 --- /dev/null +++ b/railties/lib/rails/templates/rails/mailers/index.html.erb @@ -0,0 +1,8 @@ +<% @previews.each do |preview| %> +<h3><%= link_to preview.preview_name.titleize, url_for(controller: "rails/mailers", action: "preview", path: preview.preview_name) %></h3> +<ul> +<% preview.emails.each do |email| %> +<li><%= link_to email, url_for(controller: "rails/mailers", action: "preview", path: "#{preview.preview_name}/#{email}") %></li> +<% end %> +</ul> +<% end %> diff --git a/railties/lib/rails/templates/rails/mailers/mailer.html.erb b/railties/lib/rails/templates/rails/mailers/mailer.html.erb new file mode 100644 index 0000000000..c12ead0f90 --- /dev/null +++ b/railties/lib/rails/templates/rails/mailers/mailer.html.erb @@ -0,0 +1,6 @@ +<h3><%= @preview.preview_name.titleize %></h3> +<ul> +<% @preview.emails.each do |email| %> +<li><%= link_to email, url_for(controller: "rails/mailers", action: "preview", path: "#{@preview.preview_name}/#{email}") %></li> +<% end %> +</ul> diff --git a/railties/lib/rails/templates/rails/welcome/index.html.erb b/railties/lib/rails/templates/rails/welcome/index.html.erb new file mode 100644 index 0000000000..acf04af416 --- /dev/null +++ b/railties/lib/rails/templates/rails/welcome/index.html.erb @@ -0,0 +1,268 @@ +<!DOCTYPE html> +<html> + <head> + <title>Ruby on Rails: Welcome aboard</title> + <style media="screen"> + body { + margin: 0; + margin-bottom: 25px; + padding: 0; + background-color: #f0f0f0; + font-family: "Lucida Grande", "Bitstream Vera Sans", "Verdana"; + font-size: 13px; + color: #333; + } + + h1 { + font-size: 28px; + color: #000; + } + + a { + color: #03c; + } + + a:hover { + background-color: #03c; + color: white; + text-decoration: none; + } + + #page { + background-color: #f0f0f0; + width: 750px; + margin: 0; + margin-left: auto; + margin-right: auto; + } + + #content { + float: left; + background-color: white; + border: 3px solid #aaa; + border-top: none; + padding: 25px; + width: 500px; + } + + #sidebar { + float: right; + width: 175px; + } + + #footer { + clear: both; + } + + #header, #about, #getting-started { + padding-left: 75px; + padding-right: 30px; + } + + #header { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAABACAYAAABY1SR7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAGZhJREFUeNqsWwmUXGWV/t5Sr9aurl6qO0l3Z9/DEoJh18gZQGAUxPHIyQHH7eioZ8bjnAFHZ0RndNxxRBhGcUbxoKIHBkTEcUYREIHIGpKQjUDS6U660/tSVV3Lq/fefPf/Xy2dBFGYx3npqvde/e/e/97v3u/e/8e4Lt2L8DCCAFcGwF8ZBjYbgM1rAZoO+WLwZhDMu9y4+YcOozbAqzwXNA3GdzX/5hV+KnKO2+GXFj/AvzmW8e72iG202CYiphbY403f9/k3QHZtJ9oWtyCQe7wGX79TKVb7rP9pXJPDVxf0Rz+oyxm4HNWrahFNixdk3EAJbERMWOm4ulctVODNVeEVK0DeRVDgb1wfJgcqUo6duaKnFOH7bm6JmH+5LOEgZprwRIHAV3JYfLjKM55Noz3bBqdcgt0Wg52Kq/cHHkXns0qIukKBlltk9rU2QaiouiefPQ+RdBuseAJeqYTK1CTH8mE4NsyIpRWu8nssCs+xULWpjGVwTvieKl/sV6mIXzOib/OftzuG8d6l8SiVMODyRb46oazg8YPP2Wnvy9ISNqplzsxYAW6hjGhHEmYiBoPC+hRMfFMrESgrBC5n0KS+lq1nPahZh2OXymg9bSNWX/u3FKyKI//7Exx96B4Y8RiCEseq8t0VznyxjMDidFIJ8QSf3hJEOFbZEAHVhIkFTX54fxtnIW5pJUQIeZ8ooZShkInuDOLpFIX1ldtCBix7KI/k4E7OwbTjcNIdiCQzsONp2LEk7GgUnZsuQN9lW2En45xlukrUghWzeZq8FsXsi8+gND6MSCqD9k3nwulIUShKZxt0LYPWortRSY0NXreC8J6pZNDChEDh53PT1NIPLaEnLbQKNTETEaR7sycA0jD1INXZAnzObjTbiWh7Vr1A3Knn4nciu+lCvstUig09cp96cVCtcELoFpEIFUjjyIM/osWIg+IMXS3DcfNwZ3NQHmmKU9OqroX2jWdgatduuPkpmA4ViZrK9RqKABEBtg9tDeW+oUIyTIYuFaX7eCG4aqbU+hhKocD3UBoZISBLiC9cpAQKyq5SQo6OjVswtec5VHLTiHUuIN4WonXlqUj2riS0DIUXwZlERFHSK+SQGzqI3MHdmNm7CzMvvowF527B8qvejZ3/+iXk9vVTao5tiTKN0OUHISZEGS/8W6UbRdoTSHe3E1f+CRaR3xhBLVJSIQ7qleZQGBigZYoYdR+ElUjBaW3H6JMPIrV0Hdo2bEayZ7my0KsdLctPBS64EuWZMYw/9wTGnvod0mtzWH71Vuz66o10bVpK8FIx6orUMejpCKYBTvfM9HXBJtA8z3/1BKDivaksVJmaYsgsYPDnd6LzzAuw8I1XUIGleC1HtDWLnguv5BiX4+jDD2D4sQeV1bQvNXBi6vAb1MGtrEEHjRPgqfZ0qMRJElYYSudfq12nmzAvtJ2yib69iRadRGnySD0Uv5bDtCPou/gqnPY3N6DnLRczgtHxCf4aVnUeUdgw6i6FqM1w292Ujo/TJdB5wHcJ2iDCaBTRmVfw4rkw4yksuvQyJJf0YvrgNiayvBLESS9AYuFqJLLLCPb4SQWulosojhxmeCeoDeaQSoVuy8lPtSKxYKnC2Bmf+DwtvBgv3/qfTI6uEtGuJV7PCBTIq5zNtt5uxBgyvap30pf55TISfX1Y/PatGPrVvcgPvEyAJ1GenaPZLSy//G2IL+qki43CNCMwk620iovy9FGUJgYwm8gwpK/guRJOS5dyD688h+n9z2L28F4Ujx2ia04jEl8Ad3oGVTePaGcnQ3sKLb1rkD3nIqx594dRIh733n6PmmrrvGj671sjVlxczRWAkxZ0r+rTrhfMJ0uEM8xKUYXONR+5nr57BdpP24TCsX6M/f5F5AYLWPauK9F11htUwjOIL8GNZH1qpKwiyVGELk0OoDj2EtziFOaODSN3aC/v24xmZzAU51TgcJKd/DktHo9jyRXvg0Or7PvejTj22KPKiyafew6zg8MYypVLNsLkJ2bxaZXM4i5EmCBPsEaoWJUUpfeSK7DgvEtQmh4ihTDQdf5FOHDHr7HqPVeh99KL4OVzpE50N18CtqnCdBCY6rsEcTsqIGUGD6rY9e3bMPzIHmTWLsbqa7ai84wL6YrTqEyOqEmwonEExSoO//R7dLcJWiWCueF+7P7mjZAUY8YdJZqySMo24j5zQSybQdeyhdrX5imho4NhEEnkRbkDQyjSRVJLeziCgef/6avIrFuOtR95P2lJNSSshg4l6rdm+Ht9inWsqIOX7voN+u/eRoEM5PvHMbbjGcwcfg7jO3YxbCcRiaaYQOXnpEaFGeahGQaMCidJRidt8RghS6Q344XQIowmFq2QXdLNdwsx8zUFqCOQNIECVqdp8pESB53Fvhdux9T2FxBb1AWX4XbjDX/HFzjEmgedB4XYKT5D4T0VTLRCtIiTwOBvfovpvS8T+Bm4MyW6jw13tIIDt/9G/TTWk8HKvzgbmd4+YldYQIdixgHJYkC82Ul6UDnQSbEGdsFGZlEWyUyLyiEyYwajRVAoAXNlEjR+pjUCUmiDQcKOORwwgpFfP4cg5mPzTZ9FoqePdGVWuZRPYQNPcgrd0/dCpqpdy3DIsQ4fxtiTu7Hxkx8iRXkcB+94iM86/K0Jx4opi5aOzGJs14toWeLAdYXWxFQCtJlkA+LUq+bI7QR3mj3YoqVNgGcXd5NWUOiZAk9GH86S4jK25jWBLVREl1uK5Voywz6WXf1WLHjTm0lPigSyxoUpnEqU8c26Wyk/Y24RMjhw/yMoj+cQbWvH0isuwuijL6BwaJwcyq7XUTaBP7N3HOU3ke7HSONJb8RTBGoGKZPFyTE8saTZyCPtrC2coxOoTuY5+x4UTzHNsNjR6d6Qa8JJ5BIV8ksVtKzpwcr3v5dyOrzHKMWXizsZAnK6k1ImPDmAqjOmdr9AwXcodzr4kwfQfuY6VKbzyhpGU96S75WxIqb2DaPnvNWKklQD4WSuzB+sVILjOYjm/VARSWKTBQQzlZCFmErYeubzVJJR14SlQtVQMjO0xrXvoulXkq3OKnxAXqSsoSmNUbOM/BV35RjDDz9JrBXpnnEM3vsYjj38LLyZihI8QNAgQhITOCmTO46i+6w1MPm86RVIiC09/RJUGcECCe2UU0G6QIyUjEC5hGaCNd4RqHKU6VuDylQlI2N8hfXDWibEdyhCKXREuZUVUX8lyhh2+Jl5Q/6akSgT4izGn3wBFu+JwYOKj8qwtsbJaYmJuYEZ5AYmFOWXPCN1jTodzeuqM0WtSI1rzXrV0LSNKRFuZLYQ2EYVPjEQVuQUMsCya65GvL1HWUwJS+FNUcBsUiZUQv7aLGlndr+I8ug4XUMVAJw4U7FmI8SFETTmUaGK2gas1SeeP8znoizIEso9DaUIy2FWkNU5V0VYs/azWXKncuCHqgQq1CHiY831H8TGr34erRvXKdD6LD3b+HnRn12qGgdqlmxHZe2aRcy6NbQScl8y8dSOfWQE1yK9YYmqXYww3xhNObemUI2IWraF2d1HMTeeh83MbkUiylKiiMdy2wjzXBjxWYdRiSkhfDVVKGSstxM9l16JxZe/E2+848c49bPXK9D2vPUyEsBOVZMINmpCW6HgEOuIQjXF6FYuAV2aHsWyrVfj9C9er5SR5Kms0PTf8QoZtIo7WSJW+mmRJLGSpDK2ipzV2bK6X6fxtWOCicYVqyhGXkXn+WeTcfape5ZDsPGM91C5iy8LI0s445bd9FkrAFHICt1N8DE+gdyeQczs34+uzeei68LNLGfdea50st6VbiyYmHq+nxTFRSSRVsD3ii7xyeQbdt/M5h/MERMT4i6GjlAWeUxh6HCN8+LIz+5H5zlUbtHSOnVp4MCa51JaIQ16i0kwP9CP0uExPP+JL2DggfuYN8jTJClYxnH4aNimdpp0r7nDkyx9h5gE0+RqSVTyZXXTsMz5FaJyMJrrGLNopyWUIImj//1LjPzuUZLCC5gzVqMwPIglV7/rxCaihFaCPCDOxDUl1EoylFP4mUlFCgPDStLKWB47PnUjrSSsNqrJsa/zR02ZwGjYRoVkEZh0ZHzbfmTPXE85SWrnKip6GeFE2I1iKVBCzNK9pmiVhS1x+Axx7myRJesvgHvvR3rNKmQ3n/OKPVGND1MVXTqHiFK6qVFiwlXgTVDhkq+ChhnyJCW9GeaoIGQOdV0M9YhYZWbvUXrIJJ+rKL6lJ9CYj5Fai0iKqyPkx0HcUsJYrBbtREIJ2H72GxTI/2CL1zAbLkZ8WIxYgUvsKebq6Zl3rEZvymx6echo1N+au9XcS3oHsxWMPrGTFH+CLhsmbhMNRWrNB4SZVSwyJ5WDFRb3DAAmaXf2rPP+6BpbkmStkBLAWwkHmdNWKfYqFaZRp2GGdo+mhpv6bBkNhepRzERpdASeW1aKSZ5RidpoUsRAvQ+NJCnJHHl+bcZ80vjkij661vo/rWMQSitWskgnNv7LP+MNN38NadYuCPtYCItIFTjMRgfeqClkhkFZ+FXCQmpFuyKXii7xNI93LT9szdrUMsNZnJkuwZX6zlKdaqRXrESiq/e19kBC3NisLt+Gc/7jW0gtZ51Bl1MCmUaoM//aRv0aapnF0l362KIUnI6EyuhCUOuWrIVfAZcRAj5NJWJ0C5epP19y1awJLWhdt/a1t3KcGF8Yxb5bbsLItoeYmxZRkRWq46IrR9StX/tcw4oKsYH+nlrZpmbcZQ7R1tDPBvMbdIwofLpVKIfcJy5nCa5WRhnDFkVOx+s5kr29GPzpfUxsuxg0zlQUxSZudG/CqNOSIJxYCclGCA7fDRDpiCK6gIVfidVmWXrHRh0fmBd+eSYIIEcWdRhdJJsWp+aQT1vI9nYjnl3wuhSJLuhAJJ1WQWDisadUELCi0bD1WlscMpq6lrV1Ft0riC9tVcFD8odfDVS9bod5pNGgC3+XFnxsXA2rsw25/gHMTcwiRxdbvLgPsY7s61IktWSZinw6l8SbupNGvUlphB1yZY3aIhfZtRmz4XS3oMoA5JP6BywdvBIr24ytMdzsWjHaMcnI0nXRG5FkdCrnS6gy6QzccxeMZDsJW+r1KbJ4pbKAVy6huXoyauVUaAUjRK5WjN9cH05PCiZl84VfsXaSVTKf191C6F61qCXjtjAORtvTSPb0sgYoEi/UmEmnMj6JkpXA6z2cTAbxxV26GdEEZB12DVVV63BrIuwYaWpCGZyuJBWSFSxPLTB5PH1+rhDDKlQbuvajNUzE+UVyRTTdQt+zWIrGWIJOozo8hjmashq8PkXsZAoty1Yqi/gVnq6ru+p1pUKFTM3dENJzu421TiqKKq3hhUp45apSyM1VGMH0xOi+liz0yOxUyijs2w2DlRjI+8tHB3XUIP+fGBxA9+LFr1kRgwV769p1fPkEQ+9KRq+dKE9MsGKc1BmxltEC7W6CEdW0aUtocIvw0tcSt5JGu3R4OA+zIxW1uKoUOUZzFxmxRp/ai+iz+xi9CK5EVJGdqBNBlG4xdvBlRq9eTQteawhm0MgPLsSGj92gVqjKk8ew/TOfxPjjz8BKxhvLFGHjWUBuJh0Cu6pqD7WCTGz4BDqKpE30rIlj05rw6sKFxuCXPP9O8MEjxQqOTuQwNjJLa1mItaRRGB3GLHnO6znaNmxC/nA/cocPKNoS61iEZVdfEy5LBHVKUieCLY5eeKIiXp6RapJuNVJFMCamYGnOUFyslBo0Xronai0dIfXmnZIqtKhgNIaj/F3ULSLx4j60dnXXy8s/OZe0dyGW7cLOL34arevXI9rayWgYhZPtoJtNqsTbyPKUgwzamyCw867MtG5NBUF9bSBXLCkeKOzDroUutaZODax52yUk5sfgsyrL897+PXtQHTmK7vWnomPpCkSTf3pI7j7/Qmz/5HWY3r5LNziYeC3WPlYsovOJJ7VKVbuPENcgXEyvuV3IbKXpPlcqqh0acqGe2S1oq1jzqmZ+b0mGDJNaM2bnjrHuPnYUifZOtDMKda9ah1RnZ30F99WO9jM2MzouZw0vLdJIuCsiUInOz0vbiVNa9DSBtITyWo3VAV/XG/KmPEuBKrmard7rNxKiyCoN7EBnpXlLCiYTmfibuEHSSSkLV4uzGNr5NEYP7EZb31J0rd6AzMIevtf+g4oIg+7e8iYM3H03J5muw9n3ZquqfwU3aGDdMBqdztr+lXBbhyg+R2xYTb5jN7YG6SKnyh870r8Ki6Py0CiO3fcTNWaCBU3E8FVDr7ZPRjbcDLHO30N/TmazdLk+JFMxVoZh6errUrcmnDQp5o4MocrI4o3N6dmXhp1hoHkOFV2R5CXtVwm3Qc0aBip8Z6lY0HtRpJ8GYz5pVFgxgkaHiaCuDE1gfOAhFdNbJIKxplCKNJqqyoqi0CT9tp9/IyyPE2SryYyDKD9LVKxKUqXbuFOM+yVDN/Rq+0ia1mLmtYNqK8rhTiSpLLNbLkDLuZvQ0X8QBoG+//5fIMjP4AQ/kJkuM+vW+sS1wkgiVSTi0Fq2XqoLFfFYMMkyHSFL2mOpHQmy+aU4xXHoLk6rrIkYiE1JNpZOJjO1ivduOLSkZeuk6/YBwR54jaVv6chXpmZQmJnEssveQjwVcPCXv1IWt4//sUVB7K4WpGTREqhvJCrO5MhtGLMTKWU5pUSpDKs1glhbB4W3VCSpTM6gOl2GQzxJt+RQUMFcOoENrXG0FEhESSvMmIVIZ6uaHL9QZn6Y067VNJueV4bdmYDdktJ7pAJNKKfG+pG/cz8GH/gfGLIARF4o9fs8RWSrUmZxN7Z+9za0sooTPiRuI22bsUMHsevWW1B+iFnYdOgqFWTPPxWnXPdxtK5eV8fB9IH92Pn1m1hz7MQh00Xm/C34+K23MiOXsPvLX8bgbXej5bz1OPs7tzIhduHgnXfghX/8OplEsr6U4ZtV9G69HMvf8wEkKUfgaUeWbs4zX/8Sxm/+AbzxCRVF1VpFM9hrvS2ZmZbuRUh2LpxPw7t60EWK8vgHPoCZ5w4i1pvBps99Bu2nbJ73XLyzB4kvLcAPt27F2LFR9MTjSKbb1L1h4mIq4iNL14u2ZRFJysazZCNHqA0DZXRcuBGnf+bz6v4JLDqVgk3r247DnMdJDkOzffJtDfoY2b0dg08/gbZlq7BiyyWk+MuQ2bAGU9v2snTtQnxBj3pu9OnfYXr/Hiq1EZ0bz0ZsUS+sFUvgDB+DFfh1v3X9Kg4xknfLRNZ21h2/RYTX29avU0pUSwUcuP07KLw0oBZrA5bGozt2MlA5updgzGuJnYyp6rt7778HP37fX+OJW77ZaKzKoo5eOdfRhMehO3+EbXzu8H/dXW/SOTwj0gZqeoVck+h3xES9LDjpVp3QXeRdqSVLkDllrepy5oeHMPH0brq2qdteRmNJwj7pYKFVlr75YrztKw6ya9aFTzF8Tk+pBZrmXRGRdCsSLMiQbKlfE7PLrjarCcSSA0QZvQQevGKncnrfXpVwZTde3+XvqN9b8d4PYfuNn8O+b9zO56K6oGpOiMYreNfSc7eoE+FO00P334XJx3fQzM685zd8/Hqs/uCHGGEy9QEslaT0Cm9t7rVyYqnGWogEGIl+nqUTmyxwTj62HTs/91ks3XqN2u8VBLKZoVt14pe/42oc/O6dzB2+qnEMNGHEPHHbSfiSqloakGP7D7+Dpz79BfT6cRXu5rHatk51Nh9aEaOJu2mOZIf36uDu6EDi3PVoIQGV5efiwSG1Rjny8COY3P4sI1WM2HKx4bpPYdEFlzA9RMOlhCAsLJssYqGxRbcZI8//9MfIrliDvjPOwqqL/xwD996P6rY9zGHWPNMNPf8UJl9+Cdm169G9YWOdapjB8auShsJMc85YdekVWL7lQgroKHd68qMfRcAEu+lrX1GdSdmBKjQn0aOrU9lso5bK53uSLiyscNu10tAy66FganAQD9zwD6jM5ZBe2IeLbvoGWs5YofZQyfKXxbpejl133omfXfth7P/Fz8NRLbXgb0nGNe26GhGST5MzFmEYll2oCl+sd2IZCcWtTKxd6rokwdYVpyK9fB1z1KnIrD0NDt1WiNGB738X3kxJVapiWVmR5pCurc2iSaIkmNJ0Hr+9+WYkMu0YfHI7Dv9+J+766Eew8vSNiFP4WGsGBanhh6bw1K3fRjSdwfSel5FikTT67At4+t9vgVssojA0Rp6VwOyhfjx9262qABrfw1KaJW15YprXvsVcEG1sT5eCji40fXSURVyAvTd9TSmv6nTVifQx/uwzmHiU7kb3Clu+GC27MsY247p07+SihN0m/Kgc6EXRIjmMgDvCF9mcsXJxDgniZSnN3xFLIcc6Yormd1mhCX2QpWc7SteolNUpNUQkIUvJpDkUrsrfqy1L8ZjaFSTrJKLsCbvz6BqxaBwdBReWbJmF3kTa2NYRVYFGHEYKqqFKFXtzMg6uUhaJyzZyQ/d/FdUm8LwmAuYwO/vhQBU+m+ddmy+NpBKNWpIzF7EdRSxrOygMMl6LruUw2tQXOTy1akNFk/XtU/V70H3g6YyNNk5GtOIp/DYvlKp9LoJLWuIl2fADfJ/X71PQ8Jo2Vzbv620OAFI9jtIqCQ7tnfC/JxhNT4dShds4UKvB66s1ftPnRqOh/l13hDDqWGhxqUgTsIV1Fzg5Y7TEpKsK+B/w+sdqUWuqv1CxUN8K/MqHLMnhj/g/J/4/juDky9VSg0kh/zQj322897Pao/8nwAC+AZicLeuzngAAAABJRU5ErkJggg==); + background-repeat: no-repeat; + background-position: top left; + height: 64px; + } + + #header h1, + #header h2 { + margin: 0; + } + + #header h2 { + color: #888; + font-weight: normal; + font-size: 16px; + } + + #about h3 { + margin: 0; + margin-bottom: 10px; + font-size: 14px; + } + + #about-content { + background-color: #ffd; + border: 1px solid #fc0; + margin-left: -55px; + margin-right: -10px; + } + + #about-content table { + margin-top: 10px; + margin-bottom: 10px; + font-size: 11px; + border-collapse: collapse; + } + + #about-content td { + padding: 10px; + padding-top: 3px; + padding-bottom: 3px; + } + + #about-content td.name { + font-weight: bold; + vertical-align: top; + color: #555; + } + + #about-content td.value { + color: #000; + } + + #about-content ul { + padding: 0; + list-style-type: none; + } + + #about-content.failure { + background-color: #fcc; + border: 1px solid #f00; + } + + #about-content.failure p { + margin: 0; + padding: 10px; + } + + #getting-started { + border-top: 1px solid #ccc; + margin-top: 25px; + padding-top: 15px; + } + + #getting-started h1 { + margin: 0; + font-size: 20px; + } + + #getting-started h2 { + margin: 0; + font-size: 14px; + font-weight: normal; + color: #333; + margin-bottom: 25px; + } + + #getting-started ol { + margin-left: 0; + padding-left: 0; + } + + #getting-started li { + font-size: 18px; + color: #888; + margin-bottom: 25px; + } + + #getting-started li h2 { + margin: 0; + font-weight: normal; + font-size: 18px; + color: #333; + } + + #getting-started li p { + color: #555; + font-size: 13px; + } + + #sidebar ul { + margin-left: 0; + padding-left: 0; + } + + #sidebar ul h3 { + margin-top: 25px; + font-size: 16px; + padding-bottom: 10px; + border-bottom: 1px solid #ccc; + } + + #sidebar li { + list-style-type: none; + } + + #sidebar ul.links li { + margin-bottom: 5px; + } + + .filename { + font-style: italic; + } + </style> + <script> + function about() { + var info = document.getElementById('about-content'), + xhr; + + if (info.innerHTML === '') { + xhr = new XMLHttpRequest(); + xhr.open("GET", "/rails/info/properties", false); + xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); + xhr.send(""); + info.innerHTML = xhr.responseText; + } + + info.style.display = info.style.display === 'none' ? 'block' : 'none'; + } + </script> + </head> + <body> + <div id="page"> + <div id="sidebar"> + <ul id="sidebar-items"> + <li> + <h3>Browse the documentation</h3> + <ul class="links"> + <li><a href="http://guides.rubyonrails.org/">Rails Guides</a></li> + <li><a href="http://api.rubyonrails.org/">Rails API</a></li> + <li><a href="http://www.ruby-doc.org/core/">Ruby core</a></li> + <li><a href="http://www.ruby-doc.org/stdlib/">Ruby standard library</a></li> + </ul> + </li> + </ul> + </div> + + <div id="content"> + <div id="header"> + <h1>Welcome aboard</h1> + <h2>You’re riding Ruby on Rails!</h2> + </div> + + <div id="about"> + <h3><a href="/rails/info/properties" onclick="about(); return false">About your application’s environment</a></h3> + <div id="about-content" style="display: none"></div> + </div> + + <div id="getting-started"> + <h1>Getting started</h1> + <h2>Here’s how to get rolling:</h2> + + <ol> + <li> + <h2>Use <code>bin/rails generate</code> to create your models and controllers</h2> + <p>To see all available options, run it without parameters.</p> + </li> + + <li> + <h2>Set up a root route to replace this page</h2> + <p>You're seeing this page because you're running in development mode and you haven't set a root route yet.</p> + <p>Routes are set up in <span class="filename">config/routes.rb</span>.</p> + </li> + + <li> + <h2>Configure your database</h2> + <p>If you're not using SQLite (the default), edit <span class="filename">config/database.yml</span> with your username and password.</p> + </li> + </ol> + </div> + </div> + + <div id="footer"> </div> + </div> + </body> +</html> diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb new file mode 100644 index 0000000000..5cc1b5b219 --- /dev/null +++ b/railties/lib/rails/test_help.rb @@ -0,0 +1,42 @@ +# Make double-sure the RAILS_ENV is not set to production, +# so fixtures aren't loaded into that environment +abort("Abort testing: Your Rails environment is running in production mode!") if Rails.env.production? + +require "rails/test_unit/minitest_plugin" +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! + + class ActiveSupport::TestCase + include ActiveRecord::TestFixtures + self.fixture_path = "#{Rails.root}/test/fixtures/" + self.file_fixture_path = self.fixture_path + "files" + end + + ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path + + def create_fixtures(*fixture_set_names, &block) + FixtureSet.create_fixtures(ActiveSupport::TestCase.fixture_path, fixture_set_names, {}, &block) + end +end + +class ActionController::TestCase + def before_setup # :nodoc: + @routes = Rails.application.routes + super + end +end + +class ActionDispatch::IntegrationTest + def before_setup # :nodoc: + @routes = Rails.application.routes + super + end +end diff --git a/railties/lib/rails/test_unit/minitest_plugin.rb b/railties/lib/rails/test_unit/minitest_plugin.rb new file mode 100644 index 0000000000..d39d2f32bf --- /dev/null +++ b/railties/lib/rails/test_unit/minitest_plugin.rb @@ -0,0 +1,91 @@ +require "active_support/core_ext/module/attribute_accessors" +require "rails/test_unit/reporter" +require "rails/test_unit/test_requirer" + +module Minitest + mattr_accessor(:hide_aggregated_results) { false } + + module AggregatedResultSuppresion + def aggregated_results + super unless Minitest.hide_aggregated_results + end + end + + SummaryReporter.prepend AggregatedResultSuppresion + + def self.plugin_rails_options(opts, options) + executable = ::Rails::TestUnitReporter.executable + opts.separator "" + opts.separator "Usage: #{executable} [options] [files or directories]" + opts.separator "You can run a single test by appending a line number to a filename:" + opts.separator "" + opts.separator " #{executable} 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 " #{executable} test/controllers test/integration/login_test.rb" + opts.separator "" + opts.separator "By default test failures and errors are reported inline during a run." + 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 + + opts.on("-d", "--defer-output", + "Output test failures and errors after the test run") do + options[:output_inline] = false + end + + opts.on("-f", "--fail-fast", + "Abort test run on first failure") do + options[:fail_fast] = true + end + + options[:output_inline] = true + options[:patterns] = opts.order! + end + + # Running several Rake tasks in a single command would trip up the runner, + # as the patterns would also contain the other Rake tasks. + def self.rake_run(patterns) # :nodoc: + @rake_patterns = patterns + passed = run + exit passed unless passed + passed + end + + def self.plugin_rails_init(options) + self.run_with_rails_extension = true + + ENV["RAILS_ENV"] = options[:environment] || "test" + + unless run_with_autorun + patterns = defined?(@rake_patterns) ? @rake_patterns : options[:patterns] + ::Rails::TestRequirer.require_files(patterns) + end + + 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 + + # Disable the extra failure output after a run, unless output is deferred. + self.hide_aggregated_results = options[:output_inline] + + self.reporter << ::Rails::TestUnitReporter.new(options[:io], options) + end + + mattr_accessor(:run_with_autorun) { false } + mattr_accessor(:run_with_rails_extension) { false } +end + +Minitest.load_plugins +Minitest.extensions << 'rails' diff --git a/railties/lib/rails/test_unit/railtie.rb b/railties/lib/rails/test_unit/railtie.rb new file mode 100644 index 0000000000..75180ff978 --- /dev/null +++ b/railties/lib/rails/test_unit/railtie.rb @@ -0,0 +1,18 @@ +if defined?(Rake.application) && Rake.application.top_level_tasks.grep(/^(default$|test(:|$))/).any? + ENV['RAILS_ENV'] ||= 'test' +end + +module Rails + class TestUnitRailtie < Rails::Railtie + config.app_generators do |c| + c.test_framework :test_unit, fixture: true, + fixture_replacement: nil + + c.integration_tool :test_unit + end + + rake_tasks do + load "rails/test_unit/testing.rake" + end + end +end diff --git a/railties/lib/rails/test_unit/reporter.rb b/railties/lib/rails/test_unit/reporter.rb new file mode 100644 index 0000000000..00ea32d1b8 --- /dev/null +++ b/railties/lib/rails/test_unit/reporter.rb @@ -0,0 +1,74 @@ +require "active_support/core_ext/class/attribute" +require "minitest" + +module Rails + class TestUnitReporter < Minitest::StatisticsReporter + class_attribute :executable + self.executable = "bin/rails test" + + def record(result) + super + + if output_inline? && result.failure && (!result.skipped? || options[:verbose]) + io.puts + io.puts + io.puts result.failures.map(&:message) + io.puts + io.puts format_rerun_snippet(result) + io.puts + end + + if fail_fast? && result.failure && !result.error? && !result.skipped? + raise Interrupt + end + end + + def report + return if output_inline? || filtered_results.empty? + io.puts + io.puts "Failed tests:" + io.puts + io.puts aggregated_results + end + + def aggregated_results # :nodoc: + filtered_results.map { |result| format_rerun_snippet(result) }.join "\n" + end + + def filtered_results + if options[:verbose] + results + else + results.reject(&:skipped?) + end + end + + def relative_path_for(file) + file.sub(/^#{app_root}\/?/, '') + end + + private + def output_inline? + options[:output_inline] + end + + def fail_fast? + options[:fail_fast] + end + + def format_rerun_snippet(result) + # Try to extract path to assertion from backtrace. + if result.location =~ /\[(.*)\]\z/ + assertion_path = $1 + else + assertion_path = result.method(result.name).source_location.join(':') + end + + "#{self.executable} #{relative_path_for(assertion_path)}" + end + + def app_root + @app_root ||= defined?(ENGINE_ROOT) ? ENGINE_ROOT : Rails.root + 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..83d2c55ffd --- /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" + else + 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 new file mode 100644 index 0000000000..6676c6a079 --- /dev/null +++ b/railties/lib/rails/test_unit/testing.rake @@ -0,0 +1,45 @@ +gem 'minitest' +require 'minitest' +require 'rails/test_unit/minitest_plugin' + +task default: :test + +desc "Runs all tests in test folder" +task :test do + $: << "test" + Minitest.rake_run(["test"]) +end + +namespace :test do + task :prepare do + # Placeholder task for other Railtie and plugins to enhance. + # If used with Active Record, this task runs before the database schema is synchronized. + end + + task :run => %w[test] + + desc "Run tests quickly, but also reset db" + task :db => %w[db:test:prepare test] + + ["models", "helpers", "controllers", "mailers", "integration", "jobs"].each do |name| + task name => "test:prepare" do + $: << "test" + Minitest.rake_run(["test/#{name}"]) + end + end + + task :generators => "test:prepare" do + $: << "test" + Minitest.rake_run(["test/lib/generators"]) + end + + task :units => "test:prepare" do + $: << "test" + Minitest.rake_run(["test/models", "test/helpers", "test/unit"]) + end + + task :functionals => "test:prepare" do + $: << "test" + Minitest.rake_run(["test/controllers", "test/mailers", "test/functional"]) + end +end diff --git a/railties/lib/rails/version.rb b/railties/lib/rails/version.rb new file mode 100644 index 0000000000..df351c4238 --- /dev/null +++ b/railties/lib/rails/version.rb @@ -0,0 +1,8 @@ +require_relative 'gem_version' + +module Rails + # Returns the version of the currently loaded Rails as a string. + def self.version + VERSION::STRING + end +end diff --git a/railties/lib/rails/welcome_controller.rb b/railties/lib/rails/welcome_controller.rb new file mode 100644 index 0000000000..de9cd18b01 --- /dev/null +++ b/railties/lib/rails/welcome_controller.rb @@ -0,0 +1,8 @@ +require 'rails/application_controller' + +class Rails::WelcomeController < Rails::ApplicationController # :nodoc: + layout false + + def index + end +end diff --git a/railties/railties.gemspec b/railties/railties.gemspec new file mode 100644 index 0000000000..a06336f698 --- /dev/null +++ b/railties/railties.gemspec @@ -0,0 +1,34 @@ +version = File.read(File.expand_path('../../RAILS_VERSION', __FILE__)).strip + +Gem::Specification.new do |s| + s.platform = Gem::Platform::RUBY + s.name = 'railties' + s.version = version + s.summary = 'Tools for creating, working with, and running Rails applications.' + s.description = 'Rails internals: application bootup, plugins, generators, and rake tasks.' + + s.required_ruby_version = '>= 2.2.2' + + s.license = 'MIT' + + s.author = 'David Heinemeier Hansson' + s.email = 'david@loudthinking.com' + s.homepage = 'http://www.rubyonrails.org' + + s.files = Dir['CHANGELOG.md', 'README.rdoc', 'MIT-LICENSE', 'RDOC_MAIN.rdoc', 'exe/**/*', 'lib/**/{*,.[a-z]*}'] + s.require_path = 'lib' + + s.bindir = 'exe' + s.executables = ['rails'] + + s.rdoc_options << '--exclude' << '.' + + s.add_dependency 'activesupport', version + s.add_dependency 'actionpack', version + + s.add_dependency 'rake', '>= 0.8.7' + s.add_dependency 'thor', '>= 0.18.1', '< 2.0' + s.add_dependency 'method_source' + + s.add_development_dependency 'actionview', version +end diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb new file mode 100644 index 0000000000..794d180e5d --- /dev/null +++ b/railties/test/abstract_unit.rb @@ -0,0 +1,33 @@ +ENV["RAILS_ENV"] ||= "test" + +require File.expand_path("../../../load_paths", __FILE__) + +require 'stringio' +require 'active_support/testing/autorun' +require 'active_support/testing/stream' +require 'fileutils' + +require 'active_support' +require 'action_controller' +require 'action_view' +require 'rails/all' + +module TestApp + class Application < Rails::Application + config.root = File.dirname(__FILE__) + secrets.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33' + end +end + +# Skips the current run on Rubinius using Minitest::Assertions#skip +def rubinius_skip(message = '') + skip message if RUBY_ENGINE == 'rbx' +end +# Skips the current run on JRuby using Minitest::Assertions#skip +def jruby_skip(message = '') + skip message if defined?(JRUBY_VERSION) +end + +class ActiveSupport::TestCase + include ActiveSupport::Testing::Stream +end diff --git a/railties/test/app_loader_test.rb b/railties/test/app_loader_test.rb new file mode 100644 index 0000000000..5946c8fd4c --- /dev/null +++ b/railties/test/app_loader_test.rb @@ -0,0 +1,89 @@ +require 'tmpdir' +require 'abstract_unit' +require 'rails/app_loader' + +class AppLoaderTest < ActiveSupport::TestCase + def loader + @loader ||= Class.new do + extend Rails::AppLoader + + def self.exec_arguments + @exec_arguments + end + + def self.exec(*args) + @exec_arguments = args + end + end + end + + def write(filename, contents=nil) + FileUtils.mkdir_p(File.dirname(filename)) + File.write(filename, contents) + end + + def expects_exec(exe) + assert_equal [Rails::AppLoader::RUBY, exe], loader.exec_arguments + end + + setup do + @tmp = Dir.mktmpdir('railties-rails-loader-test-suite') + @cwd = Dir.pwd + Dir.chdir(@tmp) + end + + ['bin', 'script'].each do |script_dir| + exe = "#{script_dir}/rails" + + test "is not in a Rails application if #{exe} is not found in the current or parent directories" do + def loader.find_executables; end + + assert !loader.exec_app + end + + test "is not in a Rails application if #{exe} exists but is a folder" do + FileUtils.mkdir_p(exe) + + assert !loader.exec_app + end + + ['APP_PATH', 'ENGINE_PATH'].each do |keyword| + test "is in a Rails application if #{exe} exists and contains #{keyword}" do + write exe, keyword + + loader.exec_app + + expects_exec exe + end + + test "is not in a Rails application if #{exe} exists but doesn't contain #{keyword}" do + write exe + + assert !loader.exec_app + end + + test "is in a Rails application if parent directory has #{exe} containing #{keyword} and chdirs to the root directory" do + write "foo/bar/#{exe}" + write "foo/#{exe}", keyword + + Dir.chdir('foo/bar') + + loader.exec_app + + expects_exec exe + + # Compare the realpath in case either of them has symlinks. + # + # This happens in particular in Mac OS X, where @tmp starts + # with "/var", and Dir.pwd with "/private/var", due to a + # default system symlink var -> private/var. + assert_equal File.realpath("#@tmp/foo"), File.realpath(Dir.pwd) + end + end + end + + teardown do + Dir.chdir(@cwd) + FileUtils.rm_rf(@tmp) + end +end diff --git a/railties/test/application/asset_debugging_test.rb b/railties/test/application/asset_debugging_test.rb new file mode 100644 index 0000000000..8b83784ed6 --- /dev/null +++ b/railties/test/application/asset_debugging_test.rb @@ -0,0 +1,73 @@ +require 'isolation/abstract_unit' +require 'rack/test' + +module ApplicationTests + class AssetDebuggingTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + include Rack::Test::Methods + + def setup + # FIXME: shush Sass warning spam, not relevant to testing Railties + Kernel.silence_warnings do + build_app(initializers: true) + end + + app_file "app/assets/javascripts/application.js", "//= require_tree ." + app_file "app/assets/javascripts/xmlhr.js", "function f1() { alert(); }" + app_file "app/views/posts/index.html.erb", "<%= javascript_include_tag 'application' %>" + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get '/posts', to: "posts#index" + end + RUBY + + app_file "app/controllers/posts_controller.rb", <<-RUBY + class PostsController < ActionController::Base + end + RUBY + + ENV["RAILS_ENV"] = "production" + + boot_rails + end + + def teardown + teardown_app + end + + # FIXME: shush Sass warning spam, not relevant to testing Railties + def get(*) + Kernel.silence_warnings { super } + end + + test "assets are concatenated when debug is off and compile is off either if debug_assets param is provided" do + # config.assets.debug and config.assets.compile are false for production environment + ENV["RAILS_ENV"] = "production" + output = Dir.chdir(app_path){ `bin/rake assets:precompile --trace 2>&1` } + assert $?.success?, output + + # Load app env + app "production" + + class ::PostsController < ActionController::Base ; end + + # the debug_assets params isn't used if compile is off + get '/posts?debug_assets=true' + assert_match(/<script src="\/assets\/application-([0-z]+)\.js"><\/script>/, last_response.body) + assert_no_match(/<script src="\/assets\/xmlhr-([0-z]+)\.js"><\/script>/, last_response.body) + end + + test "assets are served with sourcemaps when compile is true and debug_assets params is true" do + add_to_env_config "production", "config.assets.compile = true" + + # Load app env + app "production" + + class ::PostsController < ActionController::Base ; end + + get '/posts?debug_assets=true' + assert_match(/<script src="\/assets\/application(\.debug)?-([0-z]+)\.js"><\/script>/, last_response.body) + end + end +end diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb new file mode 100644 index 0000000000..18882e1855 --- /dev/null +++ b/railties/test/application/assets_test.rb @@ -0,0 +1,504 @@ +require 'isolation/abstract_unit' +require 'rack/test' +require 'active_support/json' + +module ApplicationTests + class AssetsTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + include Rack::Test::Methods + + def setup + build_app(initializers: true) + boot_rails + end + + def teardown + teardown_app + end + + def precompile!(env = nil) + with_env env.to_h do + quietly do + precompile_task = "bin/rake assets:precompile --trace 2>&1" + output = Dir.chdir(app_path) { %x[ #{precompile_task} ] } + assert $?.success?, output + output + end + end + end + + def with_env(env) + env.each { |k, v| ENV[k.to_s] = v } + yield + ensure + env.each_key { |k| ENV.delete k.to_s } + end + + def clean_assets! + quietly do + assert Dir.chdir(app_path) { system('bin/rake assets:clobber') } + end + end + + def assert_file_exists(filename) + globbed = Dir[filename] + assert globbed.one?, "Found #{globbed.size} files matching #{filename}. All files in the directory: #{Dir.entries(File.dirname(filename)).inspect}" + end + + def assert_no_file_exists(filename) + assert !File.exist?(filename), "#{filename} does exist" + end + + test "assets routes have higher priority" do + app_file "app/assets/images/rails.png", "notactuallyapng" + app_file "app/assets/javascripts/demo.js.erb", "a = <%= image_path('rails.png').inspect %>;" + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get '*path', to: lambda { |env| [200, { "Content-Type" => "text/html" }, ["Not an asset"]] } + end + RUBY + + add_to_env_config "development", "config.assets.digest = false" + + # FIXME: shush Sass warning spam, not relevant to testing Railties + Kernel.silence_warnings do + require "#{app_path}/config/environment" + end + + get "/assets/demo.js" + assert_equal 'a = "/assets/rails.png";', last_response.body.strip + end + + test "assets do not require compressors until it is used" do + app_file "app/assets/javascripts/demo.js.erb", "<%= :alert %>();" + add_to_env_config "production", "config.assets.compile = true" + add_to_env_config "production", "config.assets.precompile = []" + + # Load app env + app "production" + + assert !defined?(Uglifier) + get "/assets/demo.js" + assert_match "alert()", last_response.body + assert defined?(Uglifier) + end + + test "precompile creates the file, gives it the original asset's content and run in production as default" do + app_file "app/assets/config/manifest.js", "//= link_tree ../javascripts" + app_file "app/assets/javascripts/application.js", "alert();" + app_file "app/assets/javascripts/foo/application.js", "alert();" + + precompile! + + files = Dir["#{app_path}/public/assets/application-*.js"] + files << Dir["#{app_path}/public/assets/foo/application-*.js"].first + files.each do |file| + assert_not_nil file, "Expected application.js asset to be generated, but none found" + assert_equal "alert();\n", File.read(file) + end + end + + def test_precompile_does_not_hit_the_database + app_file "app/assets/config/manifest.js", "//= link_tree ../javascripts" + app_file "app/assets/javascripts/application.js", "alert();" + app_file "app/assets/javascripts/foo/application.js", "alert();" + app_file "app/controllers/users_controller.rb", <<-eoruby + class UsersController < ApplicationController; end + eoruby + app_file "app/models/user.rb", <<-eoruby + class User < ActiveRecord::Base; raise 'should not be reached'; end + eoruby + + precompile! \ + RAILS_ENV: 'production', + DATABASE_URL: 'postgresql://baduser:badpass@127.0.0.1/dbname' + + files = Dir["#{app_path}/public/assets/application-*.js"] + files << Dir["#{app_path}/public/assets/foo/application-*.js"].first + files.each do |file| + assert_not_nil file, "Expected application.js asset to be generated, but none found" + assert_equal "alert();".strip, File.read(file).strip + end + end + + test "precompile application.js and application.css and all other non JS/CSS files" do + app_file "app/assets/javascripts/application.js", "alert();" + app_file "app/assets/stylesheets/application.css", "body{}" + + app_file "app/assets/javascripts/someapplication.js", "alert();" + app_file "app/assets/stylesheets/someapplication.css", "body{}" + + app_file "app/assets/javascripts/something.min.js", "alert();" + app_file "app/assets/stylesheets/something.min.css", "body{}" + + app_file "app/assets/javascripts/something.else.js.erb", "alert();" + app_file "app/assets/stylesheets/something.else.css.erb", "body{}" + + images_should_compile = ["a.png", "happyface.png", "happy_face.png", "happy.face.png", + "happy-face.png", "happy.happy_face.png", "happy_happy.face.png", + "happy.happy.face.png", "-happy.png", "-happy.face.png", + "_happy.face.png", "_happy.png"] + + images_should_compile.each do |filename| + app_file "app/assets/images/#{filename}", "happy" + end + + precompile! + + images_should_compile = ["a-*.png", "happyface-*.png", "happy_face-*.png", "happy.face-*.png", + "happy-face-*.png", "happy.happy_face-*.png", "happy_happy.face-*.png", + "happy.happy.face-*.png", "-happy-*.png", "-happy.face-*.png", + "_happy.face-*.png", "_happy-*.png"] + + images_should_compile.each do |filename| + assert_file_exists("#{app_path}/public/assets/#{filename}") + end + + assert_file_exists("#{app_path}/public/assets/application-*.js") + assert_file_exists("#{app_path}/public/assets/application-*.css") + + assert_no_file_exists("#{app_path}/public/assets/someapplication-*.js") + assert_no_file_exists("#{app_path}/public/assets/someapplication-*.css") + + assert_no_file_exists("#{app_path}/public/assets/something.min-*.js") + assert_no_file_exists("#{app_path}/public/assets/something.min-*.css") + + assert_no_file_exists("#{app_path}/public/assets/something.else-*.js") + assert_no_file_exists("#{app_path}/public/assets/something.else-*.css") + end + + test "precompile something.js for directory containing index file" do + add_to_config "config.assets.precompile = [ 'something.js' ]" + app_file "app/assets/javascripts/something/index.js.erb", "alert();" + + precompile! + + assert_file_exists("#{app_path}/public/assets/something-*.js") + end + + test 'precompile use assets defined in app env config' do + add_to_env_config 'production', 'config.assets.precompile = [ "something.js" ]' + app_file 'app/assets/javascripts/something.js.erb', 'alert();' + + precompile! RAILS_ENV: 'production' + + assert_file_exists("#{app_path}/public/assets/something-*.js") + end + + test 'precompile use assets defined in app config and reassigned in app env config' do + add_to_config 'config.assets.precompile = [ "something_manifest.js" ]' + add_to_env_config 'production', 'config.assets.precompile += [ "another_manifest.js" ]' + + app_file 'app/assets/config/something_manifest.js', '//= link something.js' + app_file 'app/assets/config/another_manifest.js', '//= link another.js' + + app_file 'app/assets/javascripts/something.js.erb', 'alert();' + app_file 'app/assets/javascripts/another.js.erb', 'alert();' + + precompile! RAILS_ENV: 'production' + + assert_file_exists("#{app_path}/public/assets/something_manifest-*.js") + assert_file_exists("#{app_path}/public/assets/something-*.js") + assert_file_exists("#{app_path}/public/assets/another_manifest-*.js") + assert_file_exists("#{app_path}/public/assets/another-*.js") + end + + test "asset pipeline should use a Sprockets::CachedEnvironment when config.assets.digest is true" do + add_to_config "config.action_controller.perform_caching = false" + add_to_env_config "production", "config.assets.compile = true" + + # Load app env + app "production" + + assert_equal Sprockets::CachedEnvironment, Rails.application.assets.class + end + + test "precompile creates a manifest file with all the assets listed" do + app_file "app/assets/images/rails.png", "notactuallyapng" + app_file "app/assets/stylesheets/application.css.erb", "<%= asset_path('rails.png') %>" + app_file "app/assets/javascripts/application.js", "alert();" + + precompile! + + manifest = Dir["#{app_path}/public/assets/.sprockets-manifest-*.json"].first + assets = ActiveSupport::JSON.decode(File.read(manifest)) + assert_match(/application-([0-z]+)\.js/, assets["assets"]["application.js"]) + assert_match(/application-([0-z]+)\.css/, assets["assets"]["application.css"]) + end + + test "the manifest file should be saved by default in the same assets folder" do + app_file "app/assets/javascripts/application.js", "alert();" + add_to_config "config.assets.prefix = '/x'" + + precompile! + + manifest = Dir["#{app_path}/public/x/.sprockets-manifest-*.json"].first + assets = ActiveSupport::JSON.decode(File.read(manifest)) + assert_match(/application-([0-z]+)\.js/, assets["assets"]["application.js"]) + end + + test "assets do not require any assets group gem when manifest file is present" do + app_file "app/assets/javascripts/application.js", "alert();" + add_to_env_config "production", "config.public_file_server.enabled = true" + + precompile! RAILS_ENV: 'production' + + manifest = Dir["#{app_path}/public/assets/.sprockets-manifest-*.json"].first + assets = ActiveSupport::JSON.decode(File.read(manifest)) + asset_path = assets["assets"]["application.js"] + + # Load app env + app "production" + + # Checking if Uglifier is defined we can know if Sprockets was reached or not + assert !defined?(Uglifier) + get "/assets/#{asset_path}" + assert_match "alert()", last_response.body + assert !defined?(Uglifier) + end + + test "precompile properly refers files referenced with asset_path" do + app_file "app/assets/images/rails.png", "notactuallyapng" + app_file "app/assets/stylesheets/application.css.erb", "p { background-image: url(<%= asset_path('rails.png') %>) }" + + precompile! + + file = Dir["#{app_path}/public/assets/application-*.css"].first + assert_match(/\/assets\/rails-([0-z]+)\.png/, File.read(file)) + end + + test "precompile shouldn't use the digests present in manifest.json" do + app_file "app/assets/images/rails.png", "notactuallyapng" + + app_file "app/assets/stylesheets/application.css.erb", "p { background-image: url(<%= asset_path('rails.png') %>) }" + + precompile! RAILS_ENV: 'production' + + manifest = Dir["#{app_path}/public/assets/.sprockets-manifest-*.json"].first + assets = ActiveSupport::JSON.decode(File.read(manifest)) + asset_path = assets["assets"]["application.css"] + + app_file "app/assets/images/rails.png", "p { url: change }" + + precompile! + + assets = ActiveSupport::JSON.decode(File.read(manifest)) + assert_not_equal asset_path, assets["assets"]["application.css"] + end + + test "precompile appends the md5 hash to files referenced with asset_path and run in production with digest true" do + app_file "app/assets/images/rails.png", "notactuallyapng" + app_file "app/assets/stylesheets/application.css.erb", "p { background-image: url(<%= asset_path('rails.png') %>) }" + + precompile! RAILS_ENV: 'production' + + file = Dir["#{app_path}/public/assets/application-*.css"].first + assert_match(/\/assets\/rails-([0-z]+)\.png/, File.read(file)) + end + + test "precompile should handle utf8 filenames" do + filename = "レイルズ.png" + app_file "app/assets/images/#{filename}", "not an image really" + app_file "app/assets/config/manifest.js", "//= link_tree ../images" + add_to_config "config.assets.precompile = %w(manifest.js)" + + precompile! + + manifest = Dir["#{app_path}/public/assets/.sprockets-manifest-*.json"].first + assets = ActiveSupport::JSON.decode(File.read(manifest)) + assert asset_path = assets["assets"].find { |(k, _)| k && k =~ /.png/ }[1] + + # Load app env + app "development" + + get "/assets/#{URI.parser.escape(asset_path)}" + assert_match "not an image really", last_response.body + assert_file_exists("#{app_path}/public/assets/#{asset_path}") + end + + test "assets are cleaned up properly" do + app_file "public/assets/application.js", "alert();" + app_file "public/assets/application.css", "a { color: green; }" + app_file "public/assets/subdir/broken.png", "not really an image file" + + clean_assets! + + files = Dir["#{app_path}/public/assets/**/*", "#{app_path}/tmp/cache/assets/development/*", + "#{app_path}/tmp/cache/assets/test/*", "#{app_path}/tmp/cache/assets/production/*"] + assert_equal 0, files.length, "Expected no assets, but found #{files.join(', ')}" + end + + test "assets routes are not drawn when compilation is disabled" do + app_file "app/assets/javascripts/demo.js.erb", "<%= :alert %>();" + add_to_config "config.assets.compile = false" + + # Load app env + app "production" + + get "/assets/demo.js" + assert_equal 404, last_response.status + end + + test "does not stream session cookies back" do + app_file "app/assets/javascripts/demo.js.erb", "<%= :alert %>();" + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get '/omg', :to => "omg#index" + end + RUBY + + add_to_env_config "development", "config.assets.digest = false" + + # Load app env + app "development" + + class ::OmgController < ActionController::Base + def index + flash[:cool_story] = true + render text: "ok" + end + end + + get "/omg" + assert_equal 'ok', last_response.body + + get "/assets/demo.js" + assert_match "alert()", last_response.body + assert_equal nil, last_response.headers["Set-Cookie"] + end + + test "files in any assets/ directories are not added to Sprockets" do + %w[app lib vendor].each do |dir| + app_file "#{dir}/assets/#{dir}_test.erb", "testing" + end + + app_file "app/assets/javascripts/demo.js", "alert();" + + add_to_env_config "development", "config.assets.digest = false" + + # Load app env + app "development" + + get "/assets/demo.js" + assert_match "alert();", last_response.body + assert_equal 200, last_response.status + end + + test "assets are concatenated when debug is off and compile is off either if debug_assets param is provided" do + app_with_assets_in_view + + # config.assets.debug and config.assets.compile are false for production environment + precompile! RAILS_ENV: 'production' + + # Load app env + app "production" + + class ::PostsController < ActionController::Base ; end + + # the debug_assets params isn't used if compile is off + get '/posts?debug_assets=true' + assert_match(/<script src="\/assets\/application-([0-z]+)\.js"><\/script>/, last_response.body) + assert_no_match(/<script src="\/assets\/xmlhr-([0-z]+)\.js"><\/script>/, last_response.body) + end + + test "assets can access model information when precompiling" do + app_file "app/models/post.rb", "class Post; end" + app_file "app/assets/javascripts/application.js", "//= require_tree ." + app_file "app/assets/javascripts/xmlhr.js.erb", "<%= Post.name %>" + + precompile! + + assert_equal "Post\n;\n", File.read(Dir["#{app_path}/public/assets/application-*.js"].first) + end + + test "initialization on the assets group should set assets_dir" do + require "#{app_path}/config/application" + Rails.application.initialize!(:assets) + assert_not_nil Rails.application.config.action_controller.assets_dir + end + + test "enhancements to assets:precompile should only run once" do + app_file "lib/tasks/enhance.rake", "Rake::Task['assets:precompile'].enhance { puts 'enhancement' }" + output = precompile! + assert_equal 1, output.scan("enhancement").size + end + + test "digested assets are not mistakenly removed" do + app_file "app/assets/application.js", "alert();" + add_to_config "config.assets.compile = true" + + precompile! + + files = Dir["#{app_path}/public/assets/application-*.js"] + assert_equal 1, files.length, "Expected digested application.js asset to be generated, but none found" + end + + test "digested assets are removed from configured path" do + app_file "public/production_assets/application.js", "alert();" + add_to_env_config "production", "config.assets.prefix = 'production_assets'" + + ENV["RAILS_ENV"] = nil + + clean_assets! + + files = Dir["#{app_path}/public/production_assets/application-*.js"] + assert_equal 0, files.length, "Expected application.js asset to be removed, but still exists" + end + + test "asset urls should use the request's protocol by default" do + app_with_assets_in_view + add_to_config "config.asset_host = 'example.com'" + add_to_env_config "development", "config.assets.digest = false" + + # Load app env + app "development" + + class ::PostsController < ActionController::Base; end + + get '/posts', {}, {'HTTPS'=>'off'} + assert_match('src="http://example.com/assets/application.debug.js', last_response.body) + get '/posts', {}, {'HTTPS'=>'on'} + assert_match('src="https://example.com/assets/application.debug.js', last_response.body) + end + + test "asset urls should be protocol-relative if no request is in scope" do + app_file "app/assets/images/rails.png", "notreallyapng" + app_file "app/assets/javascripts/image_loader.js.erb", "var src='<%= image_path('rails.png') %>';" + add_to_config "config.assets.precompile = %w{rails.png image_loader.js}" + add_to_config "config.asset_host = 'example.com'" + add_to_env_config "development", "config.assets.digest = false" + + precompile! + + assert_match "src='//example.com/assets/rails.png'", File.read(Dir["#{app_path}/public/assets/image_loader-*.js"].first) + end + + test "asset paths should use RAILS_RELATIVE_URL_ROOT by default" do + ENV["RAILS_RELATIVE_URL_ROOT"] = "/sub/uri" + app_file "app/assets/images/rails.png", "notreallyapng" + app_file "app/assets/javascripts/app.js.erb", "var src='<%= image_path('rails.png') %>';" + add_to_config "config.assets.precompile = %w{rails.png app.js}" + add_to_env_config "development", "config.assets.digest = false" + + precompile! + + assert_match "src='/sub/uri/assets/rails.png'", File.read(Dir["#{app_path}/public/assets/app-*.js"].first) + end + + private + + def app_with_assets_in_view + app_file "app/assets/javascripts/application.js", "//= require_tree ." + app_file "app/assets/javascripts/xmlhr.js", "function f1() { alert(); }" + app_file "app/views/posts/index.html.erb", "<%= javascript_include_tag 'application' %>" + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get '/posts', :to => "posts#index" + end + RUBY + end + end +end diff --git a/railties/test/application/bin_setup_test.rb b/railties/test/application/bin_setup_test.rb new file mode 100644 index 0000000000..1bdced02e9 --- /dev/null +++ b/railties/test/application/bin_setup_test.rb @@ -0,0 +1,54 @@ +require 'isolation/abstract_unit' + +module ApplicationTests + class BinSetupTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + end + + def teardown + teardown_app + end + + def test_bin_setup + Dir.chdir(app_path) do + app_file 'db/schema.rb', <<-RUBY + ActiveRecord::Schema.define(version: 20140423102712) do + create_table(:articles) {} + end + RUBY + + list_tables = lambda { `bin/rails runner 'p ActiveRecord::Base.connection.tables'`.strip } + File.write("log/my.log", "zomg!") + + assert_equal '[]', list_tables.call + assert_equal 5, File.size("log/my.log") + assert_not File.exist?("tmp/restart.txt") + `bin/setup 2>&1` + assert_equal 0, File.size("log/my.log") + assert_equal '["articles", "schema_migrations"]', list_tables.call + assert File.exist?("tmp/restart.txt") + end + end + + def test_bin_setup_output + Dir.chdir(app_path) do + app_file 'db/schema.rb', "" + + output = `bin/setup 2>&1` + assert_equal(<<-OUTPUT, output) +== Installing dependencies == +The Gemfile's dependencies are satisfied + +== Preparing database == + +== Removing old logs and tempfiles == + +== Restarting application server == + OUTPUT + end + end + end +end diff --git a/railties/test/application/configuration/custom_test.rb b/railties/test/application/configuration/custom_test.rb new file mode 100644 index 0000000000..28b3b2f2d6 --- /dev/null +++ b/railties/test/application/configuration/custom_test.rb @@ -0,0 +1,54 @@ +require 'isolation/abstract_unit' + +module ApplicationTests + module ConfigurationTests + class CustomTest < ActiveSupport::TestCase + def setup + build_app + boot_rails + FileUtils.rm_rf("#{app_path}/config/environments") + end + + def teardown + teardown_app + FileUtils.rm_rf(new_app) if File.directory?(new_app) + end + + test 'access custom configuration point' do + add_to_config <<-RUBY + config.x.payment_processing.schedule = :daily + config.x.payment_processing.retries = 3 + config.x.super_debugger = true + config.x.hyper_debugger = false + config.x.nil_debugger = nil + RUBY + require_environment + + x = Rails.configuration.x + assert_equal :daily, x.payment_processing.schedule + assert_equal 3, x.payment_processing.retries + assert_equal true, x.super_debugger + assert_equal false, x.hyper_debugger + assert_equal nil, x.nil_debugger + assert_nil x.i_do_not_exist.zomg + end + + private + def new_app + File.expand_path("#{app_path}/../new_app") + end + + def copy_app + FileUtils.cp_r(app_path, new_app) + end + + def app + @app ||= Rails.application + end + + def require_environment + require "#{app_path}/config/environment" + end + end + end +end diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb new file mode 100644 index 0000000000..49f63d5d31 --- /dev/null +++ b/railties/test/application/configuration_test.rb @@ -0,0 +1,1437 @@ +require "isolation/abstract_unit" +require 'rack/test' +require 'env_helpers' + +class ::MyMailInterceptor + def self.delivering_email(email); email; end +end + +class ::MyOtherMailInterceptor < ::MyMailInterceptor; end + +class ::MyPreviewMailInterceptor + def self.previewing_email(email); email; end +end + +class ::MyOtherPreviewMailInterceptor < ::MyPreviewMailInterceptor; end + +class ::MyMailObserver + def self.delivered_email(email); email; end +end + +class ::MyOtherMailObserver < ::MyMailObserver; end + +module ApplicationTests + class ConfigurationTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + include Rack::Test::Methods + include EnvHelpers + + def new_app + File.expand_path("#{app_path}/../new_app") + end + + def copy_app + FileUtils.cp_r(app_path, new_app) + end + + def app(env = 'development') + @app ||= begin + ENV['RAILS_ENV'] = env + + # FIXME: shush Sass warning spam, not relevant to testing Railties + Kernel.silence_warnings do + require "#{app_path}/config/environment" + end + + Rails.application + ensure + ENV.delete 'RAILS_ENV' + end + end + + def setup + build_app + boot_rails + supress_default_config + end + + def teardown + teardown_app + FileUtils.rm_rf(new_app) if File.directory?(new_app) + end + + def supress_default_config + FileUtils.mv("#{app_path}/config/environments", "#{app_path}/config/__environments__") + end + + def restore_default_config + FileUtils.rm_rf("#{app_path}/config/environments") + FileUtils.mv("#{app_path}/config/__environments__", "#{app_path}/config/environments") + end + + test "Rails.env does not set the RAILS_ENV environment variable which would leak out into rake tasks" do + require "rails" + + switch_env "RAILS_ENV", nil do + Rails.env = "development" + assert_equal "development", Rails.env + assert_nil ENV['RAILS_ENV'] + end + end + + test "lib dir is on LOAD_PATH during config" do + app_file 'lib/my_logger.rb', <<-RUBY + require "logger" + class MyLogger < ::Logger + end + RUBY + add_to_top_of_config <<-RUBY + require 'my_logger' + config.logger = MyLogger.new STDOUT + RUBY + + app 'development' + + assert_equal 'MyLogger', Rails.application.config.logger.class.name + end + + test "a renders exception on pending migration" do + add_to_config <<-RUBY + config.active_record.migration_error = :page_load + config.consider_all_requests_local = true + config.action_dispatch.show_exceptions = true + RUBY + + app_file 'db/migrate/20140708012246_create_user.rb', <<-RUBY + class CreateUser < ActiveRecord::Migration + def change + create_table :users + end + end + RUBY + + app 'development' + + ActiveRecord::Migrator.migrations_paths = ["#{app_path}/db/migrate"] + + begin + get "/foo" + assert_equal 500, last_response.status + assert_match "ActiveRecord::PendingMigrationError", last_response.body + ensure + ActiveRecord::Migrator.migrations_paths = nil + end + end + + test "Rails.groups returns available groups" do + require "rails" + + Rails.env = "development" + assert_equal [:default, "development"], Rails.groups + assert_equal [:default, "development", :assets], Rails.groups(assets: [:development]) + assert_equal [:default, "development", :another, :assets], Rails.groups(:another, assets: %w(development)) + + Rails.env = "test" + assert_equal [:default, "test"], Rails.groups(assets: [:development]) + + ENV["RAILS_GROUPS"] = "javascripts,stylesheets" + assert_equal [:default, "test", "javascripts", "stylesheets"], Rails.groups + end + + test "Rails.application is nil until app is initialized" do + require 'rails' + assert_nil Rails.application + app 'development' + assert_equal AppTemplate::Application.instance, Rails.application + end + + test "Rails.application responds to all instance methods" do + app 'development' + assert_respond_to Rails.application, :routes_reloader + assert_equal Rails.application.routes_reloader, AppTemplate::Application.routes_reloader + end + + test "Rails::Application responds to paths" do + app 'development' + assert_respond_to AppTemplate::Application, :paths + assert_equal ["#{app_path}/app/views"], AppTemplate::Application.paths["app/views"].expanded + end + + test "the application root is set correctly" do + app 'development' + assert_equal Pathname.new(app_path), Rails.application.root + end + + test "the application root can be seen from the application singleton" do + app 'development' + assert_equal Pathname.new(app_path), AppTemplate::Application.root + end + + test "the application root can be set" do + copy_app + add_to_config <<-RUBY + config.root = '#{new_app}' + RUBY + + use_frameworks [] + + app 'development' + + assert_equal Pathname.new(new_app), Rails.application.root + end + + test "the application root is Dir.pwd if there is no config.ru" do + File.delete("#{app_path}/config.ru") + + use_frameworks [] + + Dir.chdir("#{app_path}") do + app 'development' + assert_equal Pathname.new("#{app_path}"), Rails.application.root + end + end + + test "Rails.root should be a Pathname" do + add_to_config <<-RUBY + config.root = "#{app_path}" + RUBY + + app 'development' + + assert_instance_of Pathname, Rails.root + end + + test "Rails.public_path should be a Pathname" do + add_to_config <<-RUBY + config.paths["public"] = "somewhere" + RUBY + + app 'development' + + assert_instance_of Pathname, Rails.public_path + end + + test "initialize an eager loaded, cache classes app" do + add_to_config <<-RUBY + config.eager_load = true + config.cache_classes = true + RUBY + + app 'development' + + assert_equal :require, ActiveSupport::Dependencies.mechanism + end + + test "application is always added to eager_load namespaces" do + app 'development' + assert_includes Rails.application.config.eager_load_namespaces, AppTemplate::Application + end + + test "the application can be eager loaded even when there are no frameworks" do + FileUtils.rm_rf("#{app_path}/config/environments") + add_to_config <<-RUBY + config.eager_load = true + config.cache_classes = true + RUBY + + use_frameworks [] + + assert_nothing_raised do + app 'development' + end + end + + test "filter_parameters should be able to set via config.filter_parameters" do + add_to_config <<-RUBY + config.filter_parameters += [ :foo, 'bar', lambda { |key, value| + value = value.reverse if key =~ /baz/ + }] + RUBY + + assert_nothing_raised do + app 'development' + end + end + + test "filter_parameters should be able to set via config.filter_parameters in an initializer" do + app_file 'config/initializers/filter_parameters_logging.rb', <<-RUBY + Rails.application.config.filter_parameters += [ :password, :foo, 'bar' ] + RUBY + + app 'development' + + assert_equal [:password, :foo, 'bar'], Rails.application.env_config['action_dispatch.parameter_filter'] + end + + test "config.to_prepare is forwarded to ActionDispatch" do + $prepared = false + + add_to_config <<-RUBY + config.to_prepare do + $prepared = true + end + RUBY + + assert !$prepared + + app 'development' + + get "/" + assert $prepared + end + + def assert_utf8 + assert_equal Encoding::UTF_8, Encoding.default_external + assert_equal Encoding::UTF_8, Encoding.default_internal + end + + test "skipping config.encoding still results in 'utf-8' as the default" do + app 'development' + assert_utf8 + end + + test "config.encoding sets the default encoding" do + add_to_config <<-RUBY + config.encoding = "utf-8" + RUBY + + app 'development' + assert_utf8 + end + + test "config.paths.public sets Rails.public_path" do + add_to_config <<-RUBY + config.paths["public"] = "somewhere" + RUBY + + app 'development' + assert_equal Pathname.new(app_path).join("somewhere"), Rails.public_path + end + + test "In production mode, config.public_file_server.enabled is off by default" do + restore_default_config + + with_rails_env "production" do + app 'production' + assert_not app.config.public_file_server.enabled + end + end + + test "In production mode, config.public_file_server.enabled is enabled when RAILS_SERVE_STATIC_FILES is set" do + restore_default_config + + with_rails_env "production" do + switch_env "RAILS_SERVE_STATIC_FILES", "1" do + app 'production' + assert app.config.public_file_server.enabled + end + end + end + + test "In production mode, config.public_file_server.enabled is disabled when RAILS_SERVE_STATIC_FILES is blank" do + restore_default_config + + with_rails_env "production" do + switch_env "RAILS_SERVE_STATIC_FILES", " " do + app 'production' + assert_not app.config.public_file_server.enabled + end + end + end + + test "config.serve_static_files is deprecated" do + make_basic_app do |application| + assert_deprecated do + application.config.serve_static_files = true + end + + assert application.config.public_file_server.enabled + end + end + + test "config.static_cache_control is deprecated" do + make_basic_app do |application| + assert_deprecated do + application.config.static_cache_control = "public, max-age=60" + end + + assert_equal application.config.static_cache_control, "public, max-age=60" + end + end + + test "Use key_generator when secret_key_base is set" do + make_basic_app do |application| + application.secrets.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33' + application.config.session_store :disabled + end + + class ::OmgController < ActionController::Base + def index + cookies.signed[:some_key] = "some_value" + render text: cookies[:some_key] + end + end + + get "/" + + secret = app.key_generator.generate_key('signed cookie') + verifier = ActiveSupport::MessageVerifier.new(secret) + assert_equal 'some_value', verifier.verify(last_response.body) + end + + test "application verifier can be used in the entire application" do + make_basic_app do |application| + application.secrets.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33' + application.config.session_store :disabled + end + + message = app.message_verifier(:sensitive_value).generate("some_value") + + assert_equal 'some_value', Rails.application.message_verifier(:sensitive_value).verify(message) + + secret = app.key_generator.generate_key('sensitive_value') + verifier = ActiveSupport::MessageVerifier.new(secret) + assert_equal 'some_value', verifier.verify(message) + end + + test "application message verifier can be used when the key_generator is ActiveSupport::LegacyKeyGenerator" do + app_file 'config/initializers/secret_token.rb', <<-RUBY + Rails.application.config.secret_token = "b3c631c314c0bbca50c1b2843150fe33" + RUBY + app_file 'config/secrets.yml', <<-YAML + development: + secret_key_base: + YAML + + app 'development' + + assert_equal app.env_config['action_dispatch.key_generator'], Rails.application.key_generator + assert_equal app.env_config['action_dispatch.key_generator'].class, ActiveSupport::LegacyKeyGenerator + message = app.message_verifier(:sensitive_value).generate("some_value") + assert_equal 'some_value', Rails.application.message_verifier(:sensitive_value).verify(message) + end + + test "warns when secrets.secret_key_base is blank and config.secret_token is set" do + app_file 'config/initializers/secret_token.rb', <<-RUBY + Rails.application.config.secret_token = "b3c631c314c0bbca50c1b2843150fe33" + RUBY + app_file 'config/secrets.yml', <<-YAML + development: + secret_key_base: + YAML + + app 'development' + + assert_deprecated(/You didn't set `secret_key_base`./) do + app.env_config + end + end + + test "raise when secrets.secret_key_base is not a type of string" do + app_file 'config/secrets.yml', <<-YAML + development: + secret_key_base: 123 + YAML + + app 'development' + + assert_raise(ArgumentError) do + app.key_generator + end + end + + test "prefer secrets.secret_token over config.secret_token" do + app_file 'config/initializers/secret_token.rb', <<-RUBY + Rails.application.config.secret_token = "" + RUBY + app_file 'config/secrets.yml', <<-YAML + development: + secret_token: 3b7cd727ee24e8444053437c36cc66c3 + YAML + + app 'development' + + assert_equal '3b7cd727ee24e8444053437c36cc66c3', app.secrets.secret_token + end + + test "application verifier can build different verifiers" do + make_basic_app do |application| + application.secrets.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33' + application.config.session_store :disabled + end + + default_verifier = app.message_verifier(:sensitive_value) + text_verifier = app.message_verifier(:text) + + message = text_verifier.generate('some_value') + + assert_equal 'some_value', text_verifier.verify(message) + assert_raises ActiveSupport::MessageVerifier::InvalidSignature do + default_verifier.verify(message) + end + + assert_equal default_verifier.object_id, app.message_verifier(:sensitive_value).object_id + assert_not_equal default_verifier.object_id, text_verifier.object_id + end + + test "secrets.secret_key_base is used when config/secrets.yml is present" do + app_file 'config/secrets.yml', <<-YAML + development: + secret_key_base: 3b7cd727ee24e8444053437c36cc66c3 + YAML + + app 'development' + assert_equal '3b7cd727ee24e8444053437c36cc66c3', app.secrets.secret_key_base + end + + test "secret_key_base is copied from config to secrets when not set" do + remove_file "config/secrets.yml" + app_file 'config/initializers/secret_token.rb', <<-RUBY + Rails.application.config.secret_key_base = "3b7cd727ee24e8444053437c36cc66c3" + RUBY + + app 'development' + assert_equal '3b7cd727ee24e8444053437c36cc66c3', app.secrets.secret_key_base + end + + test "config.secret_token over-writes a blank secrets.secret_token" do + app_file 'config/initializers/secret_token.rb', <<-RUBY + Rails.application.config.secret_token = "b3c631c314c0bbca50c1b2843150fe33" + RUBY + app_file 'config/secrets.yml', <<-YAML + development: + secret_key_base: + secret_token: + YAML + + app 'development' + + assert_equal 'b3c631c314c0bbca50c1b2843150fe33', app.secrets.secret_token + assert_equal 'b3c631c314c0bbca50c1b2843150fe33', app.config.secret_token + end + + test "custom secrets saved in config/secrets.yml are loaded in app secrets" do + app_file 'config/secrets.yml', <<-YAML + development: + secret_key_base: 3b7cd727ee24e8444053437c36cc66c3 + aws_access_key_id: myamazonaccesskeyid + aws_secret_access_key: myamazonsecretaccesskey + YAML + + app 'development' + + assert_equal 'myamazonaccesskeyid', app.secrets.aws_access_key_id + assert_equal 'myamazonsecretaccesskey', app.secrets.aws_secret_access_key + end + + test "blank config/secrets.yml does not crash the loading process" do + app_file 'config/secrets.yml', <<-YAML + YAML + + app 'development' + + assert_nil app.secrets.not_defined + end + + test "config.secret_key_base over-writes a blank secrets.secret_key_base" do + app_file 'config/initializers/secret_token.rb', <<-RUBY + Rails.application.config.secret_key_base = "iaminallyoursecretkeybase" + RUBY + app_file 'config/secrets.yml', <<-YAML + development: + secret_key_base: + YAML + + app 'development' + + assert_equal "iaminallyoursecretkeybase", app.secrets.secret_key_base + end + + test "uses ActiveSupport::LegacyKeyGenerator as app.key_generator when secrets.secret_key_base is blank" do + app_file 'config/initializers/secret_token.rb', <<-RUBY + Rails.application.config.secret_token = "b3c631c314c0bbca50c1b2843150fe33" + RUBY + app_file 'config/secrets.yml', <<-YAML + development: + secret_key_base: + YAML + + app 'development' + + assert_equal 'b3c631c314c0bbca50c1b2843150fe33', app.config.secret_token + assert_equal nil, app.secrets.secret_key_base + assert_equal app.key_generator.class, ActiveSupport::LegacyKeyGenerator + end + + test "uses ActiveSupport::LegacyKeyGenerator with config.secret_token as app.key_generator when secrets.secret_key_base is blank" do + app_file 'config/initializers/secret_token.rb', <<-RUBY + Rails.application.config.secret_token = "" + RUBY + app_file 'config/secrets.yml', <<-YAML + development: + secret_key_base: + YAML + + app 'development' + + assert_equal '', app.config.secret_token + assert_equal nil, app.secrets.secret_key_base + assert_raise ArgumentError, /\AA secret is required/ do + app.key_generator + end + end + + test "protect from forgery is the default in a new app" do + make_basic_app + + class ::OmgController < ActionController::Base + def index + render inline: "<%= csrf_meta_tags %>" + end + end + + get "/" + assert last_response.body =~ /csrf\-param/ + end + + test "default form builder specified as a string" do + app_file 'config/initializers/form_builder.rb', <<-RUBY + class CustomFormBuilder < ActionView::Helpers::FormBuilder + def text_field(attribute, *args) + label(attribute) + super(attribute, *args) + end + end + Rails.configuration.action_view.default_form_builder = "CustomFormBuilder" + RUBY + + app_file 'app/models/post.rb', <<-RUBY + class Post + include ActiveModel::Model + attr_accessor :name + end + RUBY + + + app_file 'app/controllers/posts_controller.rb', <<-RUBY + class PostsController < ApplicationController + def index + render inline: "<%= begin; form_for(Post.new) {|f| f.text_field(:name)}; rescue => e; e.to_s; end %>" + end + end + RUBY + + add_to_config <<-RUBY + routes.prepend do + resources :posts + end + RUBY + + app 'development' + + get "/posts" + assert_match(/label/, last_response.body) + end + + test "default method for update can be changed" do + app_file 'app/models/post.rb', <<-RUBY + class Post + include ActiveModel::Model + def to_key; [1]; end + def persisted?; true; end + end + RUBY + + token = "cf50faa3fe97702ca1ae" + + app_file 'app/controllers/posts_controller.rb', <<-RUBY + class PostsController < ApplicationController + def show + render inline: "<%= begin; form_for(Post.new) {}; rescue => e; e.to_s; end %>" + end + + def update + render text: "update" + end + + private + + def form_authenticity_token; token; end # stub the authenticy token + end + RUBY + + add_to_config <<-RUBY + routes.prepend do + resources :posts + end + RUBY + + app 'development' + + params = { authenticity_token: token } + + get "/posts/1" + assert_match(/patch/, last_response.body) + + patch "/posts/1", params + assert_match(/update/, last_response.body) + + patch "/posts/1", params + assert_equal 200, last_response.status + + put "/posts/1", params + assert_match(/update/, last_response.body) + + put "/posts/1", params + assert_equal 200, last_response.status + end + + test "request forgery token param can be changed" do + make_basic_app do |application| + application.config.action_controller.request_forgery_protection_token = '_xsrf_token_here' + end + + class ::OmgController < ActionController::Base + def index + render inline: "<%= csrf_meta_tags %>" + end + end + + get "/" + assert_match "_xsrf_token_here", last_response.body + end + + test "sets ActionDispatch.test_app" do + make_basic_app + assert_equal Rails.application, ActionDispatch.test_app + end + + test "sets ActionDispatch::Response.default_charset" do + make_basic_app do |application| + application.config.action_dispatch.default_charset = "utf-16" + end + + assert_equal "utf-16", ActionDispatch::Response.default_charset + end + + test "registers interceptors with ActionMailer" do + add_to_config <<-RUBY + config.action_mailer.interceptors = MyMailInterceptor + RUBY + + app 'development' + + require "mail" + _ = ActionMailer::Base + + assert_equal [::MyMailInterceptor], ::Mail.send(:class_variable_get, "@@delivery_interceptors") + end + + test "registers multiple interceptors with ActionMailer" do + add_to_config <<-RUBY + config.action_mailer.interceptors = [MyMailInterceptor, "MyOtherMailInterceptor"] + RUBY + + app 'development' + + require "mail" + _ = ActionMailer::Base + + assert_equal [::MyMailInterceptor, ::MyOtherMailInterceptor], ::Mail.send(:class_variable_get, "@@delivery_interceptors") + end + + test "registers preview interceptors with ActionMailer" do + add_to_config <<-RUBY + config.action_mailer.preview_interceptors = MyPreviewMailInterceptor + RUBY + + app 'development' + + require "mail" + _ = ActionMailer::Base + + assert_equal [ActionMailer::InlinePreviewInterceptor, ::MyPreviewMailInterceptor], ActionMailer::Base.preview_interceptors + end + + test "registers multiple preview interceptors with ActionMailer" do + add_to_config <<-RUBY + config.action_mailer.preview_interceptors = [MyPreviewMailInterceptor, "MyOtherPreviewMailInterceptor"] + RUBY + + app 'development' + + require "mail" + _ = ActionMailer::Base + + assert_equal [ActionMailer::InlinePreviewInterceptor, MyPreviewMailInterceptor, MyOtherPreviewMailInterceptor], ActionMailer::Base.preview_interceptors + end + + test "default preview interceptor can be removed" do + app_file 'config/initializers/preview_interceptors.rb', <<-RUBY + ActionMailer::Base.preview_interceptors.delete(ActionMailer::InlinePreviewInterceptor) + RUBY + + app 'development' + + require "mail" + _ = ActionMailer::Base + + assert_equal [], ActionMailer::Base.preview_interceptors + end + + test "registers observers with ActionMailer" do + add_to_config <<-RUBY + config.action_mailer.observers = MyMailObserver + RUBY + + app 'development' + + require "mail" + _ = ActionMailer::Base + + assert_equal [::MyMailObserver], ::Mail.send(:class_variable_get, "@@delivery_notification_observers") + end + + test "registers multiple observers with ActionMailer" do + add_to_config <<-RUBY + config.action_mailer.observers = [MyMailObserver, "MyOtherMailObserver"] + RUBY + + app 'development' + + require "mail" + _ = ActionMailer::Base + + assert_equal [::MyMailObserver, ::MyOtherMailObserver], ::Mail.send(:class_variable_get, "@@delivery_notification_observers") + end + + test "allows setting the queue name for the ActionMailer::DeliveryJob" do + add_to_config <<-RUBY + config.action_mailer.deliver_later_queue_name = 'test_default' + RUBY + + app 'development' + + require "mail" + _ = ActionMailer::Base + + assert_equal 'test_default', ActionMailer::Base.send(:class_variable_get, "@@deliver_later_queue_name") + end + + test "valid timezone is setup correctly" do + add_to_config <<-RUBY + config.root = "#{app_path}" + config.time_zone = "Wellington" + RUBY + + app 'development' + + assert_equal "Wellington", Rails.application.config.time_zone + end + + test "raises when an invalid timezone is defined in the config" do + add_to_config <<-RUBY + config.root = "#{app_path}" + config.time_zone = "That big hill over yonder hill" + RUBY + + assert_raise(ArgumentError) do + app 'development' + end + end + + test "valid beginning of week is setup correctly" do + add_to_config <<-RUBY + config.root = "#{app_path}" + config.beginning_of_week = :wednesday + RUBY + + app 'development' + + assert_equal :wednesday, Rails.application.config.beginning_of_week + end + + test "raises when an invalid beginning of week is defined in the config" do + add_to_config <<-RUBY + config.root = "#{app_path}" + config.beginning_of_week = :invalid + RUBY + + assert_raise(ArgumentError) do + app 'development' + end + end + + test "config.action_view.cache_template_loading with cache_classes default" do + add_to_config "config.cache_classes = true" + + app 'development' + require 'action_view/base' + + assert_equal true, ActionView::Resolver.caching? + end + + test "config.action_view.cache_template_loading without cache_classes default" do + add_to_config "config.cache_classes = false" + + app 'development' + require 'action_view/base' + + assert_equal false, ActionView::Resolver.caching? + end + + test "config.action_view.cache_template_loading = false" do + add_to_config <<-RUBY + config.cache_classes = true + config.action_view.cache_template_loading = false + RUBY + + app 'development' + require 'action_view/base' + + assert_equal false, ActionView::Resolver.caching? + end + + test "config.action_view.cache_template_loading = true" do + add_to_config <<-RUBY + config.cache_classes = false + config.action_view.cache_template_loading = true + RUBY + + app 'development' + require 'action_view/base' + + assert_equal true, ActionView::Resolver.caching? + end + + test "config.action_view.cache_template_loading with cache_classes in an environment" do + build_app(initializers: true) + add_to_env_config "development", "config.cache_classes = false" + + # These requires are to emulate an engine loading Action View before the application + require 'action_view' + require 'action_view/railtie' + require 'action_view/base' + + app 'development' + + assert_equal false, ActionView::Resolver.caching? + end + + test "config.action_dispatch.show_exceptions is sent in env" do + make_basic_app do |application| + application.config.action_dispatch.show_exceptions = true + end + + class ::OmgController < ActionController::Base + def index + render text: env["action_dispatch.show_exceptions"] + end + end + + get "/" + assert_equal 'true', last_response.body + end + + test "config.action_controller.wrap_parameters is set in ActionController::Base" do + app_file 'config/initializers/wrap_parameters.rb', <<-RUBY + ActionController::Base.wrap_parameters format: [:json] + RUBY + + app_file 'app/models/post.rb', <<-RUBY + class Post + def self.attribute_names + %w(title) + end + end + RUBY + + app_file 'app/controllers/application_controller.rb', <<-RUBY + class ApplicationController < ActionController::Base + protect_from_forgery with: :reset_session # as we are testing API here + end + RUBY + + app_file 'app/controllers/posts_controller.rb', <<-RUBY + class PostsController < ApplicationController + def create + render text: params[:post].inspect + end + end + RUBY + + add_to_config <<-RUBY + routes.prepend do + resources :posts + end + RUBY + + app 'development' + + post "/posts.json", '{ "title": "foo", "name": "bar" }', "CONTENT_TYPE" => "application/json" + assert_equal '{"title"=>"foo"}', last_response.body + end + + test "config.action_controller.permit_all_parameters = true" do + app_file 'app/controllers/posts_controller.rb', <<-RUBY + class PostsController < ActionController::Base + def create + render text: params[:post].permitted? ? "permitted" : "forbidden" + end + end + RUBY + + add_to_config <<-RUBY + routes.prepend do + resources :posts + end + config.action_controller.permit_all_parameters = true + RUBY + + app 'development' + + post "/posts", {post: {"title" =>"zomg"}} + assert_equal 'permitted', last_response.body + end + + test "config.action_controller.action_on_unpermitted_parameters = :raise" do + app_file 'app/controllers/posts_controller.rb', <<-RUBY + class PostsController < ActionController::Base + def create + render text: params.require(:post).permit(:name) + end + end + RUBY + + add_to_config <<-RUBY + routes.prepend do + resources :posts + end + config.action_controller.action_on_unpermitted_parameters = :raise + RUBY + + app 'development' + + assert_equal :raise, ActionController::Parameters.action_on_unpermitted_parameters + + post "/posts", {post: {"title" =>"zomg"}} + assert_match "We're sorry, but something went wrong", last_response.body + end + + test "config.action_controller.always_permitted_parameters are: controller, action by default" do + app 'development' + assert_equal %w(controller action), ActionController::Parameters.always_permitted_parameters + end + + test "config.action_controller.always_permitted_parameters = ['controller', 'action', 'format']" do + add_to_config <<-RUBY + config.action_controller.always_permitted_parameters = %w( controller action format ) + RUBY + + app 'development' + + assert_equal %w( controller action format ), ActionController::Parameters.always_permitted_parameters + end + + test "config.action_controller.always_permitted_parameters = ['controller','action','format'] does not raise exeception" do + app_file 'app/controllers/posts_controller.rb', <<-RUBY + class PostsController < ActionController::Base + def create + render text: params.permit(post: [:title]) + end + end + RUBY + + add_to_config <<-RUBY + routes.prepend do + resources :posts + end + config.action_controller.always_permitted_parameters = %w( controller action format ) + config.action_controller.action_on_unpermitted_parameters = :raise + RUBY + + app 'development' + + assert_equal :raise, ActionController::Parameters.action_on_unpermitted_parameters + + post "/posts", {post: {"title" =>"zomg"}, format: "json"} + assert_equal 200, last_response.status + end + + test "config.action_controller.action_on_unpermitted_parameters is :log by default on development" do + app 'development' + + assert_equal :log, ActionController::Parameters.action_on_unpermitted_parameters + end + + test "config.action_controller.action_on_unpermitted_parameters is :log by default on test" do + app 'test' + + assert_equal :log, ActionController::Parameters.action_on_unpermitted_parameters + end + + test "config.action_controller.action_on_unpermitted_parameters is false by default on production" do + app 'production' + + assert_equal false, ActionController::Parameters.action_on_unpermitted_parameters + end + + test "config.action_dispatch.ignore_accept_header" do + make_basic_app do |application| + application.config.action_dispatch.ignore_accept_header = true + end + + class ::OmgController < ActionController::Base + def index + respond_to do |format| + format.html { render text: "HTML" } + format.xml { render text: "XML" } + end + end + end + + get "/", {}, "HTTP_ACCEPT" => "application/xml" + assert_equal 'HTML', last_response.body + + get "/", { format: :xml }, "HTTP_ACCEPT" => "application/xml" + assert_equal 'XML', last_response.body + end + + test "Rails.application#env_config exists and include some existing parameters" do + make_basic_app + + assert_respond_to app, :env_config + assert_equal app.env_config['action_dispatch.parameter_filter'], app.config.filter_parameters + assert_equal app.env_config['action_dispatch.show_exceptions'], app.config.action_dispatch.show_exceptions + assert_equal app.env_config['action_dispatch.logger'], Rails.logger + assert_equal app.env_config['action_dispatch.backtrace_cleaner'], Rails.backtrace_cleaner + assert_equal app.env_config['action_dispatch.key_generator'], Rails.application.key_generator + end + + test "config.colorize_logging default is true" do + make_basic_app + assert app.config.colorize_logging + end + + test "config.session_store with :active_record_store with activerecord-session_store gem" do + begin + make_basic_app do |application| + ActionDispatch::Session::ActiveRecordStore = Class.new(ActionDispatch::Session::CookieStore) + application.config.session_store :active_record_store + end + ensure + ActionDispatch::Session.send :remove_const, :ActiveRecordStore + end + end + + test "config.session_store with :active_record_store without activerecord-session_store gem" do + assert_raise RuntimeError, /activerecord-session_store/ do + make_basic_app do |application| + application.config.session_store :active_record_store + end + end + end + + test "config.log_level with custom logger" do + make_basic_app do |application| + application.config.logger = Logger.new(STDOUT) + application.config.log_level = :info + end + assert_equal Logger::INFO, Rails.logger.level + end + + test "respond_to? accepts include_private" do + make_basic_app + + assert_not Rails.configuration.respond_to?(:method_missing) + assert Rails.configuration.respond_to?(:method_missing, true) + end + + test "config.active_record.dump_schema_after_migration is false on production" do + build_app + + app 'production' + + assert_not ActiveRecord::Base.dump_schema_after_migration + end + + test "config.active_record.dump_schema_after_migration is true by default on development" do + app 'development' + + assert ActiveRecord::Base.dump_schema_after_migration + end + + test "config.annotations wrapping SourceAnnotationExtractor::Annotation class" do + make_basic_app do |application| + application.config.annotations.register_extensions("coffee") do |tag| + /#\s*(#{tag}):?\s*(.*)$/ + end + end + + assert_not_nil SourceAnnotationExtractor::Annotation.extensions[/\.(coffee)$/] + end + + test "rake_tasks block works at instance level" do + app_file "config/environments/development.rb", <<-RUBY + Rails.application.configure do + config.ran_block = false + + rake_tasks do + config.ran_block = true + end + end + RUBY + + app 'development' + assert_not Rails.configuration.ran_block + + require 'rake' + require 'rake/testtask' + require 'rdoc/task' + + Rails.application.load_tasks + assert Rails.configuration.ran_block + end + + test "generators block works at instance level" do + app_file "config/environments/development.rb", <<-RUBY + Rails.application.configure do + config.ran_block = false + + generators do + config.ran_block = true + end + end + RUBY + + app 'development' + assert_not Rails.configuration.ran_block + + Rails.application.load_generators + assert Rails.configuration.ran_block + end + + test "console block works at instance level" do + app_file "config/environments/development.rb", <<-RUBY + Rails.application.configure do + config.ran_block = false + + console do + config.ran_block = true + end + end + RUBY + + app 'development' + assert_not Rails.configuration.ran_block + + Rails.application.load_console + assert Rails.configuration.ran_block + end + + test "runner block works at instance level" do + app_file "config/environments/development.rb", <<-RUBY + Rails.application.configure do + config.ran_block = false + + runner do + config.ran_block = true + end + end + RUBY + + app 'development' + assert_not Rails.configuration.ran_block + + Rails.application.load_runner + assert Rails.configuration.ran_block + end + + test "loading the first existing database configuration available" do + app_file 'config/environments/development.rb', <<-RUBY + + Rails.application.configure do + config.paths.add 'config/database', with: 'config/nonexistent.yml' + config.paths['config/database'] << 'config/database.yml' + end + RUBY + + app 'development' + + assert_kind_of Hash, Rails.application.config.database_configuration + end + + test 'raises with proper error message if no database configuration found' do + FileUtils.rm("#{app_path}/config/database.yml") + app 'development' + err = assert_raises RuntimeError do + Rails.application.config.database_configuration + end + assert_match 'config/database', err.message + end + + test 'config.action_mailer.show_previews defaults to true in development' do + app 'development' + + assert Rails.application.config.action_mailer.show_previews + end + + test 'config.action_mailer.show_previews defaults to false in production' do + app 'production' + + assert_equal false, Rails.application.config.action_mailer.show_previews + end + + test 'config.action_mailer.show_previews can be set in the configuration file' do + add_to_config <<-RUBY + config.action_mailer.show_previews = true + RUBY + + app 'production' + + assert_equal true, Rails.application.config.action_mailer.show_previews + end + + test "config_for loads custom configuration from yaml files" do + app_file 'config/custom.yml', <<-RUBY + development: + key: 'custom key' + RUBY + + add_to_config <<-RUBY + config.my_custom_config = config_for('custom') + RUBY + + app 'development' + + assert_equal 'custom key', Rails.application.config.my_custom_config['key'] + end + + test "config_for raises an exception if the file does not exist" do + add_to_config <<-RUBY + config.my_custom_config = config_for('custom') + RUBY + + exception = assert_raises(RuntimeError) do + app 'development' + end + + assert_equal "Could not load configuration. No such file - #{app_path}/config/custom.yml", exception.message + end + + test "config_for without the environment configured returns an empty hash" do + app_file 'config/custom.yml', <<-RUBY + test: + key: 'custom key' + RUBY + + add_to_config <<-RUBY + config.my_custom_config = config_for('custom') + RUBY + + app 'development' + + assert_equal({}, Rails.application.config.my_custom_config) + end + + test "config_for with empty file returns an empty hash" do + app_file 'config/custom.yml', <<-RUBY + RUBY + + add_to_config <<-RUBY + config.my_custom_config = config_for('custom') + RUBY + + app 'development' + + assert_equal({}, Rails.application.config.my_custom_config) + end + + test "config_for containing ERB tags should evaluate" do + app_file 'config/custom.yml', <<-RUBY + development: + key: <%= 'custom key' %> + RUBY + + add_to_config <<-RUBY + config.my_custom_config = config_for('custom') + RUBY + + app 'development' + + assert_equal 'custom key', Rails.application.config.my_custom_config['key'] + end + + test "config_for with syntax error show a more descriptive exception" do + app_file 'config/custom.yml', <<-RUBY + development: + key: foo: + RUBY + + add_to_config <<-RUBY + config.my_custom_config = config_for('custom') + RUBY + + exception = assert_raises(RuntimeError) do + app 'development' + end + + assert_match 'YAML syntax error occurred while parsing', exception.message + end + + test "config_for allows overriding the environment" do + app_file 'config/custom.yml', <<-RUBY + test: + key: 'walrus' + production: + key: 'unicorn' + RUBY + + add_to_config <<-RUBY + config.my_custom_config = config_for('custom', env: 'production') + RUBY + require "#{app_path}/config/environment" + + assert_equal 'unicorn', Rails.application.config.my_custom_config['key'] + end + + test "api_only is false by default" do + app 'development' + refute Rails.application.config.api_only + end + + test "api_only generator config is set when api_only is set" do + add_to_config <<-RUBY + config.api_only = true + RUBY + app 'development' + + Rails.application.load_generators + assert Rails.configuration.api_only + end + + test "debug_exception_response_format is :api by default if only_api is enabled" do + add_to_config <<-RUBY + config.api_only = true + RUBY + app 'development' + + assert_equal :api, Rails.configuration.debug_exception_response_format + end + + test "debug_exception_response_format can be override" do + add_to_config <<-RUBY + config.api_only = true + RUBY + + app_file 'config/environments/development.rb', <<-RUBY + Rails.application.configure do + config.debug_exception_response_format = :default + end + RUBY + + app 'development' + + assert_equal :default, Rails.configuration.debug_exception_response_format + end + end +end diff --git a/railties/test/application/console_test.rb b/railties/test/application/console_test.rb new file mode 100644 index 0000000000..7bf123d12b --- /dev/null +++ b/railties/test/application/console_test.rb @@ -0,0 +1,165 @@ +require 'isolation/abstract_unit' + +class ConsoleTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + end + + def teardown + teardown_app + end + + def load_environment(sandbox = false) + require "#{rails_root}/config/environment" + Rails.application.sandbox = sandbox + Rails.application.load_console + end + + def irb_context + Object.new.extend(Rails::ConsoleMethods) + end + + def test_app_method_should_return_integration_session + TestHelpers::Rack.send :remove_method, :app + load_environment + console_session = irb_context.app + assert_instance_of ActionDispatch::Integration::Session, console_session + end + + def test_app_can_access_path_helper_method + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get 'foo', to: 'foo#index' + end + RUBY + + load_environment + console_session = irb_context.app + assert_equal '/foo', console_session.foo_path + end + + def test_new_session_should_return_integration_session + load_environment + session = irb_context.new_session + assert_instance_of ActionDispatch::Integration::Session, session + end + + def test_reload_should_fire_preparation_and_cleanup_callbacks + load_environment + a = b = c = nil + + # TODO: These should be defined on the initializer + ActionDispatch::Reloader.to_cleanup { a = b = c = 1 } + ActionDispatch::Reloader.to_cleanup { b = c = 2 } + ActionDispatch::Reloader.to_prepare { c = 3 } + + # Hide Reloading... output + silence_stream(STDOUT) { irb_context.reload! } + + assert_equal 1, a + assert_equal 2, b + assert_equal 3, c + end + + def test_reload_should_reload_constants + app_file "app/models/user.rb", <<-MODEL + class User + attr_accessor :name + end + MODEL + + load_environment + assert User.new.respond_to?(:name) + + app_file "app/models/user.rb", <<-MODEL + class User + attr_accessor :name, :age + end + MODEL + + assert !User.new.respond_to?(:age) + silence_stream(STDOUT) { irb_context.reload! } + assert User.new.respond_to?(:age) + end + + def test_access_to_helpers + load_environment + helper = irb_context.helper + assert_not_nil helper + assert_instance_of ActionView::Base, helper + assert_equal 'Once upon a time in a world...', + helper.truncate('Once upon a time in a world far far away') + end +end + +begin + require "pty" +rescue LoadError +end + +class FullStackConsoleTest < ActiveSupport::TestCase + def setup + skip "PTY unavailable" unless defined?(PTY) && PTY.respond_to?(:open) + + build_app + app_file 'app/models/post.rb', <<-CODE + class Post < ActiveRecord::Base + end + CODE + system "#{app_path}/bin/rails runner 'Post.connection.create_table :posts'" + + @master, @slave = PTY.open + end + + def teardown + teardown_app + end + + def assert_output(expected, timeout = 1) + timeout = Time.now + timeout + + output = "" + until output.include?(expected) || Time.now > timeout + if IO.select([@master], [], [], 0.1) + output << @master.read(1) + end + end + + assert output.include?(expected), "#{expected.inspect} expected, but got:\n\n#{output}" + end + + def write_prompt(command, expected_output = nil) + @master.puts command + assert_output command + assert_output expected_output if expected_output + assert_output "> " + end + + def spawn_console + Process.spawn( + "#{app_path}/bin/rails console --sandbox", + in: @slave, out: @slave, err: @slave + ) + + assert_output "> ", 30 + end + + def test_sandbox + spawn_console + + write_prompt "Post.count", "=> 0" + write_prompt "Post.create" + write_prompt "Post.count", "=> 1" + @master.puts "quit" + + spawn_console + + write_prompt "Post.count", "=> 0" + write_prompt "Post.transaction { Post.create; raise }" + write_prompt "Post.count", "=> 0" + @master.puts "quit" + end +end diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb new file mode 100644 index 0000000000..84cc6e120b --- /dev/null +++ b/railties/test/application/generators_test.rb @@ -0,0 +1,164 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class GeneratorsTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + end + + def teardown + teardown_app + end + + def app_const + @app_const ||= Class.new(Rails::Application) + end + + def with_config + require "rails/all" + require "rails/generators" + yield app_const.config + end + + def with_bare_config + require "rails" + require "rails/generators" + yield app_const.config + end + + test "allow running plugin new generator inside Rails app directory" do + FileUtils.cd(rails_root){ `ruby bin/rails plugin new vendor/plugins/bukkits` } + assert File.exist?(File.join(rails_root, "vendor/plugins/bukkits/test/dummy/config/application.rb")) + end + + test "generators default values" do + with_bare_config do |c| + assert_equal(true, c.generators.colorize_logging) + assert_equal({}, c.generators.aliases) + assert_equal({}, c.generators.options) + assert_equal({}, c.generators.fallbacks) + end + end + + test "generators set rails options" do + with_bare_config do |c| + c.generators.orm = :data_mapper + c.generators.test_framework = :rspec + c.generators.helper = false + expected = { rails: { orm: :data_mapper, test_framework: :rspec, helper: false } } + assert_equal(expected, c.generators.options) + end + end + + test "generators set rails aliases" do + with_config do |c| + c.generators.aliases = { rails: { test_framework: "-w" } } + expected = { rails: { test_framework: "-w" } } + assert_equal expected, c.generators.aliases + end + end + + test "generators aliases, options, templates and fallbacks on initialization" do + add_to_config <<-RUBY + config.generators.rails aliases: { test_framework: "-w" } + config.generators.orm :data_mapper + config.generators.test_framework :rspec + config.generators.fallbacks[:shoulda] = :test_unit + config.generators.templates << "some/where" + RUBY + + # Initialize the application + require "#{app_path}/config/environment" + Rails.application.load_generators + + assert_equal :rspec, Rails::Generators.options[:rails][:test_framework] + assert_equal "-w", Rails::Generators.aliases[:rails][:test_framework] + assert_equal Hash[shoulda: :test_unit], Rails::Generators.fallbacks + assert_equal ["some/where"], Rails::Generators.templates_path + end + + test "generators no color on initialization" do + add_to_config <<-RUBY + config.generators.colorize_logging = false + RUBY + + # Initialize the application + require "#{app_path}/config/environment" + Rails.application.load_generators + + assert_equal Thor::Base.shell, Thor::Shell::Basic + end + + test "generators with hashes for options and aliases" do + with_bare_config do |c| + c.generators do |g| + g.orm :data_mapper, migration: false + g.plugin aliases: { generator: "-g" }, + generator: true + end + + expected = { + rails: { orm: :data_mapper }, + plugin: { generator: true }, + data_mapper: { migration: false } + } + + assert_equal expected, c.generators.options + assert_equal({ plugin: { generator: "-g" } }, c.generators.aliases) + end + end + + test "generators with string and hash for options should generate symbol keys" do + with_bare_config do |c| + c.generators do |g| + g.orm 'data_mapper', migration: false + end + + expected = { + rails: { orm: :data_mapper }, + data_mapper: { migration: false } + } + + 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 new file mode 100644 index 0000000000..13f3250f5b --- /dev/null +++ b/railties/test/application/initializers/frameworks_test.rb @@ -0,0 +1,269 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class FrameworksTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + FileUtils.rm_rf "#{app_path}/config/environments" + end + + def teardown + teardown_app + end + + # AC & AM + test "set load paths set only if action controller or action mailer are in use" do + assert_nothing_raised NameError do + add_to_config <<-RUBY + config.root = "#{app_path}" + RUBY + + use_frameworks [] + require "#{app_path}/config/environment" + end + end + + test "sets action_controller and action_mailer load paths" do + add_to_config <<-RUBY + config.root = "#{app_path}" + RUBY + + require "#{app_path}/config/environment" + + expanded_path = File.expand_path("app/views", app_path) + assert_equal expanded_path, ActionController::Base.view_paths[0].to_s + assert_equal expanded_path, ActionMailer::Base.view_paths[0].to_s + end + + test "allows me to configure default url options for ActionMailer" do + app_file "config/environments/development.rb", <<-RUBY + Rails.application.configure do + config.action_mailer.default_url_options = { :host => "test.rails" } + end + RUBY + + require "#{app_path}/config/environment" + assert_equal "test.rails", ActionMailer::Base.default_url_options[:host] + end + + test "Default to HTTPS for ActionMailer URLs when force_ssl is on" do + app_file "config/environments/development.rb", <<-RUBY + Rails.application.configure do + config.force_ssl = true + end + RUBY + + require "#{app_path}/config/environment" + assert_equal "https", ActionMailer::Base.default_url_options[:protocol] + end + + test "includes url helpers as action methods" do + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get "/foo", :to => lambda { |env| [200, {}, []] }, :as => :foo + end + RUBY + + app_file "app/mailers/foo.rb", <<-RUBY + class Foo < ActionMailer::Base + def notify + end + end + RUBY + + require "#{app_path}/config/environment" + assert Foo.method_defined?(:foo_url) + assert Foo.method_defined?(:main_app) + end + + test "allows to not load all helpers for controllers" do + add_to_config "config.action_controller.include_all_helpers = false" + + app_file "app/controllers/application_controller.rb", <<-RUBY + class ApplicationController < ActionController::Base + end + RUBY + + app_file "app/controllers/foo_controller.rb", <<-RUBY + class FooController < ApplicationController + def included_helpers + render :inline => "<%= from_app_helper -%> <%= from_foo_helper %>" + end + + def not_included_helper + render :inline => "<%= respond_to?(:from_bar_helper) -%>" + end + end + RUBY + + app_file "app/helpers/application_helper.rb", <<-RUBY + module ApplicationHelper + def from_app_helper + "from_app_helper" + end + end + RUBY + + app_file "app/helpers/foo_helper.rb", <<-RUBY + module FooHelper + def from_foo_helper + "from_foo_helper" + end + end + RUBY + + app_file "app/helpers/bar_helper.rb", <<-RUBY + module BarHelper + def from_bar_helper + "from_bar_helper" + 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 "/foo/included_helpers" + assert_equal "from_app_helper from_foo_helper", last_response.body + + get "/foo/not_included_helper" + 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" + require "#{app_path}/config/environment" + assert_equal 2, ActionDispatch::Http::URL.tld_length + end + + test "assignment config.encoding to default_charset" do + charset = 'Shift_JIS' + add_to_config "config.encoding = '#{charset}'" + require "#{app_path}/config/environment" + assert_equal charset, ActionDispatch::Response.default_charset + end + + # AS + test "if there's no config.active_support.bare, all of ActiveSupport is required" do + use_frameworks [] + require "#{app_path}/config/environment" + assert_nothing_raised { [1,2,3].sample } + end + + test "config.active_support.bare does not require all of ActiveSupport" do + add_to_config "config.active_support.bare = true" + + use_frameworks [] + + Dir.chdir("#{app_path}/app") do + require "#{app_path}/config/environment" + assert_raises(NoMethodError) { "hello".exclude? "lo" } + end + end + + # AR + test "active_record extensions are applied to ActiveRecord" do + add_to_config "config.active_record.table_name_prefix = 'tbl_'" + require "#{app_path}/config/environment" + assert_equal 'tbl_', ActiveRecord::Base.table_name_prefix + end + + test "database middleware doesn't initialize when activerecord is not in frameworks" do + use_frameworks [] + require "#{app_path}/config/environment" + assert_nil defined?(ActiveRecord::Base) + end + + test "use schema cache dump" do + Dir.chdir(app_path) do + `rails generate model post title:string; + bin/rake db:migrate db:schema:cache:dump` + end + require "#{app_path}/config/environment" + ActiveRecord::Base.connection.drop_table("posts") # force drop posts table for test. + assert ActiveRecord::Base.connection.schema_cache.tables("posts") + end + + test "expire schema cache dump" do + Dir.chdir(app_path) do + `rails generate model post title:string; + bin/rake db:migrate db:schema:cache:dump db:rollback` + end + silence_warnings { + require "#{app_path}/config/environment" + assert !ActiveRecord::Base.connection.schema_cache.tables("posts") + } + end + + test "active record establish_connection uses Rails.env if DATABASE_URL is not set" do + begin + require "#{app_path}/config/environment" + orig_database_url = ENV.delete("DATABASE_URL") + orig_rails_env, Rails.env = Rails.env, 'development' + ActiveRecord::Base.establish_connection + assert ActiveRecord::Base.connection + assert_match(/#{ActiveRecord::Base.configurations[Rails.env]['database']}/, ActiveRecord::Base.connection_config[:database]) + ensure + ActiveRecord::Base.remove_connection + ENV["DATABASE_URL"] = orig_database_url if orig_database_url + Rails.env = orig_rails_env if orig_rails_env + end + end + + test "active record establish_connection uses DATABASE_URL even if Rails.env is set" do + begin + require "#{app_path}/config/environment" + orig_database_url = ENV.delete("DATABASE_URL") + orig_rails_env, Rails.env = Rails.env, 'development' + database_url_db_name = "db/database_url_db.sqlite3" + ENV["DATABASE_URL"] = "sqlite3:#{database_url_db_name}" + ActiveRecord::Base.establish_connection + assert ActiveRecord::Base.connection + assert_match(/#{database_url_db_name}/, ActiveRecord::Base.connection_config[:database]) + ensure + ActiveRecord::Base.remove_connection + ENV["DATABASE_URL"] = orig_database_url if orig_database_url + Rails.env = orig_rails_env if orig_rails_env + end + end + end +end diff --git a/railties/test/application/initializers/hooks_test.rb b/railties/test/application/initializers/hooks_test.rb new file mode 100644 index 0000000000..b2cea0a8e1 --- /dev/null +++ b/railties/test/application/initializers/hooks_test.rb @@ -0,0 +1,90 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class HooksTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + FileUtils.rm_rf "#{app_path}/config/environments" + end + + def teardown + teardown_app + end + + test "load initializers" do + app_file "config/initializers/foo.rb", "$foo = true" + require "#{app_path}/config/environment" + assert $foo + end + + test "hooks block works correctly without eager_load (before_eager_load is not called)" do + add_to_config <<-RUBY + $initialization_callbacks = [] + config.root = "#{app_path}" + config.eager_load = false + config.before_configuration { $initialization_callbacks << 1 } + config.before_initialize { $initialization_callbacks << 2 } + config.before_eager_load { Boom } + config.after_initialize { $initialization_callbacks << 3 } + RUBY + + require "#{app_path}/config/environment" + assert_equal [1,2,3], $initialization_callbacks + end + + test "hooks block works correctly with eager_load" do + add_to_config <<-RUBY + $initialization_callbacks = [] + config.root = "#{app_path}" + config.eager_load = true + config.before_configuration { $initialization_callbacks << 1 } + config.before_initialize { $initialization_callbacks << 2 } + config.before_eager_load { $initialization_callbacks << 3 } + config.after_initialize { $initialization_callbacks << 4 } + RUBY + + require "#{app_path}/config/environment" + assert_equal [1,2,3,4], $initialization_callbacks + end + + test "after_initialize runs after frameworks have been initialized" do + $activerecord_configurations = nil + add_to_config <<-RUBY + config.after_initialize { $activerecord_configurations = ActiveRecord::Base.configurations } + RUBY + + require "#{app_path}/config/environment" + assert $activerecord_configurations + assert $activerecord_configurations['development'] + end + + test "after_initialize happens after to_prepare in development" do + $order = [] + add_to_config <<-RUBY + config.cache_classes = false + config.after_initialize { $order << :after_initialize } + config.to_prepare { $order << :to_prepare } + RUBY + + require "#{app_path}/config/environment" + assert_equal [:to_prepare, :after_initialize], $order + end + + test "after_initialize happens after to_prepare in production" do + $order = [] + add_to_config <<-RUBY + config.cache_classes = true + config.after_initialize { $order << :after_initialize } + config.to_prepare { $order << :to_prepare } + RUBY + + require "#{app_path}/config/application" + Rails.env.replace "production" + require "#{app_path}/config/environment" + assert_equal [:to_prepare, :after_initialize], $order + end + end +end diff --git a/railties/test/application/initializers/i18n_test.rb b/railties/test/application/initializers/i18n_test.rb new file mode 100644 index 0000000000..ab7f29b0f2 --- /dev/null +++ b/railties/test/application/initializers/i18n_test.rb @@ -0,0 +1,289 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class I18nTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + FileUtils.rm_rf "#{app_path}/config/environments" + require "rails/all" + end + + def teardown + teardown_app + end + + def load_app + require "#{app_path}/config/environment" + end + + def app + @app ||= Rails.application + end + + def assert_fallbacks(fallbacks) + fallbacks.each do |locale, expected| + actual = I18n.fallbacks[locale] + assert_equal expected, actual, "expected fallbacks for #{locale.inspect} to be #{expected.inspect}, but were #{actual.inspect}" + end + end + + def assert_no_fallbacks + assert !I18n.backend.class.included_modules.include?(I18n::Backend::Fallbacks) + end + + # Locales + test "setting another default locale" do + add_to_config <<-RUBY + config.i18n.default_locale = :de + RUBY + + load_app + assert_equal :de, I18n.default_locale + end + + # Load paths + test "no config locales directory present should return empty load path" do + FileUtils.rm_rf "#{app_path}/config/locales" + load_app + assert_equal [], Rails.application.config.i18n.load_path + end + + test "locale files should be added to the load path" do + app_file "config/another_locale.yml", "en:\nfoo: ~" + + add_to_config <<-RUBY + config.i18n.load_path << config.root.join("config/another_locale.yml").to_s + RUBY + + load_app + assert_equal [ + "#{app_path}/config/locales/en.yml", "#{app_path}/config/another_locale.yml" + ], Rails.application.config.i18n.load_path + + assert I18n.load_path.include?("#{app_path}/config/locales/en.yml") + assert I18n.load_path.include?("#{app_path}/config/another_locale.yml") + end + + test "load_path is populated before eager loaded models" do + add_to_config <<-RUBY + config.cache_classes = true + RUBY + + app_file "config/locales/en.yml", <<-YAML +en: + foo: "1" + YAML + + app_file 'app/models/foo.rb', <<-RUBY + class Foo < ActiveRecord::Base + @foo = I18n.t(:foo) + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get '/i18n', :to => lambda { |env| [200, {}, [Foo.instance_variable_get('@foo')]] } + end + RUBY + + require 'rack/test' + extend Rack::Test::Methods + load_app + + get "/i18n" + assert_equal "1", last_response.body + end + + test "locales are reloaded if they change between requests" do + add_to_config <<-RUBY + config.cache_classes = false + RUBY + + app_file "config/locales/en.yml", <<-YAML +en: + foo: "1" + YAML + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get '/i18n', :to => lambda { |env| [200, {}, [I18n.t(:foo)]] } + end + RUBY + + require 'rack/test' + extend Rack::Test::Methods + load_app + + get "/i18n" + assert_equal "1", last_response.body + + # Wait a full second so we have time for changes to propagate + sleep(1) + + app_file "config/locales/en.yml", <<-YAML +en: + foo: "2" + YAML + + get "/i18n" + assert_equal "2", last_response.body + end + + test "new locale files are loaded" do + add_to_config <<-RUBY + config.cache_classes = false + RUBY + + app_file "config/locales/en.yml", <<-YAML +en: + foo: "1" + YAML + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get '/i18n', :to => lambda { |env| [200, {}, [I18n.t(:foo)]] } + end + RUBY + + require 'rack/test' + extend Rack::Test::Methods + load_app + + get "/i18n" + assert_equal "1", last_response.body + + # Wait a full second so we have time for changes to propagate + sleep(1) + + remove_file "config/locales/en.yml" + app_file "config/locales/custom.en.yml", <<-YAML +en: + foo: "2" + YAML + + get "/i18n" + assert_equal "2", last_response.body + end + + test "I18n.load_path is reloaded" do + add_to_config <<-RUBY + config.cache_classes = false + RUBY + + app_file "config/locales/en.yml", <<-YAML +en: + foo: "1" + YAML + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get '/i18n', :to => lambda { |env| [200, {}, [I18n.load_path.inspect]] } + end + RUBY + + require 'rack/test' + extend Rack::Test::Methods + load_app + + get "/i18n" + + assert_match "en.yml", last_response.body + + # Wait a full second so we have time for changes to propagate + sleep(1) + + app_file "config/locales/fr.yml", <<-YAML +fr: + foo: "2" + YAML + + get "/i18n" + assert_match "fr.yml", last_response.body + assert_match "en.yml", last_response.body + end + + # Fallbacks + test "not using config.i18n.fallbacks does not initialize I18n.fallbacks" do + I18n.backend = Class.new(I18n::Backend::Simple).new + load_app + assert_no_fallbacks + end + + test "config.i18n.fallbacks = true initializes I18n.fallbacks with default settings" do + I18n::Railtie.config.i18n.fallbacks = true + load_app + assert I18n.backend.class.included_modules.include?(I18n::Backend::Fallbacks) + assert_fallbacks de: [:de, :en] + end + + test "config.i18n.fallbacks = true initializes I18n.fallbacks with default settings even when backend changes" do + I18n::Railtie.config.i18n.fallbacks = true + I18n::Railtie.config.i18n.backend = Class.new(I18n::Backend::Simple).new + load_app + assert I18n.backend.class.included_modules.include?(I18n::Backend::Fallbacks) + assert_fallbacks de: [:de, :en] + end + + test "config.i18n.fallbacks.defaults = [:'en-US'] initializes fallbacks with en-US as a fallback default" do + I18n::Railtie.config.i18n.fallbacks.defaults = [:'en-US'] + load_app + assert_fallbacks de: [:de, :'en-US', :en] + end + + test "config.i18n.fallbacks.map = { :ca => :'es-ES' } initializes fallbacks with a mapping ca => es-ES" do + I18n::Railtie.config.i18n.fallbacks.map = { :ca => :'es-ES' } + load_app + assert_fallbacks ca: [:ca, :"es-ES", :es, :en] + end + + test "[shortcut] config.i18n.fallbacks = [:'en-US'] initializes fallbacks with en-US as a fallback default" do + I18n::Railtie.config.i18n.fallbacks = [:'en-US'] + load_app + assert_fallbacks de: [:de, :'en-US', :en] + end + + test "[shortcut] config.i18n.fallbacks = [{ :ca => :'es-ES' }] initializes fallbacks with a mapping de-AT => de-DE" do + I18n::Railtie.config.i18n.fallbacks.map = { :ca => :'es-ES' } + load_app + assert_fallbacks ca: [:ca, :"es-ES", :es, :en] + end + + test "[shortcut] config.i18n.fallbacks = [:'en-US', { :ca => :'es-ES' }] initializes fallbacks with the given arguments" do + I18n::Railtie.config.i18n.fallbacks = [:'en-US', { :ca => :'es-ES' }] + load_app + assert_fallbacks ca: [:ca, :"es-ES", :es, :'en-US', :en] + end + + test "disable config.i18n.enforce_available_locales" do + add_to_config <<-RUBY + config.i18n.enforce_available_locales = false + config.i18n.default_locale = :fr + RUBY + + load_app + assert_equal false, I18n.enforce_available_locales + + assert_nothing_raised do + I18n.locale = :es + end + end + + test "default config.i18n.enforce_available_locales does not override I18n.enforce_available_locales" do + I18n.enforce_available_locales = false + + add_to_config <<-RUBY + config.i18n.default_locale = :fr + RUBY + + load_app + assert_equal false, I18n.enforce_available_locales + + assert_nothing_raised do + I18n.locale = :es + end + end + end +end diff --git a/railties/test/application/initializers/load_path_test.rb b/railties/test/application/initializers/load_path_test.rb new file mode 100644 index 0000000000..cd05956356 --- /dev/null +++ b/railties/test/application/initializers/load_path_test.rb @@ -0,0 +1,110 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class LoadPathTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + FileUtils.rm_rf "#{app_path}/config/environments" + end + + def teardown + teardown_app + end + + test "initializing an application adds the application paths to the load path" do + add_to_config <<-RUBY + config.root = "#{app_path}" + RUBY + + require "#{app_path}/config/environment" + assert $:.include?("#{app_path}/app/models") + end + + test "initializing an application allows to load code on lib path inside application class definition" do + app_file "lib/foo.rb", <<-RUBY + module Foo; end + RUBY + + add_to_config <<-RUBY + require "foo" + raise "Expected Foo to be defined" unless defined?(Foo) + RUBY + + assert_nothing_raised do + require "#{app_path}/config/environment" + end + + assert $:.include?("#{app_path}/lib") + end + + test "initializing an application eager load any path under app" do + app_file "app/anything/foo.rb", <<-RUBY + module Foo; end + RUBY + + add_to_config <<-RUBY + config.root = "#{app_path}" + RUBY + + require "#{app_path}/config/environment" + assert Foo + end + + test "eager loading loads parent classes before children" do + app_file "lib/zoo.rb", <<-ZOO + class Zoo ; include ReptileHouse ; end + ZOO + + app_file "lib/zoo/reptile_house.rb", <<-ZOO + module Zoo::ReptileHouse ; end + ZOO + + add_to_config <<-RUBY + config.root = "#{app_path}" + config.eager_load_paths << "#{app_path}/lib" + RUBY + + require "#{app_path}/config/environment" + assert Zoo + end + + test "eager loading accepts Pathnames" do + app_file "lib/foo.rb", <<-RUBY + module Foo; end + RUBY + + add_to_config <<-RUBY + config.eager_load = true + config.eager_load_paths << Pathname.new("#{app_path}/lib") + RUBY + + require "#{app_path}/config/environment" + assert Foo + end + + test "load environment with global" do + $initialize_test_set_from_env = nil + app_file "config/environments/development.rb", <<-RUBY + $initialize_test_set_from_env = 'success' + Rails.application.configure do + config.cache_classes = true + config.time_zone = "Brasilia" + end + RUBY + + assert_nil $initialize_test_set_from_env + add_to_config <<-RUBY + config.root = "#{app_path}" + config.time_zone = "UTC" + RUBY + + require "#{app_path}/config/environment" + assert_equal "success", $initialize_test_set_from_env + assert Rails.application.config.cache_classes + assert_equal "Brasilia", Rails.application.config.time_zone + end + end +end diff --git a/railties/test/application/initializers/notifications_test.rb b/railties/test/application/initializers/notifications_test.rb new file mode 100644 index 0000000000..95655b74cf --- /dev/null +++ b/railties/test/application/initializers/notifications_test.rb @@ -0,0 +1,56 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class NotificationsTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + end + + def teardown + teardown_app + end + + def instrument(*args, &block) + ActiveSupport::Notifications.instrument(*args, &block) + end + + def wait + ActiveSupport::Notifications.notifier.wait + end + + test "rails log_subscribers are added" do + add_to_config <<-RUBY + config.colorize_logging = false + RUBY + + require "#{app_path}/config/environment" + require "active_support/log_subscriber/test_helper" + + logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new + ActiveRecord::Base.logger = logger + + # Mimic Active Record notifications + instrument "sql.active_record", name: "SQL", sql: "SHOW tables" + wait + + assert_equal 1, logger.logged(:debug).size + assert_match(/SHOW tables/, logger.logged(:debug).last) + end + + test 'rails load_config_initializer event is instrumented' do + app_file 'config/initializers/foo.rb', '' + + events = [] + callback = ->(*_) { events << _ } + ActiveSupport::Notifications.subscribed(callback, 'load_config_initializer.railties') do + app + end + + assert_equal %w[load_config_initializer.railties], events.map(&:first) + assert_includes events.first.last[:initializer], 'config/initializers/foo.rb' + end + end +end diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb new file mode 100644 index 0000000000..2106708c98 --- /dev/null +++ b/railties/test/application/loading_test.rb @@ -0,0 +1,372 @@ +require 'isolation/abstract_unit' + +class LoadingTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + end + + def teardown + teardown_app + end + + def app + @app ||= Rails.application + end + + test "constants in app are autoloaded" do + app_file "app/models/post.rb", <<-MODEL + class Post < ActiveRecord::Base + validates_acceptance_of :title, accept: "omg" + end + MODEL + + require "#{rails_root}/config/environment" + setup_ar! + + p = Post.create(title: 'omg') + assert_equal 1, Post.count + assert_equal 'omg', p.title + p = Post.first + assert_equal 'omg', p.title + end + + test "concerns in app are autoloaded" do + app_file "app/controllers/concerns/trackable.rb", <<-CONCERN + module Trackable + end + CONCERN + + app_file "app/mailers/concerns/email_loggable.rb", <<-CONCERN + module EmailLoggable + end + CONCERN + + app_file "app/models/concerns/orderable.rb", <<-CONCERN + module Orderable + end + CONCERN + + app_file "app/validators/concerns/matchable.rb", <<-CONCERN + module Matchable + end + CONCERN + + require "#{rails_root}/config/environment" + + assert_nothing_raised(NameError) { Trackable } + assert_nothing_raised(NameError) { EmailLoggable } + assert_nothing_raised(NameError) { Orderable } + assert_nothing_raised(NameError) { Matchable } + end + + test "models without table do not panic on scope definitions when loaded" do + app_file "app/models/user.rb", <<-MODEL + class User < ActiveRecord::Base + default_scope { where(published: true) } + end + MODEL + + require "#{rails_root}/config/environment" + setup_ar! + + User + end + + test "load config/environments/environment before Bootstrap initializers" do + app_file "config/environments/development.rb", <<-RUBY + Rails.application.configure do + config.development_environment_loaded = true + end + RUBY + + add_to_config <<-RUBY + config.before_initialize do + config.loaded = config.development_environment_loaded + end + RUBY + + require "#{app_path}/config/environment" + assert ::Rails.application.config.loaded + end + + test "descendants loaded after framework initialization are cleaned on each request without cache classes" do + add_to_config <<-RUBY + config.cache_classes = false + config.reload_classes_only_on_change = false + RUBY + + app_file "app/models/post.rb", <<-MODEL + class Post < ActiveRecord::Base + end + MODEL + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get '/load', to: lambda { |env| [200, {}, Post.all] } + get '/unload', to: lambda { |env| [200, {}, []] } + end + RUBY + + require 'rack/test' + extend Rack::Test::Methods + + require "#{rails_root}/config/environment" + setup_ar! + + assert_equal [ActiveRecord::SchemaMigration], ActiveRecord::Base.descendants + get "/load" + assert_equal [ActiveRecord::SchemaMigration, Post], ActiveRecord::Base.descendants + get "/unload" + assert_equal [ActiveRecord::SchemaMigration], ActiveRecord::Base.descendants + end + + test "initialize cant be called twice" do + require "#{app_path}/config/environment" + assert_raise(RuntimeError) { Rails.application.initialize! } + end + + test "reload constants on development" do + add_to_config <<-RUBY + config.cache_classes = false + RUBY + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get '/c', to: lambda { |env| [200, {"Content-Type" => "text/plain"}, [User.counter.to_s]] } + end + RUBY + + app_file "app/models/user.rb", <<-MODEL + class User + def self.counter; 1; end + end + MODEL + + require 'rack/test' + extend Rack::Test::Methods + + require "#{rails_root}/config/environment" + + get "/c" + assert_equal "1", last_response.body + + app_file "app/models/user.rb", <<-MODEL + class User + def self.counter; 2; end + end + MODEL + + get "/c" + assert_equal "2", last_response.body + end + + test "does not reload constants on development if custom file watcher always returns false" do + add_to_config <<-RUBY + config.cache_classes = false + config.file_watcher = Class.new do + def initialize(*); end + def updated?; false; end + def execute; end + def execute_if_updated; false; end + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get '/c', to: lambda { |env| [200, {"Content-Type" => "text/plain"}, [User.counter.to_s]] } + end + RUBY + + app_file "app/models/user.rb", <<-MODEL + class User + def self.counter; 1; end + end + MODEL + + require 'rack/test' + extend Rack::Test::Methods + + require "#{rails_root}/config/environment" + + get "/c" + assert_equal "1", last_response.body + + app_file "app/models/user.rb", <<-MODEL + class User + def self.counter; 2; end + end + MODEL + + get "/c" + assert_equal "1", last_response.body + end + + test "added files (like db/schema.rb) also trigger reloading" do + add_to_config <<-RUBY + config.cache_classes = false + RUBY + + app_file 'config/routes.rb', <<-RUBY + $counter ||= 0 + Rails.application.routes.draw do + get '/c', to: lambda { |env| User.name; [200, {"Content-Type" => "text/plain"}, [$counter.to_s]] } + end + RUBY + + app_file "app/models/user.rb", <<-MODEL + class User + $counter += 1 + end + MODEL + + require 'rack/test' + extend Rack::Test::Methods + + require "#{rails_root}/config/environment" + + get "/c" + assert_equal "1", last_response.body + + app_file "db/schema.rb", "" + + get "/c" + assert_equal "2", last_response.body + end + + test "dependencies reloading is followed by routes reloading" do + add_to_config <<-RUBY + config.cache_classes = false + RUBY + + app_file 'config/routes.rb', <<-RUBY + $counter ||= 1 + $counter *= 2 + Rails.application.routes.draw do + get '/c', to: lambda { |env| User.name; [200, {"Content-Type" => "text/plain"}, [$counter.to_s]] } + end + RUBY + + app_file "app/models/user.rb", <<-MODEL + class User + $counter += 1 + end + MODEL + + require 'rack/test' + extend Rack::Test::Methods + + require "#{rails_root}/config/environment" + + get "/c" + assert_equal "3", last_response.body + + app_file "db/schema.rb", "" + + get "/c" + assert_equal "7", last_response.body + end + + test "columns migrations also trigger reloading" do + add_to_config <<-RUBY + config.cache_classes = false + RUBY + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get '/title', to: lambda { |env| [200, {"Content-Type" => "text/plain"}, [Post.new.title]] } + get '/body', to: lambda { |env| [200, {"Content-Type" => "text/plain"}, [Post.new.body]] } + end + RUBY + + app_file "app/models/post.rb", <<-MODEL + class Post < ActiveRecord::Base + end + MODEL + + require 'rack/test' + extend Rack::Test::Methods + + app_file "db/migrate/1_create_posts.rb", <<-MIGRATION + class CreatePosts < ActiveRecord::Migration + def change + create_table :posts do |t| + t.string :title, default: "TITLE" + end + end + end + MIGRATION + + Dir.chdir(app_path) { `rake db:migrate`} + require "#{rails_root}/config/environment" + + get "/title" + assert_equal "TITLE", last_response.body + + app_file "db/migrate/2_add_body_to_posts.rb", <<-MIGRATION + class AddBodyToPosts < ActiveRecord::Migration + def change + add_column :posts, :body, :text, default: "BODY" + end + end + MIGRATION + + Dir.chdir(app_path) { `rake db:migrate` } + + get "/body" + assert_equal "BODY", last_response.body + end + + test "AC load hooks can be used with metal" do + app_file "app/controllers/omg_controller.rb", <<-RUBY + begin + class OmgController < ActionController::Metal + ActiveSupport.run_load_hooks(:action_controller, self) + def show + self.response_body = ["OK"] + end + end + rescue => e + puts "Error loading metal: \#{e.class} \#{e.message}" + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get "/:controller(/:action)" + end + RUBY + + require "#{rails_root}/config/environment" + + require 'rack/test' + extend Rack::Test::Methods + + get '/omg/show' + assert_equal 'OK', last_response.body + end + + def test_initialize_can_be_called_at_any_time + require "#{app_path}/config/application" + + assert !Rails.initialized? + assert !Rails.application.initialized? + Rails.initialize! + assert Rails.initialized? + assert Rails.application.initialized? + end + + protected + + def setup_ar! + ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:") + ActiveRecord::Migration.verbose = false + ActiveRecord::Schema.define(version: 1) do + create_table :posts do |t| + t.string :title + end + end + end +end diff --git a/railties/test/application/mailer_previews_test.rb b/railties/test/application/mailer_previews_test.rb new file mode 100644 index 0000000000..643d876a26 --- /dev/null +++ b/railties/test/application/mailer_previews_test.rb @@ -0,0 +1,701 @@ +require 'isolation/abstract_unit' +require 'rack/test' +require 'base64' + +module ApplicationTests + class MailerPreviewsTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + include Rack::Test::Methods + + def setup + build_app + boot_rails + end + + def teardown + teardown_app + end + + test "/rails/mailers is accessible in development" do + app("development") + get "/rails/mailers" + assert_equal 200, last_response.status + end + + test "/rails/mailers is not accessible in production" do + app("production") + get "/rails/mailers" + assert_equal 404, last_response.status + end + + test "/rails/mailers is accessible with correct configuraiton" do + add_to_config "config.action_mailer.show_previews = true" + app("production") + get "/rails/mailers", {}, {"REMOTE_ADDR" => "4.2.42.42"} + assert_equal 200, last_response.status + end + + test "/rails/mailers is not accessible with show_previews = false" do + add_to_config "config.action_mailer.show_previews = false" + app("development") + get "/rails/mailers" + assert_equal 404, last_response.status + end + + test "/rails/mailers is accessible with globbing route present" do + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get '*foo', to: 'foo#index' + end + RUBY + app("development") + get "/rails/mailers" + assert_equal 200, last_response.status + end + + test "mailer previews are loaded from the default preview_path" do + mailer 'notifier', <<-RUBY + class Notifier < ActionMailer::Base + default from: "from@example.com" + + def foo + mail to: "to@example.org" + end + end + RUBY + + text_template 'notifier/foo', <<-RUBY + Hello, World! + RUBY + + mailer_preview 'notifier', <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo + end + end + RUBY + + app('development') + + get "/rails/mailers" + assert_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body + assert_match '<li><a href="/rails/mailers/notifier/foo">foo</a></li>', last_response.body + end + + test "mailer previews are loaded from a custom preview_path" do + add_to_config "config.action_mailer.preview_path = '#{app_path}/lib/mailer_previews'" + + mailer 'notifier', <<-RUBY + class Notifier < ActionMailer::Base + default from: "from@example.com" + + def foo + mail to: "to@example.org" + end + end + RUBY + + text_template 'notifier/foo', <<-RUBY + Hello, World! + RUBY + + app_file 'lib/mailer_previews/notifier_preview.rb', <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo + end + end + RUBY + + app('development') + + get "/rails/mailers" + assert_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body + assert_match '<li><a href="/rails/mailers/notifier/foo">foo</a></li>', last_response.body + end + + test "mailer previews are reloaded across requests" do + app('development') + + get "/rails/mailers" + assert_no_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body + + mailer 'notifier', <<-RUBY + class Notifier < ActionMailer::Base + default from: "from@example.com" + + def foo + mail to: "to@example.org" + end + end + RUBY + + text_template 'notifier/foo', <<-RUBY + Hello, World! + RUBY + + mailer_preview 'notifier', <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo + end + end + RUBY + + get "/rails/mailers" + assert_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body + + remove_file 'test/mailers/previews/notifier_preview.rb' + sleep(1) + + get "/rails/mailers" + assert_no_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body + end + + test "mailer preview actions are added and removed" do + mailer 'notifier', <<-RUBY + class Notifier < ActionMailer::Base + default from: "from@example.com" + + def foo + mail to: "to@example.org" + end + end + RUBY + + text_template 'notifier/foo', <<-RUBY + Hello, World! + RUBY + + mailer_preview 'notifier', <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo + end + end + RUBY + + app('development') + + get "/rails/mailers" + assert_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body + assert_match '<li><a href="/rails/mailers/notifier/foo">foo</a></li>', last_response.body + assert_no_match '<li><a href="/rails/mailers/notifier/bar">bar</a></li>', last_response.body + + mailer 'notifier', <<-RUBY + class Notifier < ActionMailer::Base + default from: "from@example.com" + + def foo + mail to: "to@example.org" + end + + def bar + mail to: "to@example.net" + end + end + RUBY + + text_template 'notifier/foo', <<-RUBY + Hello, World! + RUBY + + text_template 'notifier/bar', <<-RUBY + Goodbye, World! + RUBY + + mailer_preview 'notifier', <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo + end + + def bar + Notifier.bar + end + end + RUBY + + sleep(1) + + get "/rails/mailers" + assert_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body + assert_match '<li><a href="/rails/mailers/notifier/foo">foo</a></li>', last_response.body + assert_match '<li><a href="/rails/mailers/notifier/bar">bar</a></li>', last_response.body + + mailer 'notifier', <<-RUBY + class Notifier < ActionMailer::Base + default from: "from@example.com" + + def foo + mail to: "to@example.org" + end + end + RUBY + + remove_file 'app/views/notifier/bar.text.erb' + + mailer_preview 'notifier', <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo + end + end + RUBY + + sleep(1) + + get "/rails/mailers" + assert_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body + assert_match '<li><a href="/rails/mailers/notifier/foo">foo</a></li>', last_response.body + assert_no_match '<li><a href="/rails/mailers/notifier/bar">bar</a></li>', last_response.body + end + + test "mailer previews are reloaded from a custom preview_path" do + add_to_config "config.action_mailer.preview_path = '#{app_path}/lib/mailer_previews'" + + app('development') + + get "/rails/mailers" + assert_no_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body + + mailer 'notifier', <<-RUBY + class Notifier < ActionMailer::Base + default from: "from@example.com" + + def foo + mail to: "to@example.org" + end + end + RUBY + + text_template 'notifier/foo', <<-RUBY + Hello, World! + RUBY + + app_file 'lib/mailer_previews/notifier_preview.rb', <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo + end + end + RUBY + + get "/rails/mailers" + assert_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body + + remove_file 'lib/mailer_previews/notifier_preview.rb' + sleep(1) + + get "/rails/mailers" + assert_no_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body + end + + test "mailer preview not found" do + app('development') + get "/rails/mailers/notifier" + assert last_response.not_found? + assert_match "Mailer preview 'notifier' not found", last_response.body + end + + test "mailer preview email not found" do + mailer 'notifier', <<-RUBY + class Notifier < ActionMailer::Base + default from: "from@example.com" + + def foo + mail to: "to@example.org" + end + end + RUBY + + text_template 'notifier/foo', <<-RUBY + Hello, World! + RUBY + + mailer_preview 'notifier', <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo + end + end + RUBY + + app('development') + + get "/rails/mailers/notifier/bar" + assert last_response.not_found? + assert_match "Email 'bar' not found in NotifierPreview", last_response.body + end + + test "mailer preview NullMail" do + mailer 'notifier', <<-RUBY + class Notifier < ActionMailer::Base + default from: "from@example.com" + + def foo + # does not call +mail+ + end + end + RUBY + + mailer_preview 'notifier', <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo + end + end + RUBY + + app('development') + + get "/rails/mailers/notifier/foo" + assert_match "You are trying to preview an email that does not have any content.", last_response.body + assert_match "notifier#foo", last_response.body + end + + test "mailer preview email part not found" do + mailer 'notifier', <<-RUBY + class Notifier < ActionMailer::Base + default from: "from@example.com" + + def foo + mail to: "to@example.org" + end + end + RUBY + + text_template 'notifier/foo', <<-RUBY + Hello, World! + RUBY + + mailer_preview 'notifier', <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo + end + end + RUBY + + app('development') + + get "/rails/mailers/notifier/foo?part=text%2Fhtml" + assert last_response.not_found? + assert_match "Email part 'text/html' not found in NotifierPreview#foo", last_response.body + end + + test "message header uses full display names" do + mailer 'notifier', <<-RUBY + class Notifier < ActionMailer::Base + default from: "Ruby on Rails <core@rubyonrails.org>" + + def foo + mail to: "Andrew White <andyw@pixeltrix.co.uk>", + cc: "David Heinemeier Hansson <david@heinemeierhansson.com>" + end + end + RUBY + + text_template 'notifier/foo', <<-RUBY + Hello, World! + RUBY + + mailer_preview 'notifier', <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo + end + end + RUBY + + app('development') + + get "/rails/mailers/notifier/foo" + assert_equal 200, last_response.status + assert_match "Ruby on Rails <core@rubyonrails.org>", last_response.body + assert_match "Andrew White <andyw@pixeltrix.co.uk>", last_response.body + assert_match "David Heinemeier Hansson <david@heinemeierhansson.com>", last_response.body + end + + test "part menu selects correct option" do + mailer 'notifier', <<-RUBY + class Notifier < ActionMailer::Base + default from: "from@example.com" + + def foo + mail to: "to@example.org" + end + end + RUBY + + html_template 'notifier/foo', <<-RUBY + <p>Hello, World!</p> + RUBY + + text_template 'notifier/foo', <<-RUBY + Hello, World! + RUBY + + mailer_preview 'notifier', <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo + end + end + RUBY + + app('development') + + get "/rails/mailers/notifier/foo.html" + assert_equal 200, last_response.status + assert_match '<option selected value="?part=text%2Fhtml">View as HTML email</option>', last_response.body + + get "/rails/mailers/notifier/foo.txt" + assert_equal 200, last_response.status + assert_match '<option selected value="?part=text%2Fplain">View as plain-text email</option>', last_response.body + end + + test "mailer previews create correct links when loaded on a subdirectory" do + mailer 'notifier', <<-RUBY + class Notifier < ActionMailer::Base + default from: "from@example.com" + + def foo + mail to: "to@example.org" + end + end + RUBY + + text_template 'notifier/foo', <<-RUBY + Hello, World! + RUBY + + mailer_preview 'notifier', <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo + end + end + RUBY + + app('development') + + get "/rails/mailers", {}, 'SCRIPT_NAME' => '/my_app' + assert_match '<h3><a href="/my_app/rails/mailers/notifier">Notifier</a></h3>', last_response.body + assert_match '<li><a href="/my_app/rails/mailers/notifier/foo">foo</a></li>', last_response.body + end + + test "plain text mailer preview with attachment" do + image_file "pixel.png", "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEWzIioca/JlAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJgggo=" + + mailer 'notifier', <<-RUBY + class Notifier < ActionMailer::Base + default from: "from@example.com" + + def foo + attachments['pixel.png'] = File.read("#{app_path}/public/images/pixel.png", mode: 'rb') + mail to: "to@example.org" + end + end + RUBY + + text_template 'notifier/foo', <<-RUBY + Hello, World! + RUBY + + mailer_preview 'notifier', <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo + end + end + RUBY + + app('development') + + get "/rails/mailers/notifier/foo" + assert_equal 200, last_response.status + assert_match %r[<iframe seamless name="messageBody"], last_response.body + + get "/rails/mailers/notifier/foo?part=text/plain" + assert_equal 200, last_response.status + assert_match %r[Hello, World!], last_response.body + end + + test "multipart mailer preview with attachment" do + image_file "pixel.png", "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEWzIioca/JlAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJgggo=" + + mailer 'notifier', <<-RUBY + class Notifier < ActionMailer::Base + default from: "from@example.com" + + def foo + attachments['pixel.png'] = File.read("#{app_path}/public/images/pixel.png", mode: 'rb') + mail to: "to@example.org" + end + end + RUBY + + text_template 'notifier/foo', <<-RUBY + Hello, World! + RUBY + + html_template 'notifier/foo', <<-RUBY + <p>Hello, World!</p> + RUBY + + mailer_preview 'notifier', <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo + end + end + RUBY + + app('development') + + get "/rails/mailers/notifier/foo" + assert_equal 200, last_response.status + assert_match %r[<iframe seamless name="messageBody"], last_response.body + + get "/rails/mailers/notifier/foo?part=text/plain" + assert_equal 200, last_response.status + assert_match %r[Hello, World!], last_response.body + + get "/rails/mailers/notifier/foo?part=text/html" + assert_equal 200, last_response.status + assert_match %r[<p>Hello, World!</p>], last_response.body + end + + test "multipart mailer preview with inline attachment" do + image_file "pixel.png", "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEWzIioca/JlAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJgggo=" + + mailer 'notifier', <<-RUBY + class Notifier < ActionMailer::Base + default from: "from@example.com" + + def foo + attachments['pixel.png'] = File.read("#{app_path}/public/images/pixel.png", mode: 'rb') + mail to: "to@example.org" + end + end + RUBY + + text_template 'notifier/foo', <<-RUBY + Hello, World! + RUBY + + html_template 'notifier/foo', <<-RUBY + <p>Hello, World!</p> + <%= image_tag attachments['pixel.png'].url %> + RUBY + + mailer_preview 'notifier', <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo + end + end + RUBY + + app('development') + + get "/rails/mailers/notifier/foo" + assert_equal 200, last_response.status + assert_match %r[<iframe seamless name="messageBody"], last_response.body + + get "/rails/mailers/notifier/foo?part=text/plain" + assert_equal 200, last_response.status + assert_match %r[Hello, World!], last_response.body + + get "/rails/mailers/notifier/foo?part=text/html" + assert_equal 200, last_response.status + assert_match %r[<p>Hello, World!</p>], last_response.body + assert_match %r[src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEWzIioca/JlAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJgggo="], last_response.body + end + + test "multipart mailer preview with attached email" do + mailer 'notifier', <<-RUBY + class Notifier < ActionMailer::Base + default from: "from@example.com" + + def foo + message = ::Mail.new do + from 'foo@example.com' + to 'bar@example.com' + subject 'Important Message' + + text_part do + body 'Goodbye, World!' + end + + html_part do + body '<p>Goodbye, World!</p>' + end + end + + attachments['message.eml'] = message.to_s + mail to: "to@example.org" + end + end + RUBY + + text_template 'notifier/foo', <<-RUBY + Hello, World! + RUBY + + html_template 'notifier/foo', <<-RUBY + <p>Hello, World!</p> + RUBY + + mailer_preview 'notifier', <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo + end + end + RUBY + + app('development') + + get "/rails/mailers/notifier/foo" + assert_equal 200, last_response.status + assert_match %r[<iframe seamless name="messageBody"], last_response.body + + get "/rails/mailers/notifier/foo?part=text/plain" + assert_equal 200, last_response.status + assert_match %r[Hello, World!], last_response.body + + get "/rails/mailers/notifier/foo?part=text/html" + assert_equal 200, last_response.status + assert_match %r[<p>Hello, World!</p>], last_response.body + end + + private + def build_app + super + app_file "config/routes.rb", "Rails.application.routes.draw do; end" + end + + def mailer(name, contents) + app_file("app/mailers/#{name}.rb", contents) + end + + def mailer_preview(name, contents) + app_file("test/mailers/previews/#{name}_preview.rb", contents) + end + + def html_template(name, contents) + app_file("app/views/#{name}.html.erb", contents) + end + + def text_template(name, contents) + app_file("app/views/#{name}.text.erb", contents) + end + + def image_file(name, contents) + app_file("public/images/#{name}", Base64.strict_decode64(contents), 'wb') + end + end +end diff --git a/railties/test/application/middleware/cache_test.rb b/railties/test/application/middleware/cache_test.rb new file mode 100644 index 0000000000..c951dabd6c --- /dev/null +++ b/railties/test/application/middleware/cache_test.rb @@ -0,0 +1,180 @@ +require 'isolation/abstract_unit' + +module ApplicationTests + class CacheTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + require 'rack/test' + extend Rack::Test::Methods + end + + def teardown + teardown_app + end + + def simple_controller + controller :expires, <<-RUBY + class ExpiresController < ApplicationController + def expires_header + expires_in 10, public: !params[:private] + render text: SecureRandom.hex(16) + end + + def expires_etag + render_conditionally(etag: "1") + end + + def expires_last_modified + $last_modified ||= Time.now.utc + render_conditionally(last_modified: $last_modified) + end + + def keeps_if_modified_since + render :text => request.headers['If-Modified-Since'] + end + private + def render_conditionally(headers) + if stale?(headers.merge(public: !params[:private])) + render text: SecureRandom.hex(16) + end + end + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get ':controller(/:action)' + end + RUBY + end + + def test_cache_keeps_if_modified_since + simple_controller + expected = "Wed, 30 May 1984 19:43:31 GMT" + + get "/expires/keeps_if_modified_since", {}, "HTTP_IF_MODIFIED_SINCE" => expected + + assert_equal 200, last_response.status + assert_equal expected, last_response.body, "cache should have kept If-Modified-Since" + end + + def test_cache_is_disabled_in_dev_mode + simple_controller + app("development") + + get "/expires/expires_header" + assert_nil last_response.headers['X-Rack-Cache'] + + body = last_response.body + + get "/expires/expires_header" + assert_nil last_response.headers['X-Rack-Cache'] + assert_not_equal body, last_response.body + end + + def test_cache_works_with_expires + simple_controller + + add_to_config "config.action_dispatch.rack_cache = true" + + get "/expires/expires_header" + assert_equal "miss, store", last_response.headers["X-Rack-Cache"] + assert_equal "max-age=10, public", last_response.headers["Cache-Control"] + + body = last_response.body + + get "/expires/expires_header" + + assert_equal "fresh", last_response.headers["X-Rack-Cache"] + + assert_equal body, last_response.body + end + + def test_cache_works_with_expires_private + simple_controller + + add_to_config "config.action_dispatch.rack_cache = true" + + get "/expires/expires_header", private: true + assert_equal "miss", last_response.headers["X-Rack-Cache"] + assert_equal "private, max-age=10", last_response.headers["Cache-Control"] + + body = last_response.body + + get "/expires/expires_header", private: true + assert_equal "miss", last_response.headers["X-Rack-Cache"] + assert_not_equal body, last_response.body + end + + def test_cache_works_with_etags + simple_controller + + add_to_config "config.action_dispatch.rack_cache = true" + + get "/expires/expires_etag" + assert_equal "miss, store", last_response.headers["X-Rack-Cache"] + assert_equal "public", last_response.headers["Cache-Control"] + + body = last_response.body + etag = last_response.headers["ETag"] + + get "/expires/expires_etag", {}, "If-None-Match" => etag + assert_equal "stale, valid, store", last_response.headers["X-Rack-Cache"] + assert_equal body, last_response.body + end + + def test_cache_works_with_etags_private + simple_controller + + add_to_config "config.action_dispatch.rack_cache = true" + + get "/expires/expires_etag", private: true + assert_equal "miss", last_response.headers["X-Rack-Cache"] + assert_equal "must-revalidate, private, max-age=0", last_response.headers["Cache-Control"] + + body = last_response.body + etag = last_response.headers["ETag"] + + get "/expires/expires_etag", {private: true}, "If-None-Match" => etag + assert_equal "miss", last_response.headers["X-Rack-Cache"] + assert_not_equal body, last_response.body + end + + def test_cache_works_with_last_modified + simple_controller + + add_to_config "config.action_dispatch.rack_cache = true" + + get "/expires/expires_last_modified" + assert_equal "miss, store", last_response.headers["X-Rack-Cache"] + assert_equal "public", last_response.headers["Cache-Control"] + + body = last_response.body + last = last_response.headers["Last-Modified"] + + get "/expires/expires_last_modified", {}, "If-Modified-Since" => last + assert_equal "stale, valid, store", last_response.headers["X-Rack-Cache"] + assert_equal body, last_response.body + end + + def test_cache_works_with_last_modified_private + simple_controller + + add_to_config "config.action_dispatch.rack_cache = true" + + get "/expires/expires_last_modified", private: true + assert_equal "miss", last_response.headers["X-Rack-Cache"] + assert_equal "must-revalidate, private, max-age=0", last_response.headers["Cache-Control"] + + body = last_response.body + last = last_response.headers["Last-Modified"] + + get "/expires/expires_last_modified", {private: true}, "If-Modified-Since" => last + assert_equal "miss", last_response.headers["X-Rack-Cache"] + assert_not_equal body, last_response.body + end + end +end diff --git a/railties/test/application/middleware/cookies_test.rb b/railties/test/application/middleware/cookies_test.rb new file mode 100644 index 0000000000..bbb7627be9 --- /dev/null +++ b/railties/test/application/middleware/cookies_test.rb @@ -0,0 +1,47 @@ +require 'isolation/abstract_unit' + +module ApplicationTests + class CookiesTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def new_app + File.expand_path("#{app_path}/../new_app") + end + + def setup + build_app + boot_rails + FileUtils.rm_rf("#{app_path}/config/environments") + end + + def teardown + teardown_app + FileUtils.rm_rf(new_app) if File.directory?(new_app) + end + + test 'always_write_cookie is true by default in development' do + require 'rails' + Rails.env = 'development' + require "#{app_path}/config/environment" + assert_equal true, ActionDispatch::Cookies::CookieJar.always_write_cookie + end + + test 'always_write_cookie is false by default in production' do + require 'rails' + Rails.env = 'production' + require "#{app_path}/config/environment" + assert_equal false, ActionDispatch::Cookies::CookieJar.always_write_cookie + end + + test 'always_write_cookie can be overridden' do + add_to_config <<-RUBY + config.action_dispatch.always_write_cookie = false + RUBY + + require 'rails' + Rails.env = 'development' + require "#{app_path}/config/environment" + assert_equal false, ActionDispatch::Cookies::CookieJar.always_write_cookie + end + end +end diff --git a/railties/test/application/middleware/exceptions_test.rb b/railties/test/application/middleware/exceptions_test.rb new file mode 100644 index 0000000000..7b4babb13b --- /dev/null +++ b/railties/test/application/middleware/exceptions_test.rb @@ -0,0 +1,125 @@ +require 'isolation/abstract_unit' +require 'rack/test' + +module ApplicationTests + class MiddlewareExceptionsTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + include Rack::Test::Methods + + def setup + build_app + boot_rails + end + + def teardown + teardown_app + end + + test "show exceptions middleware filter backtrace before logging" do + controller :foo, <<-RUBY + class FooController < ActionController::Base + def index + raise 'oops' + end + end + RUBY + + get "/foo" + assert_equal 500, last_response.status + + log = File.read(Rails.application.config.paths["log"].first) + assert_no_match(/action_dispatch/, log, log) + assert_match(/oops/, log, log) + end + + test "renders active record exceptions as 404" do + controller :foo, <<-RUBY + class FooController < ActionController::Base + def index + raise ActiveRecord::RecordNotFound + end + end + RUBY + + get "/foo" + assert_equal 404, last_response.status + end + + test "uses custom exceptions app" do + add_to_config <<-RUBY + config.exceptions_app = lambda do |env| + [404, { "Content-Type" => "text/plain" }, ["YOU FAILED"]] + end + RUBY + + app.config.action_dispatch.show_exceptions = true + + get "/foo" + assert_equal 404, last_response.status + assert_equal "YOU FAILED", last_response.body + end + + test "url generation error when action_dispatch.show_exceptions is set raises an exception" do + controller :foo, <<-RUBY + class FooController < ActionController::Base + def index + raise ActionController::UrlGenerationError + end + end + RUBY + + app.config.action_dispatch.show_exceptions = true + + get '/foo' + assert_equal 500, last_response.status + end + + test "unspecified route when action_dispatch.show_exceptions is not set raises an exception" do + app.config.action_dispatch.show_exceptions = false + + assert_raise(ActionController::RoutingError) do + get '/foo' + end + end + + test "unspecified route when action_dispatch.show_exceptions is set shows 404" do + app.config.action_dispatch.show_exceptions = true + + assert_nothing_raised(ActionController::RoutingError) do + get '/foo' + assert_match "The page you were looking for doesn't exist.", last_response.body + end + end + + test "unspecified route when action_dispatch.show_exceptions and consider_all_requests_local are set shows diagnostics" do + app.config.action_dispatch.show_exceptions = true + app.config.consider_all_requests_local = true + + assert_nothing_raised(ActionController::RoutingError) do + get '/foo' + assert_match "No route matches", last_response.body + end + end + + test "displays diagnostics message when exception raised in template that contains UTF-8" do + controller :foo, <<-RUBY + class FooController < ActionController::Base + def index + end + end + RUBY + + app.config.action_dispatch.show_exceptions = true + app.config.consider_all_requests_local = true + + app_file 'app/views/foo/index.html.erb', <<-ERB + <% raise 'boooom' %> + ✓測試テスト시험 + ERB + + get '/foo', :utf8 => '✓' + assert_match(/boooom/, last_response.body) + assert_match(/測試テスト시험/, last_response.body) + end + end +end diff --git a/railties/test/application/middleware/remote_ip_test.rb b/railties/test/application/middleware/remote_ip_test.rb new file mode 100644 index 0000000000..97d5b5c698 --- /dev/null +++ b/railties/test/application/middleware/remote_ip_test.rb @@ -0,0 +1,78 @@ +require 'ipaddr' +require 'isolation/abstract_unit' +require 'active_support/key_generator' + +module ApplicationTests + class RemoteIpTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def remote_ip(env = {}) + remote_ip = nil + env = Rack::MockRequest.env_for("/").merge(env).merge!( + 'action_dispatch.show_exceptions' => false, + 'action_dispatch.key_generator' => ActiveSupport::LegacyKeyGenerator.new('b3c631c314c0bbca50c1b2843150fe33') + ) + + endpoint = Proc.new do |e| + remote_ip = ActionDispatch::Request.new(e).remote_ip + [200, {}, ["Hello"]] + end + + Rails.application.middleware.build(endpoint).call(env) + remote_ip + end + + test "remote_ip works" do + make_basic_app + assert_equal "1.1.1.1", remote_ip("REMOTE_ADDR" => "1.1.1.1") + end + + test "checks IP spoofing by default" do + make_basic_app + assert_raises(ActionDispatch::RemoteIp::IpSpoofAttackError) do + remote_ip("HTTP_X_FORWARDED_FOR" => "1.1.1.1", "HTTP_CLIENT_IP" => "1.1.1.2") + end + end + + test "works with both headers individually" do + make_basic_app + assert_nothing_raised(ActionDispatch::RemoteIp::IpSpoofAttackError) do + assert_equal "1.1.1.1", remote_ip("HTTP_X_FORWARDED_FOR" => "1.1.1.1") + end + assert_nothing_raised(ActionDispatch::RemoteIp::IpSpoofAttackError) do + assert_equal "1.1.1.2", remote_ip("HTTP_CLIENT_IP" => "1.1.1.2") + end + end + + test "can disable IP spoofing check" do + make_basic_app do |app| + app.config.action_dispatch.ip_spoofing_check = false + end + + assert_nothing_raised(ActionDispatch::RemoteIp::IpSpoofAttackError) do + assert_equal "1.1.1.1", remote_ip("HTTP_X_FORWARDED_FOR" => "1.1.1.1", "HTTP_CLIENT_IP" => "1.1.1.2") + end + end + + test "remote_ip works with HTTP_X_FORWARDED_FOR" do + make_basic_app + assert_equal "4.2.42.42", remote_ip("REMOTE_ADDR" => "1.1.1.1", "HTTP_X_FORWARDED_FOR" => "4.2.42.42") + end + + test "the user can set trusted proxies" do + make_basic_app do |app| + app.config.action_dispatch.trusted_proxies = /^4\.2\.42\.42$/ + end + + assert_equal "1.1.1.1", remote_ip("REMOTE_ADDR" => "1.1.1.1", "HTTP_X_FORWARDED_FOR" => "4.2.42.42") + end + + test "the user can set trusted proxies with an IPAddr argument" do + make_basic_app do |app| + app.config.action_dispatch.trusted_proxies = IPAddr.new('4.2.42.0/24') + end + + assert_equal "1.1.1.1", remote_ip("REMOTE_ADDR" => "1.1.1.1", "HTTP_X_FORWARDED_FOR" => "10.0.0.0,4.2.42.42") + end + end +end diff --git a/railties/test/application/middleware/sendfile_test.rb b/railties/test/application/middleware/sendfile_test.rb new file mode 100644 index 0000000000..be86f1a3b8 --- /dev/null +++ b/railties/test/application/middleware/sendfile_test.rb @@ -0,0 +1,74 @@ +require 'isolation/abstract_unit' + +module ApplicationTests + class SendfileTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + FileUtils.rm_rf "#{app_path}/config/environments" + end + + def teardown + teardown_app + end + + def app + @app ||= Rails.application + end + + define_method :simple_controller do + class ::OmgController < ActionController::Base + def index + send_file __FILE__ + end + end + end + + # x_sendfile_header middleware + test "config.action_dispatch.x_sendfile_header defaults to nil" do + make_basic_app + simple_controller + + get "/" + assert !last_response.headers["X-Sendfile"] + assert_equal File.read(__FILE__), last_response.body + end + + test "config.action_dispatch.x_sendfile_header can be set" do + make_basic_app do |app| + app.config.action_dispatch.x_sendfile_header = "X-Sendfile" + end + + simple_controller + + get "/" + assert_equal File.expand_path(__FILE__), last_response.headers["X-Sendfile"] + end + + test "config.action_dispatch.x_sendfile_header is sent to Rack::Sendfile" do + make_basic_app do |app| + app.config.action_dispatch.x_sendfile_header = 'X-Lighttpd-Send-File' + end + + simple_controller + + get "/" + assert_equal File.expand_path(__FILE__), last_response.headers["X-Lighttpd-Send-File"] + end + + test "files handled by ActionDispatch::Static are handled by Rack::Sendfile" do + make_basic_app do |app| + app.config.action_dispatch.x_sendfile_header = 'X-Sendfile' + app.config.public_file_server.enabled = true + app.paths["public"] = File.join(rails_root, "public") + end + + app_file "public/foo.txt", "foo" + + get "/foo.txt", "HTTP_X_SENDFILE_TYPE" => "X-Sendfile" + assert_equal File.join(rails_root, "public/foo.txt"), last_response.headers["X-Sendfile"] + end + end +end diff --git a/railties/test/application/middleware/session_test.rb b/railties/test/application/middleware/session_test.rb new file mode 100644 index 0000000000..25eadfc387 --- /dev/null +++ b/railties/test/application/middleware/session_test.rb @@ -0,0 +1,342 @@ +require 'isolation/abstract_unit' +require 'rack/test' + +module ApplicationTests + class MiddlewareSessionTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + include Rack::Test::Methods + + def setup + build_app + boot_rails + FileUtils.rm_rf "#{app_path}/config/environments" + end + + def teardown + teardown_app + end + + def app + @app ||= Rails.application + end + + test "config.force_ssl sets cookie to secure only" do + add_to_config "config.force_ssl = true" + require "#{app_path}/config/environment" + assert app.config.session_options[:secure], "Expected session to be marked as secure" + end + + test "session is not loaded if it's not used" do + make_basic_app + + class ::OmgController < ActionController::Base + def index + if params[:flash] + flash[:notice] = "notice" + end + + head :ok + end + end + + get "/?flash=true" + get "/" + + assert last_request.env["HTTP_COOKIE"] + assert !last_response.headers["Set-Cookie"] + end + + test "session is empty and isn't saved on unverified request when using :null_session protect method" do + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get ':controller(/:action)' + post ':controller(/:action)' + end + RUBY + + controller :foo, <<-RUBY + class FooController < ActionController::Base + protect_from_forgery with: :null_session + + def write_session + session[:foo] = 1 + head :ok + end + + def read_session + render text: session[:foo].inspect + end + end + RUBY + + add_to_config <<-RUBY + config.action_controller.allow_forgery_protection = true + RUBY + + require "#{app_path}/config/environment" + + get '/foo/write_session' + get '/foo/read_session' + assert_equal '1', last_response.body + + post '/foo/read_session' # Read session using POST request without CSRF token + assert_equal 'nil', last_response.body # Stored value shouldn't be accessible + + post '/foo/write_session' # Write session using POST request without CSRF token + get '/foo/read_session' # Session shouldn't be changed + assert_equal '1', last_response.body + end + + test "cookie jar is empty and isn't saved on unverified request when using :null_session protect method" do + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get ':controller(/:action)' + post ':controller(/:action)' + end + RUBY + + controller :foo, <<-RUBY + class FooController < ActionController::Base + protect_from_forgery with: :null_session + + def write_cookie + cookies[:foo] = '1' + head :ok + end + + def read_cookie + render text: cookies[:foo].inspect + end + end + RUBY + + add_to_config <<-RUBY + config.action_controller.allow_forgery_protection = true + RUBY + + require "#{app_path}/config/environment" + + get '/foo/write_cookie' + get '/foo/read_cookie' + assert_equal '"1"', last_response.body + + post '/foo/read_cookie' # Read cookie using POST request without CSRF token + assert_equal 'nil', last_response.body # Stored value shouldn't be accessible + + post '/foo/write_cookie' # Write cookie using POST request without CSRF token + get '/foo/read_cookie' # Cookie shouldn't be changed + assert_equal '"1"', last_response.body + end + + test "session using encrypted cookie store" do + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get ':controller(/:action)' + end + RUBY + + controller :foo, <<-RUBY + class FooController < ActionController::Base + def write_session + session[:foo] = 1 + head :ok + end + + def read_session + render text: session[:foo] + end + + def read_encrypted_cookie + render text: cookies.encrypted[:_myapp_session]['foo'] + end + + def read_raw_cookie + render text: cookies[:_myapp_session] + end + end + RUBY + + require "#{app_path}/config/environment" + + get '/foo/write_session' + get '/foo/read_session' + assert_equal '1', last_response.body + + get '/foo/read_encrypted_cookie' + assert_equal '1', last_response.body + + secret = app.key_generator.generate_key('encrypted cookie') + sign_secret = app.key_generator.generate_key('signed encrypted cookie') + encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret) + + get '/foo/read_raw_cookie' + assert_equal 1, encryptor.decrypt_and_verify(last_response.body)['foo'] + end + + test "session upgrading signature to encryption cookie store works the same way as encrypted cookie store" do + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get ':controller(/:action)' + end + RUBY + + controller :foo, <<-RUBY + class FooController < ActionController::Base + def write_session + session[:foo] = 1 + head :ok + end + + def read_session + render text: session[:foo] + end + + def read_encrypted_cookie + render text: cookies.encrypted[:_myapp_session]['foo'] + end + + def read_raw_cookie + render text: cookies[:_myapp_session] + end + end + RUBY + + add_to_config <<-RUBY + secrets.secret_token = "3b7cd727ee24e8444053437c36cc66c4" + RUBY + + require "#{app_path}/config/environment" + + get '/foo/write_session' + get '/foo/read_session' + assert_equal '1', last_response.body + + get '/foo/read_encrypted_cookie' + assert_equal '1', last_response.body + + secret = app.key_generator.generate_key('encrypted cookie') + sign_secret = app.key_generator.generate_key('signed encrypted cookie') + encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret) + + get '/foo/read_raw_cookie' + assert_equal 1, encryptor.decrypt_and_verify(last_response.body)['foo'] + end + + test "session upgrading signature to encryption cookie store upgrades session to encrypted mode" do + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get ':controller(/:action)' + end + RUBY + + controller :foo, <<-RUBY + class FooController < ActionController::Base + def write_raw_session + # {"session_id"=>"1965d95720fffc123941bdfb7d2e6870", "foo"=>1} + cookies[:_myapp_session] = "BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJTE5NjVkOTU3MjBmZmZjMTIzOTQxYmRmYjdkMmU2ODcwBjsAVEkiCGZvbwY7AEZpBg==--315fb9931921a87ae7421aec96382f0294119749" + head :ok + end + + def write_session + session[:foo] = session[:foo] + 1 + head :ok + end + + def read_session + render text: session[:foo] + end + + def read_encrypted_cookie + render text: cookies.encrypted[:_myapp_session]['foo'] + end + + def read_raw_cookie + render text: cookies[:_myapp_session] + end + end + RUBY + + add_to_config <<-RUBY + secrets.secret_token = "3b7cd727ee24e8444053437c36cc66c4" + RUBY + + require "#{app_path}/config/environment" + + get '/foo/write_raw_session' + get '/foo/read_session' + assert_equal '1', last_response.body + + get '/foo/write_session' + get '/foo/read_session' + assert_equal '2', last_response.body + + get '/foo/read_encrypted_cookie' + assert_equal '2', last_response.body + + secret = app.key_generator.generate_key('encrypted cookie') + sign_secret = app.key_generator.generate_key('signed encrypted cookie') + encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret) + + get '/foo/read_raw_cookie' + assert_equal 2, encryptor.decrypt_and_verify(last_response.body)['foo'] + end + + test "session upgrading legacy signed cookies to new signed cookies" do + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get ':controller(/:action)' + end + RUBY + + controller :foo, <<-RUBY + class FooController < ActionController::Base + def write_raw_session + # {"session_id"=>"1965d95720fffc123941bdfb7d2e6870", "foo"=>1} + cookies[:_myapp_session] = "BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJTE5NjVkOTU3MjBmZmZjMTIzOTQxYmRmYjdkMmU2ODcwBjsAVEkiCGZvbwY7AEZpBg==--315fb9931921a87ae7421aec96382f0294119749" + head :ok + end + + def write_session + session[:foo] = session[:foo] + 1 + head :ok + end + + def read_session + render text: session[:foo] + end + + def read_signed_cookie + render text: cookies.signed[:_myapp_session]['foo'] + end + + def read_raw_cookie + render text: cookies[:_myapp_session] + end + end + RUBY + + add_to_config <<-RUBY + secrets.secret_token = "3b7cd727ee24e8444053437c36cc66c4" + secrets.secret_key_base = nil + RUBY + + require "#{app_path}/config/environment" + + get '/foo/write_raw_session' + get '/foo/read_session' + assert_equal '1', last_response.body + + get '/foo/write_session' + get '/foo/read_session' + assert_equal '2', last_response.body + + get '/foo/read_signed_cookie' + assert_equal '2', last_response.body + + verifier = ActiveSupport::MessageVerifier.new(app.secrets.secret_token) + + get '/foo/read_raw_cookie' + assert_equal 2, verifier.verify(last_response.body)['foo'] + end + end +end diff --git a/railties/test/application/middleware/static_test.rb b/railties/test/application/middleware/static_test.rb new file mode 100644 index 0000000000..1246e20d94 --- /dev/null +++ b/railties/test/application/middleware/static_test.rb @@ -0,0 +1,68 @@ +require 'isolation/abstract_unit' +require 'rack/test' + +module ApplicationTests + class MiddlewareStaticTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + include Rack::Test::Methods + + def setup + build_app + FileUtils.rm_rf "#{app_path}/config/environments" + end + + def teardown + teardown_app + end + + # Regression test to #8907 + # See https://github.com/rails/rails/commit/9cc82b77196d21a5c7021f6dca59ab9b2b158a45#commitcomment-2416514 + test "doesn't set Cache-Control header when it is nil" do + app_file "public/foo.html", 'static' + + require "#{app_path}/config/environment" + + get 'foo' + + assert_not last_response.headers.has_key?('Cache-Control'), "Cache-Control should not be set" + end + + test "headers for static files are configurable" do + app_file "public/about.html", 'static' + add_to_config <<-CONFIG + config.public_file_server.headers = { + "Access-Control-Allow-Origin" => "http://rubyonrails.org", + "Cache-Control" => "public, max-age=60" + } + CONFIG + + require "#{app_path}/config/environment" + + get '/about.html' + + assert_equal 'http://rubyonrails.org', last_response.headers["Access-Control-Allow-Origin"] + assert_equal 'public, max-age=60', last_response.headers["Cache-Control"] + end + + test "public_file_server.index_name defaults to 'index'" do + app_file "public/index.html", "/index.html" + + require "#{app_path}/config/environment" + + get '/' + + assert_equal "/index.html\n", last_response.body + end + + test "public_file_server.index_name configurable" do + app_file "public/other-index.html", "/other-index.html" + add_to_config "config.public_file_server.index_name = 'other-index'" + + require "#{app_path}/config/environment" + + get '/' + + assert_equal "/other-index.html\n", last_response.body + end + end +end diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb new file mode 100644 index 0000000000..1434522cce --- /dev/null +++ b/railties/test/application/middleware_test.rb @@ -0,0 +1,298 @@ +require 'isolation/abstract_unit' + +module ApplicationTests + class MiddlewareTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + FileUtils.rm_rf "#{app_path}/config/environments" + end + + def teardown + teardown_app + end + + def app + @app ||= Rails.application + end + + test "default middleware stack" do + add_to_config "config.active_record.migration_error = :page_load" + + boot! + + assert_equal [ + "Rack::Sendfile", + "ActionDispatch::Static", + "ActionDispatch::LoadInterlock", + "ActiveSupport::Cache::Strategy::LocalCache", + "Rack::Runtime", + "Rack::MethodOverride", + "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::Migration::CheckPending", + "ActiveRecord::ConnectionAdapters::ConnectionManagement", + "ActiveRecord::QueryCache", + "ActionDispatch::Cookies", + "ActionDispatch::Session::CookieStore", + "ActionDispatch::Flash", + "Rack::Head", + "Rack::ConditionalGet", + "Rack::ETag" + ], middleware + end + + test "api middleware stack" do + add_to_config "config.api_only = true" + + boot! + + assert_equal [ + "Rack::Sendfile", + "ActionDispatch::Static", + "ActionDispatch::LoadInterlock", + "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", + "Rack::Head", + "Rack::ConditionalGet", + "Rack::ETag" + ], middleware + end + + test "Rack::Cache is not included by default" do + boot! + + assert !middleware.include?("Rack::Cache"), "Rack::Cache is not included in the default stack unless you set config.action_dispatch.rack_cache" + end + + test "Rack::Cache is present when action_dispatch.rack_cache is set" do + add_to_config "config.action_dispatch.rack_cache = true" + + boot! + + assert middleware.include?("Rack::Cache") + end + + test "ActiveRecord::Migration::CheckPending is present when active_record.migration_error is set to :page_load" do + add_to_config "config.active_record.migration_error = :page_load" + + boot! + + assert middleware.include?("ActiveRecord::Migration::CheckPending") + end + + test "ActionDispatch::SSL is present when force_ssl is set" do + add_to_config "config.force_ssl = true" + boot! + assert middleware.include?("ActionDispatch::SSL") + end + + test "ActionDispatch::SSL is configured with options when given" do + add_to_config "config.force_ssl = true" + add_to_config "config.ssl_options = { host: 'example.com' }" + boot! + + assert_equal [{host: 'example.com'}], Rails.application.middleware.first.args + end + + test "removing Active Record omits its middleware" do + use_frameworks [] + boot! + assert !middleware.include?("ActiveRecord::ConnectionAdapters::ConnectionManagement") + assert !middleware.include?("ActiveRecord::QueryCache") + assert !middleware.include?("ActiveRecord::Migration::CheckPending") + end + + test "includes interlock if cache_classes is set but eager_load is not" do + add_to_config "config.cache_classes = true" + boot! + assert_not_includes middleware, "Rack::Lock" + assert_includes middleware, "ActionDispatch::LoadInterlock" + end + + test "includes interlock if cache_classes is off" do + add_to_config "config.cache_classes = false" + boot! + assert_not_includes middleware, "Rack::Lock" + assert_includes middleware, "ActionDispatch::LoadInterlock" + end + + test "does not include lock if cache_classes is set and so is eager_load" do + add_to_config "config.cache_classes = true" + add_to_config "config.eager_load = true" + boot! + assert_not_includes middleware, "Rack::Lock" + assert_not_includes middleware, "ActionDispatch::LoadInterlock" + end + + test "does not include lock if allow_concurrency is set to :unsafe" do + add_to_config "config.allow_concurrency = :unsafe" + boot! + assert_not_includes middleware, "Rack::Lock" + assert_not_includes middleware, "ActionDispatch::LoadInterlock" + end + + test "includes lock if allow_concurrency is disabled" do + add_to_config "config.allow_concurrency = false" + boot! + assert_includes middleware, "Rack::Lock" + assert_not_includes middleware, "ActionDispatch::LoadInterlock" + end + + test "removes static asset server if public_file_server.enabled is disabled" do + add_to_config "config.public_file_server.enabled = false" + boot! + assert !middleware.include?("ActionDispatch::Static") + end + + test "can delete a middleware from the stack" do + add_to_config "config.middleware.delete ActionDispatch::Static" + boot! + assert !middleware.include?("ActionDispatch::Static") + end + + test "can delete a middleware from the stack even if insert_before is added after delete" do + add_to_config "config.middleware.delete Rack::Runtime" + add_to_config "config.middleware.insert_before(Rack::Runtime, Rack::Config)" + boot! + assert middleware.include?("Rack::Config") + assert_not middleware.include?("Rack::Runtime") + end + + test "can delete a middleware from the stack even if insert_after is added after delete" do + add_to_config "config.middleware.delete Rack::Runtime" + add_to_config "config.middleware.insert_after(Rack::Runtime, Rack::Config)" + boot! + assert middleware.include?("Rack::Config") + assert_not middleware.include?("Rack::Runtime") + end + + test "includes exceptions middlewares even if action_dispatch.show_exceptions is disabled" do + add_to_config "config.action_dispatch.show_exceptions = false" + boot! + assert middleware.include?("ActionDispatch::ShowExceptions") + assert middleware.include?("ActionDispatch::DebugExceptions") + end + + test "removes ActionDispatch::Reloader if cache_classes is true" do + add_to_config "config.cache_classes = true" + boot! + assert !middleware.include?("ActionDispatch::Reloader") + end + + test "use middleware" do + use_frameworks [] + add_to_config "config.middleware.use Rack::Config" + boot! + assert_equal "Rack::Config", middleware.last + end + + test "insert middleware after" do + add_to_config "config.middleware.insert_after Rack::Sendfile, Rack::Config" + boot! + assert_equal "Rack::Config", middleware.second + end + + test 'unshift middleware' do + add_to_config 'config.middleware.unshift Rack::Config' + boot! + assert_equal 'Rack::Config', middleware.first + end + + test "Rails.cache does not respond to middleware" do + add_to_config "config.cache_store = :memory_store" + boot! + assert_equal "Rack::Runtime", middleware.fourth + end + + test "Rails.cache does respond to middleware" do + boot! + assert_equal "Rack::Runtime", middleware.fifth + end + + test "insert middleware before" do + add_to_config "config.middleware.insert_before Rack::Sendfile, Rack::Config" + boot! + assert_equal "Rack::Config", middleware.first + end + + test "can't change middleware after it's built" do + boot! + assert_raise RuntimeError do + app.config.middleware.use Rack::Config + end + end + + # ConditionalGet + Etag + test "conditional get + etag middlewares handle http caching based on body" do + make_basic_app + + class ::OmgController < ActionController::Base + def index + if params[:nothing] + render text: "" + else + render text: "OMG" + end + end + end + + etag = "W/" + "5af83e3196bf99f440f31f2e1a6c9afe".inspect + + get "/" + assert_equal 200, last_response.status + assert_equal "OMG", last_response.body + assert_equal "text/html; charset=utf-8", last_response.headers["Content-Type"] + assert_equal "max-age=0, private, must-revalidate", last_response.headers["Cache-Control"] + assert_equal etag, last_response.headers["Etag"] + + get "/", {}, "HTTP_IF_NONE_MATCH" => etag + assert_equal 304, last_response.status + assert_equal "", last_response.body + assert_equal nil, last_response.headers["Content-Type"] + assert_equal "max-age=0, private, must-revalidate", last_response.headers["Cache-Control"] + assert_equal etag, last_response.headers["Etag"] + + get "/?nothing=true" + assert_equal 200, last_response.status + assert_equal "", last_response.body + assert_equal "text/html; charset=utf-8", last_response.headers["Content-Type"] + assert_equal "no-cache", last_response.headers["Cache-Control"] + assert_equal nil, last_response.headers["Etag"] + end + + test "ORIGINAL_FULLPATH is passed to env" do + boot! + env = ::Rack::MockRequest.env_for("/foo/?something") + Rails.application.call(env) + + assert_equal "/foo/?something", env["ORIGINAL_FULLPATH"] + end + + private + + def boot! + require "#{app_path}/config/environment" + end + + def middleware + Rails.application.middleware.map(&:klass).map(&:name) + end + end +end diff --git a/railties/test/application/multiple_applications_test.rb b/railties/test/application/multiple_applications_test.rb new file mode 100644 index 0000000000..f2770a9cb4 --- /dev/null +++ b/railties/test/application/multiple_applications_test.rb @@ -0,0 +1,176 @@ +require 'isolation/abstract_unit' + +module ApplicationTests + class MultipleApplicationsTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app(initializers: true) + boot_rails + require "#{rails_root}/config/environment" + Rails.application.config.some_setting = 'something_or_other' + end + + def teardown + teardown_app + end + + def test_cloning_an_application_makes_a_shallow_copy_of_config + clone = Rails.application.clone + + assert_equal Rails.application.config, clone.config, "The cloned application should get a copy of the config" + assert_equal Rails.application.config.some_setting, clone.config.some_setting, "The some_setting on the config should be the same" + end + + def test_inheriting_multiple_times_from_application + new_application_class = Class.new(Rails::Application) + + assert_not_equal Rails.application.object_id, new_application_class.instance.object_id + end + + def test_initialization_of_multiple_copies_of_same_application + application1 = AppTemplate::Application.new + application2 = AppTemplate::Application.new + + assert_not_equal Rails.application.object_id, application1.object_id, "New applications should not be the same as the original application" + assert_not_equal Rails.application.object_id, application2.object_id, "New applications should not be the same as the original application" + end + + def test_initialization_of_application_with_previous_config + application1 = AppTemplate::Application.create(config: Rails.application.config) + application2 = AppTemplate::Application.create + + assert_equal Rails.application.config, application1.config, "Creating a new application while setting an initial config should result in the same config" + assert_not_equal Rails.application.config, application2.config, "New applications without setting an initial config should not have the same config" + end + + def test_initialization_of_application_with_previous_railties + application1 = AppTemplate::Application.create(railties: Rails.application.railties) + application2 = AppTemplate::Application.create + + assert_equal Rails.application.railties, application1.railties + assert_not_equal Rails.application.railties, application2.railties + end + + def test_initialize_new_application_with_all_previous_initialization_variables + application1 = AppTemplate::Application.create( + config: Rails.application.config, + railties: Rails.application.railties, + routes_reloader: Rails.application.routes_reloader, + reloaders: Rails.application.reloaders, + routes: Rails.application.routes, + helpers: Rails.application.helpers, + app_env_config: Rails.application.env_config + ) + + assert_equal Rails.application.config, application1.config + assert_equal Rails.application.railties, application1.railties + assert_equal Rails.application.routes_reloader, application1.routes_reloader + assert_equal Rails.application.reloaders, application1.reloaders + assert_equal Rails.application.routes, application1.routes + assert_equal Rails.application.helpers, application1.helpers + assert_equal Rails.application.env_config, application1.env_config + end + + def test_rake_tasks_defined_on_different_applications_go_to_the_same_class + run_count = 0 + + application1 = AppTemplate::Application.new + application1.rake_tasks do + run_count += 1 + end + + application2 = AppTemplate::Application.new + application2.rake_tasks do + run_count += 1 + end + + require "#{app_path}/config/environment" + + assert_equal 0, run_count, "The count should stay at zero without any calls to the rake tasks" + require 'rake' + require 'rake/testtask' + require 'rdoc/task' + Rails.application.load_tasks + assert_equal 2, run_count, "Calling a rake task should result in two increments to the count" + end + + def test_multiple_applications_can_be_initialized + assert_nothing_raised { AppTemplate::Application.new } + end + + def test_initializers_run_on_different_applications_go_to_the_same_class + application1 = AppTemplate::Application.new + run_count = 0 + + AppTemplate::Application.initializer :init0 do + run_count += 1 + end + + application1.initializer :init1 do + run_count += 1 + end + + AppTemplate::Application.new.initializer :init2 do + run_count += 1 + end + + assert_equal 0, run_count, "Without loading the initializers, the count should be 0" + + # Set config.eager_load to false so that an eager_load warning doesn't pop up + AppTemplate::Application.create { config.eager_load = false }.initialize! + + assert_equal 3, run_count, "There should have been three initializers that incremented the count" + end + + def test_consoles_run_on_different_applications_go_to_the_same_class + run_count = 0 + AppTemplate::Application.console { run_count += 1 } + AppTemplate::Application.new.console { run_count += 1 } + + assert_equal 0, run_count, "Without loading the consoles, the count should be 0" + Rails.application.load_console + assert_equal 2, run_count, "There should have been two consoles that increment the count" + end + + def test_generators_run_on_different_applications_go_to_the_same_class + run_count = 0 + AppTemplate::Application.generators { run_count += 1 } + AppTemplate::Application.new.generators { run_count += 1 } + + assert_equal 0, run_count, "Without loading the generators, the count should be 0" + Rails.application.load_generators + assert_equal 2, run_count, "There should have been two generators that increment the count" + end + + def test_runners_run_on_different_applications_go_to_the_same_class + run_count = 0 + AppTemplate::Application.runner { run_count += 1 } + AppTemplate::Application.new.runner { run_count += 1 } + + assert_equal 0, run_count, "Without loading the runners, the count should be 0" + Rails.application.load_runner + assert_equal 2, run_count, "There should have been two runners that increment the count" + end + + def test_isolate_namespace_on_an_application + assert_nil Rails.application.railtie_namespace, "Before isolating namespace, the railtie namespace should be nil" + Rails.application.isolate_namespace(AppTemplate) + assert_equal Rails.application.railtie_namespace, AppTemplate, "After isolating namespace, we should have a namespace" + end + + def test_inserting_configuration_into_application + app = AppTemplate::Application.new(config: Rails.application.config) + app.config.some_setting = "a_different_setting" + assert_equal "a_different_setting", app.config.some_setting, "The configuration's some_setting should be set." + + new_config = Rails::Application::Configuration.new("root_of_application") + new_config.some_setting = "some_setting_dude" + app.config = new_config + + assert_equal "some_setting_dude", app.config.some_setting, "The configuration's some_setting should have changed." + assert_equal "root_of_application", app.config.root, "The root should have changed to the new config's root." + assert_equal new_config, app.config, "The application's config should have changed to the new config." + end + end +end diff --git a/railties/test/application/paths_test.rb b/railties/test/application/paths_test.rb new file mode 100644 index 0000000000..4029984ce9 --- /dev/null +++ b/railties/test/application/paths_test.rb @@ -0,0 +1,83 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class PathsTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + FileUtils.rm_rf("#{app_path}/config/environments") + app_file "config/environments/development.rb", "" + add_to_config <<-RUBY + config.root = "#{app_path}" + config.after_initialize do |app| + app.config.session_store nil + end + RUBY + require "#{app_path}/config/environment" + @paths = Rails.application.config.paths + end + + def teardown + teardown_app + end + + def root(*path) + app_path(*path).to_s + end + + def assert_path(paths, *dir) + assert_equal [root(*dir)], paths.expanded + end + + def assert_in_load_path(*path) + assert $:.any? { |p| File.expand_path(p) == root(*path) }, "Load path does not include '#{root(*path)}'. They are:\n-----\n #{$:.join("\n")}\n-----" + end + + def assert_not_in_load_path(*path) + assert !$:.any? { |p| File.expand_path(p) == root(*path) }, "Load path includes '#{root(*path)}'. They are:\n-----\n #{$:.join("\n")}\n-----" + end + + test "booting up Rails yields a valid paths object" do + assert_path @paths["app/models"], "app/models" + assert_path @paths["app/helpers"], "app/helpers" + assert_path @paths["app/views"], "app/views" + assert_path @paths["lib"], "lib" + assert_path @paths["vendor"], "vendor" + assert_path @paths["tmp"], "tmp" + assert_path @paths["config"], "config" + assert_path @paths["config/locales"], "config/locales/en.yml" + assert_path @paths["config/environment"], "config/environment.rb" + assert_path @paths["config/environments"], "config/environments/development.rb" + + assert_equal root("app", "controllers"), @paths["app/controllers"].expanded.first + end + + test "booting up Rails yields a list of paths that are eager" do + eager_load = @paths.eager_load + assert eager_load.include?(root("app/controllers")) + assert eager_load.include?(root("app/helpers")) + assert eager_load.include?(root("app/models")) + end + + test "environments has a glob equal to the current environment" do + assert_equal "#{Rails.env}.rb", @paths["config/environments"].glob + end + + test "load path includes each of the paths in config.paths as long as the directories exist" do + assert_in_load_path "app", "controllers" + assert_in_load_path "app", "models" + assert_in_load_path "app", "helpers" + assert_in_load_path "lib" + assert_in_load_path "vendor" + + assert_not_in_load_path "app", "views" + assert_not_in_load_path "config" + assert_not_in_load_path "config", "locales" + assert_not_in_load_path "config", "environments" + assert_not_in_load_path "tmp" + assert_not_in_load_path "tmp", "cache" + end + end +end diff --git a/railties/test/application/per_request_digest_cache_test.rb b/railties/test/application/per_request_digest_cache_test.rb new file mode 100644 index 0000000000..3198e12662 --- /dev/null +++ b/railties/test/application/per_request_digest_cache_test.rb @@ -0,0 +1,63 @@ +require 'isolation/abstract_unit' +require 'rack/test' +require 'minitest/mock' + +require 'action_view' +require 'active_support/testing/method_call_assertions' + +class PerRequestDigestCacheTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + include ActiveSupport::Testing::MethodCallAssertions + include Rack::Test::Methods + + setup do + build_app + add_to_config 'config.consider_all_requests_local = true' + + app_file 'app/models/customer.rb', <<-RUBY + class Customer < Struct.new(:name, :id) + extend ActiveModel::Naming + include ActiveModel::Conversion + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + resources :customers, only: :index + end + RUBY + + app_file 'app/controllers/customers_controller.rb', <<-RUBY + class CustomersController < ApplicationController + def index + render [ Customer.new('david', 1), Customer.new('dingus', 2) ] + end + end + RUBY + + app_file 'app/views/customers/_customer.html.erb', <<-RUBY + <% cache customer do %> + <%= customer.name %> + <% end %> + RUBY + + require "#{app_path}/config/environment" + end + + teardown :teardown_app + + test "digests are reused when rendering the same template twice" do + get '/customers' + assert_equal 200, last_response.status + + assert_equal [ '8ba099b7749542fe765ff34a6824d548' ], ActionView::Digestor.cache.values + assert_equal %w(david dingus), last_response.body.split.map(&:strip) + end + + test "template digests are cleared before a request" do + assert_called(ActionView::Digestor.cache, :clear) do + get '/customers' + assert_equal 200, last_response.status + end + end +end diff --git a/railties/test/application/rack/logger_test.rb b/railties/test/application/rack/logger_test.rb new file mode 100644 index 0000000000..0082ec9cd2 --- /dev/null +++ b/railties/test/application/rack/logger_test.rb @@ -0,0 +1,56 @@ +require "isolation/abstract_unit" +require "active_support/log_subscriber/test_helper" +require "rack/test" + +module ApplicationTests + module RackTests + class LoggerTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + include ActiveSupport::LogSubscriber::TestHelper + include Rack::Test::Methods + + def setup + build_app + add_to_config <<-RUBY + config.logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new + RUBY + + require "#{app_path}/config/environment" + super + end + + def teardown + super + teardown_app + end + + def logs + @logs ||= Rails.logger.logged(:info).join("\n") + end + + test "logger logs proper HTTP GET verb and path" do + get "/blah" + wait + assert_match 'Started GET "/blah"', logs + end + + test "logger logs proper HTTP HEAD verb and path" do + head "/blah" + wait + assert_match 'Started HEAD "/blah"', logs + end + + test "logger logs HTTP verb override" do + post "/", _method: 'put' + wait + assert_match 'Started PUT "/"', logs + end + + test "logger logs HEAD requests" do + post "/", _method: 'head' + wait + assert_match 'Started HEAD "/"', logs + end + end + end +end diff --git a/railties/test/application/rackup_test.rb b/railties/test/application/rackup_test.rb new file mode 100644 index 0000000000..49ac9fc66c --- /dev/null +++ b/railties/test/application/rackup_test.rb @@ -0,0 +1,43 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class RackupTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def rackup + require "rack" + app, _ = Rack::Builder.parse_file("#{app_path}/config.ru") + app + end + + def setup + build_app + boot_rails + end + + def teardown + teardown_app + end + + test "rails app is present" do + assert File.exist?(app_path("config")) + end + + test "config.ru can be racked up" do + Dir.chdir app_path do + @app = rackup + assert_welcome get("/") + end + end + + test "Rails.application is available after config.ru has been racked up" do + rackup + assert_kind_of Rails::Application, Rails.application + end + + test "the config object is available on the application object" do + rackup + assert_equal 'UTC', Rails.application.config.time_zone + end + end +end diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb new file mode 100644 index 0000000000..0b0fb50fe1 --- /dev/null +++ b/railties/test/application/rake/dbs_test.rb @@ -0,0 +1,313 @@ +require "isolation/abstract_unit" +require "active_support/core_ext/string/strip" + +module ApplicationTests + module RakeTests + class RakeDbsTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + FileUtils.rm_rf("#{app_path}/config/environments") + end + + def teardown + teardown_app + end + + def database_url_db_name + "db/database_url_db.sqlite3" + end + + def set_database_url + ENV['DATABASE_URL'] = "sqlite3:#{database_url_db_name}" + # ensure it's using the DATABASE_URL + FileUtils.rm_rf("#{app_path}/config/database.yml") + end + + def db_create_and_drop(expected_database) + Dir.chdir(app_path) do + output = `bin/rake db:create` + assert_empty output + assert File.exist?(expected_database) + assert_equal expected_database, ActiveRecord::Base.connection_config[:database] + output = `bin/rake db:drop` + assert_empty output + assert !File.exist?(expected_database) + end + end + + test 'db:create and db:drop without database url' do + require "#{app_path}/config/environment" + db_create_and_drop ActiveRecord::Base.configurations[Rails.env]['database'] + end + + test 'db:create and db:drop with database url' do + require "#{app_path}/config/environment" + set_database_url + db_create_and_drop database_url_db_name + end + + def with_database_existing + Dir.chdir(app_path) do + set_database_url + `bin/rake db:create` + yield + `bin/rake db:drop` + end + end + + test 'db:create failure because database exists' do + with_database_existing do + output = `bin/rake db:create 2>&1` + assert_match(/already exists/, output) + assert_equal 0, $?.exitstatus + end + end + + def with_bad_permissions + Dir.chdir(app_path) do + set_database_url + FileUtils.chmod("-w", "db") + yield + FileUtils.chmod("+w", "db") + end + end + + test 'db:create failure because bad permissions' do + with_bad_permissions do + output = `bin/rake db:create 2>&1` + assert_match(/Couldn't create database/, output) + assert_equal 1, $?.exitstatus + end + end + + test 'db:drop failure because database does not exist' do + Dir.chdir(app_path) do + output = `bin/rake db:drop 2>&1` + assert_match(/does not exist/, output) + assert_equal 0, $?.exitstatus + end + end + + test 'db:drop failure because bad permissions' do + with_database_existing do + with_bad_permissions do + output = `bin/rake db:drop 2>&1` + assert_match(/Couldn't drop/, output) + assert_equal 1, $?.exitstatus + end + end + end + + def db_migrate_and_status(expected_database) + Dir.chdir(app_path) do + `bin/rails generate model book title:string; + bin/rake db:migrate` + output = `bin/rake db:migrate:status` + assert_match(%r{database:\s+\S*#{Regexp.escape(expected_database)}}, output) + assert_match(/up\s+\d{14}\s+Create books/, output) + end + end + + test 'db:migrate and db:migrate:status without database_url' do + require "#{app_path}/config/environment" + db_migrate_and_status ActiveRecord::Base.configurations[Rails.env]['database'] + end + + test 'db:migrate and db:migrate:status with database_url' do + require "#{app_path}/config/environment" + set_database_url + db_migrate_and_status database_url_db_name + end + + def db_schema_dump + Dir.chdir(app_path) do + `bin/rails generate model book title:string; + bin/rake db:migrate db:schema:dump` + schema_dump = File.read("db/schema.rb") + assert_match(/create_table \"books\"/, schema_dump) + end + end + + test 'db:schema:dump without database_url' do + db_schema_dump + end + + test 'db:schema:dump with database_url' do + set_database_url + db_schema_dump + end + + def db_fixtures_load(expected_database) + Dir.chdir(app_path) do + `bin/rails generate model book title:string; + bin/rake db:migrate db:fixtures:load` + assert_match expected_database, ActiveRecord::Base.connection_config[:database] + require "#{app_path}/app/models/book" + assert_equal 2, Book.count + end + end + + test 'db:fixtures:load without database_url' do + require "#{app_path}/config/environment" + db_fixtures_load ActiveRecord::Base.configurations[Rails.env]['database'] + end + + test 'db:fixtures:load with database_url' do + require "#{app_path}/config/environment" + set_database_url + db_fixtures_load database_url_db_name + end + + test 'db:fixtures:load with namespaced fixture' do + require "#{app_path}/config/environment" + Dir.chdir(app_path) do + `bin/rails generate model admin::book title:string; + bin/rake db:migrate db:fixtures:load` + require "#{app_path}/app/models/admin/book" + assert_equal 2, Admin::Book.count + end + end + + def db_structure_dump_and_load(expected_database) + Dir.chdir(app_path) do + `bin/rails generate model book title:string; + bin/rake db:migrate db:structure:dump` + structure_dump = File.read("db/structure.sql") + assert_match(/CREATE TABLE \"books\"/, structure_dump) + `bin/rake environment db:drop db:structure:load` + assert_match expected_database, ActiveRecord::Base.connection_config[:database] + require "#{app_path}/app/models/book" + #if structure is not loaded correctly, exception would be raised + assert_equal 0, Book.count + end + end + + test 'db:structure:dump and db:structure:load without database_url' do + require "#{app_path}/config/environment" + db_structure_dump_and_load ActiveRecord::Base.configurations[Rails.env]['database'] + end + + test 'db:structure:dump and db:structure:load with database_url' do + require "#{app_path}/config/environment" + set_database_url + db_structure_dump_and_load database_url_db_name + end + + test 'db:structure:dump does not dump schema information when no migrations are used' do + Dir.chdir(app_path) do + # create table without migrations + `bin/rails runner 'ActiveRecord::Base.connection.create_table(:posts) {|t| t.string :title }'` + + stderr_output = capture(:stderr) { `bin/rake db:structure:dump` } + assert_empty stderr_output + structure_dump = File.read("db/structure.sql") + assert_match(/CREATE TABLE \"posts\"/, structure_dump) + end + end + + test 'db:schema:load and db:structure:load do not purge the existing database' do + Dir.chdir(app_path) do + `bin/rails runner 'ActiveRecord::Base.connection.create_table(:posts) {|t| t.string :title }'` + + app_file 'db/schema.rb', <<-RUBY + ActiveRecord::Schema.define(version: 20140423102712) do + create_table(:comments) {} + end + RUBY + + list_tables = lambda { `bin/rails runner 'p ActiveRecord::Base.connection.tables'`.strip } + + assert_equal '["posts"]', list_tables[] + `bin/rake db:schema:load` + assert_equal '["posts", "comments", "schema_migrations"]', list_tables[] + + app_file 'db/structure.sql', <<-SQL + CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255)); + SQL + + `bin/rake db:structure:load` + assert_equal '["posts", "comments", "schema_migrations", "users"]', list_tables[] + end + end + + test "db:schema:load with inflections" do + Dir.chdir(app_path) do + app_file 'config/initializers/inflection.rb', <<-RUBY + ActiveSupport::Inflector.inflections do |inflect| + inflect.irregular 'goose', 'geese' + end + RUBY + app_file 'config/initializers/primary_key_table_name.rb', <<-RUBY + ActiveRecord::Base.primary_key_prefix_type = :table_name + RUBY + app_file 'db/schema.rb', <<-RUBY + ActiveRecord::Schema.define(version: 20140423102712) do + create_table("goose".pluralize) do |t| + t.string :name + end + end + RUBY + + `bin/rake db:schema:load` + + tables = `bin/rails runner 'p ActiveRecord::Base.connection.tables'`.strip + assert_match(/"geese"/, tables) + + columns = `bin/rails runner 'p ActiveRecord::Base.connection.columns("geese").map(&:name)'`.strip + assert_equal columns, '["gooseid", "name"]' + end + end + + def db_test_load_structure + Dir.chdir(app_path) do + `bin/rails generate model book title:string; + bin/rake db:migrate db:structure:dump db:test:load_structure` + ActiveRecord::Base.configurations = Rails.application.config.database_configuration + ActiveRecord::Base.establish_connection :test + require "#{app_path}/app/models/book" + #if structure is not loaded correctly, exception would be raised + assert_equal 0, Book.count + assert_match ActiveRecord::Base.configurations['test']['database'], + ActiveRecord::Base.connection_config[:database] + end + end + + test 'db:test:load_structure without database_url' do + require "#{app_path}/config/environment" + db_test_load_structure + end + + test 'db:setup loads schema and seeds database' do + begin + @old_rails_env = ENV["RAILS_ENV"] + @old_rack_env = ENV["RACK_ENV"] + ENV.delete "RAILS_ENV" + ENV.delete "RACK_ENV" + + app_file 'db/schema.rb', <<-RUBY + ActiveRecord::Schema.define(version: "1") do + create_table :users do |t| + t.string :name + end + end + RUBY + + app_file 'db/seeds.rb', <<-RUBY + puts ActiveRecord::Base.connection_config[:database] + RUBY + + Dir.chdir(app_path) do + database_path = `bin/rake db:setup` + assert_equal "development.sqlite3", File.basename(database_path.strip) + end + ensure + ENV["RAILS_ENV"] = @old_rails_env + ENV["RACK_ENV"] = @old_rack_env + end + end + end + end +end diff --git a/railties/test/application/rake/framework_test.rb b/railties/test/application/rake/framework_test.rb new file mode 100644 index 0000000000..ec57af79f6 --- /dev/null +++ b/railties/test/application/rake/framework_test.rb @@ -0,0 +1,48 @@ +require "isolation/abstract_unit" +require "active_support/core_ext/string/strip" + +module ApplicationTests + module RakeTests + class FrameworkTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + FileUtils.rm_rf("#{app_path}/config/environments") + end + + def teardown + teardown_app + end + + def load_tasks + require 'rake' + require 'rdoc/task' + require 'rake/testtask' + + Rails.application.load_tasks + end + + test 'requiring the rake task should not define method .app_generator on Object' do + require "#{app_path}/config/environment" + + load_tasks + + assert_raise NameError do + Object.method(:app_generator) + end + end + + test 'requiring the rake task should not define method .invoke_from_app_generator on Object' do + require "#{app_path}/config/environment" + + load_tasks + + assert_raise NameError do + Object.method(:invoke_from_app_generator) + end + end + end + end +end diff --git a/railties/test/application/rake/migrations_test.rb b/railties/test/application/rake/migrations_test.rb new file mode 100644 index 0000000000..6b74707959 --- /dev/null +++ b/railties/test/application/rake/migrations_test.rb @@ -0,0 +1,228 @@ +require "isolation/abstract_unit" + +module ApplicationTests + module RakeTests + class RakeMigrationsTest < ActiveSupport::TestCase + def setup + build_app + boot_rails + FileUtils.rm_rf("#{app_path}/config/environments") + end + + def teardown + teardown_app + end + + test 'running migrations with given scope' do + Dir.chdir(app_path) do + `bin/rails generate model user username:string password:string` + + app_file "db/migrate/01_a_migration.bukkits.rb", <<-MIGRATION + class AMigration < ActiveRecord::Migration + end + MIGRATION + + output = `bin/rake db:migrate SCOPE=bukkits` + assert_no_match(/create_table\(:users\)/, output) + assert_no_match(/CreateUsers/, output) + assert_no_match(/add_column\(:users, :email, :string\)/, output) + + assert_match(/AMigration: migrated/, output) + + output = `bin/rake db:migrate SCOPE=bukkits VERSION=0` + assert_no_match(/drop_table\(:users\)/, output) + assert_no_match(/CreateUsers/, output) + assert_no_match(/remove_column\(:users, :email\)/, output) + + assert_match(/AMigration: reverted/, output) + end + end + + test 'model and migration generator with change syntax' do + Dir.chdir(app_path) do + `bin/rails generate model user username:string password:string; + bin/rails generate migration add_email_to_users email:string` + + output = `bin/rake db:migrate` + assert_match(/create_table\(:users\)/, output) + assert_match(/CreateUsers: migrated/, output) + assert_match(/add_column\(:users, :email, :string\)/, output) + assert_match(/AddEmailToUsers: migrated/, output) + + output = `bin/rake db:rollback STEP=2` + assert_match(/drop_table\(:users\)/, output) + assert_match(/CreateUsers: reverted/, output) + assert_match(/remove_column\(:users, :email, :string\)/, output) + assert_match(/AddEmailToUsers: reverted/, output) + end + end + + test 'migration status when schema migrations table is not present' do + output = Dir.chdir(app_path){ `bin/rake db:migrate:status 2>&1` } + assert_equal "Schema migrations table does not exist yet.\n", output + end + + test 'test migration status' do + Dir.chdir(app_path) do + `bin/rails generate model user username:string password:string; + bin/rails generate migration add_email_to_users email:string; + bin/rake db:migrate` + + output = `bin/rake db:migrate:status` + + assert_match(/up\s+\d{14}\s+Create users/, output) + assert_match(/up\s+\d{14}\s+Add email to users/, output) + + `bin/rake db:rollback STEP=1` + output = `bin/rake db:migrate:status` + + assert_match(/up\s+\d{14}\s+Create users/, output) + assert_match(/down\s+\d{14}\s+Add email to users/, output) + end + end + + test 'migration status without timestamps' do + add_to_config('config.active_record.timestamped_migrations = false') + + Dir.chdir(app_path) do + `bin/rails generate model user username:string password:string; + bin/rails generate migration add_email_to_users email:string; + bin/rake db:migrate` + + output = `bin/rake db:migrate:status` + + assert_match(/up\s+\d{3,}\s+Create users/, output) + assert_match(/up\s+\d{3,}\s+Add email to users/, output) + + `bin/rake db:rollback STEP=1` + output = `bin/rake db:migrate:status` + + assert_match(/up\s+\d{3,}\s+Create users/, output) + assert_match(/down\s+\d{3,}\s+Add email to users/, output) + end + end + + test 'test migration status after rollback and redo' do + Dir.chdir(app_path) do + `bin/rails generate model user username:string password:string; + bin/rails generate migration add_email_to_users email:string; + bin/rake db:migrate` + + output = `bin/rake db:migrate:status` + + assert_match(/up\s+\d{14}\s+Create users/, output) + assert_match(/up\s+\d{14}\s+Add email to users/, output) + + `bin/rake db:rollback STEP=2` + output = `bin/rake db:migrate:status` + + assert_match(/down\s+\d{14}\s+Create users/, output) + assert_match(/down\s+\d{14}\s+Add email to users/, output) + + `bin/rake db:migrate:redo` + output = `bin/rake db:migrate:status` + + assert_match(/up\s+\d{14}\s+Create users/, output) + assert_match(/up\s+\d{14}\s+Add email to users/, output) + end + end + + test 'migration status after rollback and redo without timestamps' do + add_to_config('config.active_record.timestamped_migrations = false') + + Dir.chdir(app_path) do + `bin/rails generate model user username:string password:string; + bin/rails generate migration add_email_to_users email:string; + bin/rake db:migrate` + + output = `bin/rake db:migrate:status` + + assert_match(/up\s+\d{3,}\s+Create users/, output) + assert_match(/up\s+\d{3,}\s+Add email to users/, output) + + `bin/rake db:rollback STEP=2` + output = `bin/rake db:migrate:status` + + assert_match(/down\s+\d{3,}\s+Create users/, output) + assert_match(/down\s+\d{3,}\s+Add email to users/, output) + + `bin/rake db:migrate:redo` + output = `bin/rake db:migrate:status` + + assert_match(/up\s+\d{3,}\s+Create users/, output) + assert_match(/up\s+\d{3,}\s+Add email to users/, output) + end + end + + test 'running migrations with not timestamp head migration files' do + Dir.chdir(app_path) do + + app_file "db/migrate/1_one_migration.rb", <<-MIGRATION + class OneMigration < ActiveRecord::Migration + end + MIGRATION + + app_file "db/migrate/02_two_migration.rb", <<-MIGRATION + class TwoMigration < ActiveRecord::Migration + end + MIGRATION + + `bin/rake db:migrate` + + output = `bin/rake db:migrate:status` + + assert_match(/up\s+001\s+One migration/, output) + assert_match(/up\s+002\s+Two migration/, output) + end + end + + test 'schema generation when dump_schema_after_migration is set' do + add_to_config('config.active_record.dump_schema_after_migration = false') + + Dir.chdir(app_path) do + `bin/rails generate model book title:string` + output = `bin/rails generate model author name:string` + version = output =~ %r{[^/]+db/migrate/(\d+)_create_authors\.rb} && $1 + + `bin/rake db:migrate db:rollback db:forward db:migrate:up db:migrate:down VERSION=#{version}` + assert !File.exist?("db/schema.rb"), "should not dump schema when configured not to" + end + + add_to_config('config.active_record.dump_schema_after_migration = true') + + Dir.chdir(app_path) do + `bin/rails generate model reviews book_id:integer` + `bin/rake db:migrate` + + structure_dump = File.read("db/schema.rb") + assert_match(/create_table "reviews"/, structure_dump) + end + end + + test 'default schema generation after migration' do + Dir.chdir(app_path) do + `bin/rails generate model book title:string; + bin/rake db:migrate` + + structure_dump = File.read("db/schema.rb") + assert_match(/create_table "books"/, structure_dump) + end + end + + test 'test migration status migrated file is deleted' do + Dir.chdir(app_path) do + `bin/rails generate model user username:string password:string; + bin/rails generate migration add_email_to_users email:string; + bin/rake db:migrate + rm db/migrate/*email*.rb` + + output = `bin/rake db:migrate:status` + File.write('test.txt', output) + + assert_match(/up\s+\d{14}\s+Create users/, output) + assert_match(/up\s+\d{14}\s+\** NO FILE \**/, output) + end + end + end + end +end diff --git a/railties/test/application/rake/notes_test.rb b/railties/test/application/rake/notes_test.rb new file mode 100644 index 0000000000..c87515f00f --- /dev/null +++ b/railties/test/application/rake/notes_test.rb @@ -0,0 +1,157 @@ +require "isolation/abstract_unit" +require 'rails/source_annotation_extractor' + +module ApplicationTests + module RakeTests + class RakeNotesTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + require "rails/all" + super + end + + def teardown + super + teardown_app + end + + test 'notes finds notes for certain file_types' do + app_file "app/views/home/index.html.erb", "<% # TODO: note in erb %>" + app_file "app/assets/javascripts/application.js", "// TODO: note in js" + app_file "app/assets/stylesheets/application.css", "// TODO: note in css" + app_file "app/controllers/application_controller.rb", 1000.times.map { "" }.join("\n") << "# TODO: note in ruby" + app_file "lib/tasks/task.rake", "# TODO: note in rake" + app_file 'app/views/home/index.html.builder', '# TODO: note in builder' + app_file 'config/locales/en.yml', '# TODO: note in yml' + app_file 'config/locales/en.yaml', '# TODO: note in yaml' + app_file "app/views/home/index.ruby", "# TODO: note in ruby" + + run_rake_notes do |output, lines| + assert_match(/note in erb/, output) + assert_match(/note in js/, output) + assert_match(/note in css/, output) + assert_match(/note in rake/, output) + assert_match(/note in builder/, output) + assert_match(/note in yml/, output) + assert_match(/note in yaml/, output) + assert_match(/note in ruby/, output) + + assert_equal 9, lines.size + assert_equal [4], lines.map(&:size).uniq + end + end + + test 'notes finds notes in default directories' do + app_file "app/controllers/some_controller.rb", "# TODO: note in app directory" + app_file "config/initializers/some_initializer.rb", "# TODO: note in config directory" + app_file "db/some_seeds.rb", "# TODO: note in db directory" + app_file "lib/some_file.rb", "# TODO: note in lib directory" + app_file "test/some_test.rb", 1000.times.map { "" }.join("\n") << "# TODO: note in test directory" + + app_file "some_other_dir/blah.rb", "# TODO: note in some_other directory" + + run_rake_notes do |output, lines| + assert_match(/note in app directory/, output) + assert_match(/note in config directory/, output) + assert_match(/note in db directory/, output) + assert_match(/note in lib directory/, output) + assert_match(/note in test directory/, output) + assert_no_match(/note in some_other directory/, output) + + assert_equal 5, lines.size + assert_equal [4], lines.map(&:size).uniq + end + end + + test 'notes finds notes in custom directories' do + app_file "app/controllers/some_controller.rb", "# TODO: note in app directory" + app_file "config/initializers/some_initializer.rb", "# TODO: note in config directory" + app_file "db/some_seeds.rb", "# TODO: note in db directory" + app_file "lib/some_file.rb", "# TODO: note in lib directory" + app_file "test/some_test.rb", 1000.times.map { "" }.join("\n") << "# TODO: note in test directory" + + app_file "some_other_dir/blah.rb", "# TODO: note in some_other directory" + + run_rake_notes "SOURCE_ANNOTATION_DIRECTORIES='some_other_dir' bin/rake notes" do |output, lines| + assert_match(/note in app directory/, output) + assert_match(/note in config directory/, output) + assert_match(/note in db directory/, output) + assert_match(/note in lib directory/, output) + assert_match(/note in test directory/, output) + + assert_match(/note in some_other directory/, output) + + assert_equal 6, lines.size + assert_equal [4], lines.map(&:size).uniq + end + end + + test 'custom rake task finds specific notes in specific directories' do + app_file "app/controllers/some_controller.rb", "# TODO: note in app directory" + app_file "lib/some_file.rb", "# OPTIMIZE: note in lib directory\n" << "# FIXME: note in lib directory" + app_file "test/some_test.rb", 1000.times.map { "" }.join("\n") << "# TODO: note in test directory" + + app_file "lib/tasks/notes_custom.rake", <<-EOS + require 'rails/source_annotation_extractor' + task :notes_custom do + tags = 'TODO|FIXME' + opts = { dirs: %w(lib test), tag: true } + SourceAnnotationExtractor.enumerate(tags, opts) + end + EOS + + run_rake_notes "bin/rake notes_custom" do |output, lines| + assert_match(/\[FIXME\] note in lib directory/, output) + assert_match(/\[TODO\] note in test directory/, output) + assert_no_match(/OPTIMIZE/, output) + assert_no_match(/note in app directory/, output) + + assert_equal 2, lines.size + assert_equal [4], lines.map(&:size).uniq + end + end + + test 'register a new extension' do + add_to_config "config.assets.precompile = []" + add_to_config %q{ config.annotations.register_extensions("scss", "sass") { |annotation| /\/\/\s*(#{annotation}):?\s*(.*)$/ } } + app_file "app/assets/stylesheets/application.css.scss", "// TODO: note in scss" + app_file "app/assets/stylesheets/application.css.sass", "// TODO: note in sass" + + run_rake_notes do |output, lines| + assert_match(/note in scss/, output) + assert_match(/note in sass/, output) + assert_equal 2, lines.size + end + end + + private + + def run_rake_notes(command = 'bin/rake notes') + boot_rails + load_tasks + + Dir.chdir(app_path) do + output = `#{command}` + lines = output.scan(/\[([0-9\s]+)\]\s/).flatten + + yield output, lines + end + end + + def load_tasks + require 'rake' + require 'rdoc/task' + require 'rake/testtask' + + Rails.application.load_tasks + end + + def boot_rails + super + require "#{app_path}/config/environment" + end + end + end +end diff --git a/railties/test/application/rake/restart_test.rb b/railties/test/application/rake/restart_test.rb new file mode 100644 index 0000000000..4cae199e6b --- /dev/null +++ b/railties/test/application/rake/restart_test.rb @@ -0,0 +1,39 @@ +require "isolation/abstract_unit" + +module ApplicationTests + module RakeTests + class RakeRestartTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + end + + def teardown + teardown_app + end + + test 'rake restart touches tmp/restart.txt' do + Dir.chdir(app_path) do + `rake restart` + assert File.exist?("tmp/restart.txt") + + prev_mtime = File.mtime("tmp/restart.txt") + sleep(1) + `rake restart` + curr_mtime = File.mtime("tmp/restart.txt") + assert_not_equal prev_mtime, curr_mtime + end + end + + test 'rake restart should work even if tmp folder does not exist' do + Dir.chdir(app_path) do + FileUtils.remove_dir('tmp') + `rake restart` + assert File.exist?('tmp/restart.txt') + end + end + end + end +end diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb new file mode 100644 index 0000000000..0da0928b48 --- /dev/null +++ b/railties/test/application/rake_test.rb @@ -0,0 +1,314 @@ +require "isolation/abstract_unit" +require "active_support/core_ext/string/strip" + +module ApplicationTests + class RakeTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + end + + def teardown + teardown_app + end + + def test_gems_tasks_are_loaded_first_than_application_ones + app_file "lib/tasks/app.rake", <<-RUBY + $task_loaded = Rake::Task.task_defined?("db:create:all") + RUBY + + require "#{app_path}/config/environment" + ::Rails.application.load_tasks + assert $task_loaded + end + + def test_environment_is_required_in_rake_tasks + app_file "config/environment.rb", <<-RUBY + SuperMiddleware = Struct.new(:app) + + Rails.application.configure do + config.middleware.use SuperMiddleware + end + + Rails.application.initialize! + RUBY + + assert_match("SuperMiddleware", Dir.chdir(app_path){ `bin/rake middleware` }) + end + + def test_initializers_are_executed_in_rake_tasks + add_to_config <<-RUBY + initializer "do_something" do + puts "Doing something..." + end + + rake_tasks do + task do_nothing: :environment do + end + end + RUBY + + output = Dir.chdir(app_path){ `bin/rake do_nothing` } + assert_match "Doing something...", output + end + + def test_does_not_explode_when_accessing_a_model + add_to_config <<-RUBY + rake_tasks do + task do_nothing: :environment do + Hello.new.world + end + end + RUBY + + app_file 'app/models/hello.rb', <<-RUBY + class Hello + def world + puts 'Hello world' + end + end + RUBY + + output = Dir.chdir(app_path) { `bin/rake do_nothing` } + assert_match 'Hello world', output + end + + def test_should_not_eager_load_model_for_rake + add_to_config <<-RUBY + rake_tasks do + task do_nothing: :environment do + end + end + RUBY + + add_to_env_config 'production', <<-RUBY + config.eager_load = true + RUBY + + app_file 'app/models/hello.rb', <<-RUBY + raise 'should not be pre-required for rake even eager_load=true' + RUBY + + Dir.chdir(app_path) do + assert system('bin/rake do_nothing RAILS_ENV=production'), + 'should not be pre-required for rake even eager_load=true' + end + end + + def test_code_statistics_sanity + assert_match "Code LOC: 7 Test LOC: 0 Code to Test Ratio: 1:0.0", + Dir.chdir(app_path){ `bin/rake stats` } + end + + def test_rake_routes_calls_the_route_inspector + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get '/cart', to: 'cart#show' + end + RUBY + + output = Dir.chdir(app_path){ `bin/rake routes` } + assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output + end + + def test_rake_routes_with_controller_environment + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get '/cart', to: 'cart#show' + get '/basketball', to: 'basketball#index' + end + RUBY + + ENV['CONTROLLER'] = 'cart' + output = Dir.chdir(app_path){ `bin/rake routes` } + assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output + end + + def test_rake_routes_displays_message_when_no_routes_are_defined + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + end + RUBY + + assert_equal <<-MESSAGE.strip_heredoc, Dir.chdir(app_path){ `bin/rake routes` } + You don't have any routes defined! + + Please add some routes in config/routes.rb. + + For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html. + MESSAGE + end + + def test_logger_is_flushed_when_exiting_production_rake_tasks + add_to_config <<-RUBY + rake_tasks do + task log_something: :environment do + Rails.logger.error("Sample log message") + end + end + RUBY + + output = Dir.chdir(app_path){ `bin/rake log_something RAILS_ENV=production && cat log/production.log` } + assert_match "Sample log message", output + end + + def test_loading_specific_fixtures + Dir.chdir(app_path) do + `bin/rails generate model user username:string password:string; + bin/rails generate model product name:string; + bin/rake db:migrate` + end + + require "#{rails_root}/config/environment" + + # loading a specific fixture + errormsg = Dir.chdir(app_path) { `bin/rake db:fixtures:load FIXTURES=products` } + assert $?.success?, errormsg + + assert_equal 2, ::AppTemplate::Application::Product.count + assert_equal 0, ::AppTemplate::Application::User.count + end + + def test_loading_only_yml_fixtures + Dir.chdir(app_path) do + `bin/rake db:migrate` + end + + app_file "test/fixtures/products.csv", "" + + require "#{rails_root}/config/environment" + errormsg = Dir.chdir(app_path) { `bin/rake db:fixtures:load` } + assert $?.success?, errormsg + end + + def test_scaffold_tests_pass_by_default + output = Dir.chdir(app_path) do + `bin/rails generate scaffold user username:string password:string; + bin/rake db:migrate test` + end + + assert_match(/7 runs, 12 assertions, 0 failures, 0 errors/, output) + 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 + `bin/rails generate scaffold user username:string password:string; + bin/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" + + output = Dir.chdir(app_path) do + `bin/rails generate scaffold LineItems product:references cart:belongs_to; + bin/rake db:migrate test` + end + + assert_match(/7 runs, 12 assertions, 0 failures, 0 errors/, output) + assert_no_match(/Errors running/, output) + end + + def test_db_test_clone_when_using_sql_format + add_to_config "config.active_record.schema_format = :sql" + output = Dir.chdir(app_path) do + `bin/rails generate scaffold user username:string; + bin/rake db:migrate; + bin/rake db:test:clone 2>&1 --trace` + end + assert_match(/Execute db:test:clone_structure/, output) + end + + def test_db_test_prepare_when_using_sql_format + add_to_config "config.active_record.schema_format = :sql" + output = Dir.chdir(app_path) do + `bin/rails generate scaffold user username:string; + bin/rake db:migrate; + bin/rake db:test:prepare 2>&1 --trace` + end + assert_match(/Execute db:test:load_structure/, output) + end + + def test_rake_dump_structure_should_respect_db_structure_env_variable + Dir.chdir(app_path) do + # ensure we have a schema_migrations table to dump + `bin/rake db:migrate db:structure:dump SCHEMA=db/my_structure.sql` + end + assert File.exist?(File.join(app_path, 'db', 'my_structure.sql')) + end + + def test_rake_dump_structure_should_be_called_twice_when_migrate_redo + add_to_config "config.active_record.schema_format = :sql" + + output = Dir.chdir(app_path) do + `bin/rails g model post title:string; + bin/rake db:migrate:redo 2>&1 --trace;` + end + + # expect only Invoke db:structure:dump (first_time) + assert_no_match(/^\*\* Invoke db:structure:dump\s+$/, output) + end + + def test_rake_dump_schema_cache + Dir.chdir(app_path) do + `bin/rails generate model post title:string; + bin/rails generate model product name:string; + bin/rake db:migrate db:schema:cache:dump` + end + assert File.exist?(File.join(app_path, 'db', 'schema_cache.dump')) + end + + def test_rake_clear_schema_cache + Dir.chdir(app_path) do + `bin/rake db:schema:cache:dump db:schema:cache:clear` + end + assert !File.exist?(File.join(app_path, 'db', 'schema_cache.dump')) + end + + def test_copy_templates + Dir.chdir(app_path) do + `bin/rake rails:templates:copy` + %w(controller mailer scaffold).each do |dir| + assert File.exist?(File.join(app_path, 'lib', 'templates', 'erb', dir)) + end + %w(controller helper scaffold_controller assets).each do |dir| + assert File.exist?(File.join(app_path, 'lib', 'templates', 'rails', dir)) + end + end + end + + def test_template_load_initializers + app_file "config/initializers/dummy.rb", "puts 'Hello, World!'" + app_file "template.rb", "" + + output = Dir.chdir(app_path) do + `bin/rake rails:template LOCATION=template.rb` + end + + assert_match(/Hello, World!/, output) + end + + def test_tmp_clear_should_work_if_folder_missing + FileUtils.remove_dir("#{app_path}/tmp") + errormsg = Dir.chdir(app_path) { `bin/rake tmp:clear` } + assert_predicate $?, :success? + assert_empty errormsg + end + end +end diff --git a/railties/test/application/rendering_test.rb b/railties/test/application/rendering_test.rb new file mode 100644 index 0000000000..b01febd768 --- /dev/null +++ b/railties/test/application/rendering_test.rb @@ -0,0 +1,45 @@ +require 'isolation/abstract_unit' +require 'rack/test' + +module ApplicationTests + class RoutingTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + include Rack::Test::Methods + + def setup + build_app + boot_rails + end + + def teardown + teardown_app + end + + test "Unknown format falls back to HTML template" do + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get 'pages/:id', to: 'pages#show' + end + RUBY + + app_file 'app/controllers/pages_controller.rb', <<-RUBY + class PagesController < ApplicationController + layout false + + def show + end + end + RUBY + + app_file 'app/views/pages/show.html.erb', <<-RUBY + <%= params[:id] %> + RUBY + + get '/pages/foo' + assert_equal 200, last_response.status + + get '/pages/foo.bar' + assert_equal 200, last_response.status + end + end +end diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb new file mode 100644 index 0000000000..0777714d35 --- /dev/null +++ b/railties/test/application/routing_test.rb @@ -0,0 +1,480 @@ +require 'isolation/abstract_unit' +require 'rack/test' + +module ApplicationTests + class RoutingTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + include Rack::Test::Methods + + def setup + build_app + boot_rails + end + + def teardown + teardown_app + end + + test "rails/welcome in development" do + app("development") + get "/" + assert_equal 200, last_response.status + end + + test "rails/info in development" do + app("development") + get "/rails/info" + assert_equal 302, last_response.status + end + + test "rails/info/routes in development" do + app("development") + get "/rails/info/routes" + assert_equal 200, last_response.status + end + + test "rails/info/properties in development" do + app("development") + get "/rails/info/properties" + assert_equal 200, last_response.status + end + + test "root takes precedence over internal welcome controller" do + app("development") + + get '/' + assert_match %r{<h1>Getting started</h1>} , last_response.body + + controller :foo, <<-RUBY + class FooController < ApplicationController + def index + render text: "foo" + end + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + root to: "foo#index" + end + RUBY + + get '/' + assert_equal 'foo', last_response.body + end + + test "rails/welcome in production" do + app("production") + get "/" + assert_equal 404, last_response.status + end + + test "rails/info in production" do + app("production") + get "/rails/info" + assert_equal 404, last_response.status + end + + test "rails/info/routes in production" do + app("production") + get "/rails/info/routes" + assert_equal 404, last_response.status + end + + test "rails/info/properties in production" do + app("production") + get "/rails/info/properties" + assert_equal 404, last_response.status + end + + test "simple controller" do + simple_controller + + get '/foo' + assert_equal 'foo', last_response.body + end + + test "simple controller with helper" do + controller :foo, <<-RUBY + class FooController < ApplicationController + def index + render inline: "<%= foo_or_bar? %>" + end + end + RUBY + + app_file 'app/helpers/bar_helper.rb', <<-RUBY + module BarHelper + def foo_or_bar? + "bar" + end + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get ':controller(/:action)' + end + RUBY + + get '/foo' + assert_equal 'bar', last_response.body + end + + test "mount rack app" do + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + mount lambda { |env| [200, {}, [env["PATH_INFO"]]] }, at: "/blog" + # The line below is required because mount sometimes + # fails when a resource route is added. + resource :user + end + RUBY + + get '/blog/archives' + assert_equal '/archives', last_response.body + end + + test "mount named rack app" do + controller :foo, <<-RUBY + class FooController < ApplicationController + def index + render text: my_blog_path + end + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + mount lambda { |env| [200, {}, [env["PATH_INFO"]]] }, at: "/blog", as: "my_blog" + get '/foo' => 'foo#index' + end + RUBY + + get '/foo' + assert_equal '/blog', last_response.body + end + + test "multiple controllers" do + controller :foo, <<-RUBY + class FooController < ApplicationController + def index + render text: "foo" + end + end + RUBY + + controller :bar, <<-RUBY + class BarController < ActionController::Base + def index + render text: "bar" + end + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get ':controller(/:action)' + end + RUBY + + get '/foo' + assert_equal 'foo', last_response.body + + get '/bar' + assert_equal 'bar', last_response.body + end + + test "nested controller" do + controller 'foo', <<-RUBY + class FooController < ApplicationController + def index + render text: "foo" + end + end + RUBY + + controller 'admin/foo', <<-RUBY + module Admin + class FooController < ApplicationController + def index + render text: "admin::foo" + end + end + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get 'admin/foo', to: 'admin/foo#index' + get 'foo', to: 'foo#index' + end + RUBY + + get '/foo' + assert_equal 'foo', last_response.body + + get '/admin/foo' + assert_equal 'admin::foo', last_response.body + end + + test "routes appending blocks" do + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get ':controller/:action' + end + RUBY + + add_to_config <<-R + routes.append do + get '/win' => lambda { |e| [200, {'Content-Type'=>'text/plain'}, ['WIN']] } + end + R + + app 'development' + + get '/win' + assert_equal 'WIN', last_response.body + + app_file 'config/routes.rb', <<-R + Rails.application.routes.draw do + get 'lol' => 'hello#index' + end + R + + get '/win' + assert_equal 'WIN', last_response.body + end + + {"development" => "baz", "production" => "bar"}.each do |mode, expected| + test "reloads routes when configuration is changed in #{mode}" do + controller :foo, <<-RUBY + class FooController < ApplicationController + def bar + render text: "bar" + end + + def baz + render text: "baz" + end + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get 'foo', to: 'foo#bar' + end + RUBY + + app(mode) + + get '/foo' + assert_equal 'bar', last_response.body + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get 'foo', to: 'foo#baz' + end + RUBY + + sleep 0.1 + + get '/foo' + assert_equal expected, last_response.body + end + end + + test 'routes are loaded just after initialization' do + require "#{app_path}/config/application" + + # Create the rack app just inside after initialize callback + ActiveSupport.on_load(:after_initialize) do + ::InitializeRackApp = lambda { |env| [200, {}, ["InitializeRackApp"]] } + end + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get 'foo', to: ::InitializeRackApp + end + RUBY + + get '/foo' + assert_equal "InitializeRackApp", last_response.body + end + + test 'reload_routes! is part of Rails.application API' do + app("development") + assert_nothing_raised do + Rails.application.reload_routes! + end + end + + def test_root_path + app('development') + + controller :foo, <<-RUBY + class FooController < ApplicationController + def index + render :text => "foo" + end + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get 'foo', :to => 'foo#index' + root :to => 'foo#index' + end + RUBY + + remove_file 'public/index.html' + + get '/' + assert_equal 'foo', last_response.body + end + + test 'routes are added and removed when reloading' do + app('development') + + controller :foo, <<-RUBY + class FooController < ApplicationController + def index + render text: "foo" + end + end + RUBY + + controller :bar, <<-RUBY + class BarController < ApplicationController + def index + render text: "bar" + end + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get 'foo', to: 'foo#index' + end + RUBY + + get '/foo' + assert_equal 'foo', last_response.body + assert_equal '/foo', Rails.application.routes.url_helpers.foo_path + + get '/bar' + assert_equal 404, last_response.status + assert_raises NoMethodError do + assert_equal '/bar', Rails.application.routes.url_helpers.bar_path + end + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get 'foo', to: 'foo#index' + get 'bar', to: 'bar#index' + end + RUBY + + Rails.application.reload_routes! + + get '/foo' + assert_equal 'foo', last_response.body + assert_equal '/foo', Rails.application.routes.url_helpers.foo_path + + get '/bar' + assert_equal 'bar', last_response.body + assert_equal '/bar', Rails.application.routes.url_helpers.bar_path + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get 'foo', to: 'foo#index' + end + RUBY + + Rails.application.reload_routes! + + get '/foo' + assert_equal 'foo', last_response.body + assert_equal '/foo', Rails.application.routes.url_helpers.foo_path + + get '/bar' + assert_equal 404, last_response.status + assert_raises NoMethodError do + assert_equal '/bar', Rails.application.routes.url_helpers.bar_path + end + end + + test 'named routes are cleared when reloading' do + app('development') + + controller :foo, <<-RUBY + class FooController < ApplicationController + def index + render text: "foo" + end + end + RUBY + + controller :bar, <<-RUBY + class BarController < ApplicationController + def index + render text: "bar" + end + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get ':locale/foo', to: 'foo#index', as: 'foo' + end + RUBY + + get '/en/foo' + assert_equal 'foo', last_response.body + assert_equal '/en/foo', Rails.application.routes.url_helpers.foo_path(:locale => 'en') + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get ':locale/bar', to: 'bar#index', as: 'foo' + end + RUBY + + Rails.application.reload_routes! + + get '/en/foo' + assert_equal 404, last_response.status + + get '/en/bar' + assert_equal 'bar', last_response.body + assert_equal '/en/bar', Rails.application.routes.url_helpers.foo_path(:locale => 'en') + end + + test 'resource routing with irregular inflection' do + app_file 'config/initializers/inflection.rb', <<-RUBY + ActiveSupport::Inflector.inflections do |inflect| + inflect.irregular 'yazi', 'yazilar' + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + resources :yazilar + end + RUBY + + controller 'yazilar', <<-RUBY + class YazilarController < ApplicationController + def index + render text: 'yazilar#index' + end + end + RUBY + + get '/yazilars' + assert_equal 404, last_response.status + + get '/yazilar' + assert_equal 200, last_response.status + end + end +end diff --git a/railties/test/application/runner_test.rb b/railties/test/application/runner_test.rb new file mode 100644 index 0000000000..0c180339b4 --- /dev/null +++ b/railties/test/application/runner_test.rb @@ -0,0 +1,89 @@ +require 'isolation/abstract_unit' +require 'env_helpers' + +module ApplicationTests + class RunnerTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + include EnvHelpers + + def setup + build_app + boot_rails + + # Lets create a model so we have something to play with + app_file "app/models/user.rb", <<-MODEL + class User + def self.count + 42 + end + end + MODEL + end + + def teardown + teardown_app + end + + def test_should_include_runner_in_shebang_line_in_help_without_option + assert_match "/rails runner", Dir.chdir(app_path) { `bin/rails runner` } + end + + def test_should_include_runner_in_shebang_line_in_help + assert_match "/rails runner", Dir.chdir(app_path) { `bin/rails runner --help` } + end + + def test_should_run_ruby_statement + assert_match "42", Dir.chdir(app_path) { `bin/rails runner "puts User.count"` } + end + + def test_should_run_file + app_file "bin/count_users.rb", <<-SCRIPT + puts User.count + SCRIPT + + assert_match "42", Dir.chdir(app_path) { `bin/rails runner "bin/count_users.rb"` } + end + + def test_should_set_dollar_0_to_file + app_file "bin/dollar0.rb", <<-SCRIPT + puts $0 + SCRIPT + + assert_match "bin/dollar0.rb", Dir.chdir(app_path) { `bin/rails runner "bin/dollar0.rb"` } + end + + def test_should_set_dollar_program_name_to_file + app_file "bin/program_name.rb", <<-SCRIPT + puts $PROGRAM_NAME + SCRIPT + + assert_match "bin/program_name.rb", Dir.chdir(app_path) { `bin/rails runner "bin/program_name.rb"` } + end + + def test_with_hook + add_to_config <<-RUBY + runner do |app| + app.config.ran = true + end + RUBY + + assert_match "true", Dir.chdir(app_path) { `bin/rails runner "puts Rails.application.config.ran"` } + end + + def test_default_environment + assert_match "development", Dir.chdir(app_path) { `bin/rails runner "puts Rails.env"` } + end + + def test_environment_with_rails_env + with_rails_env "production" do + assert_match "production", Dir.chdir(app_path) { `bin/rails runner "puts Rails.env"` } + end + end + + def test_environment_with_rack_env + with_rack_env "production" do + assert_match "production", Dir.chdir(app_path) { `bin/rails runner "puts Rails.env"` } + end + end + end +end diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb new file mode 100644 index 0000000000..4965ab7da0 --- /dev/null +++ b/railties/test/application/test_runner_test.rb @@ -0,0 +1,455 @@ +require 'isolation/abstract_unit' +require 'active_support/core_ext/string/strip' +require 'env_helpers' + +module ApplicationTests + class TestRunnerTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation, EnvHelpers + + def setup + build_app + create_schema + end + + def teardown + teardown_app + end + + def test_run_single_file + create_test_file :models, 'foo' + create_test_file :models, 'bar' + assert_match "1 runs, 1 assertions, 0 failures", run_test_command("test/models/foo_test.rb") + end + + def test_run_multiple_files + create_test_file :models, 'foo' + create_test_file :models, 'bar' + assert_match "2 runs, 2 assertions, 0 failures", run_test_command("test/models/foo_test.rb test/models/bar_test.rb") + end + + def test_run_file_with_syntax_error + app_file 'test/models/error_test.rb', <<-RUBY + require 'test_helper' + def; end + RUBY + + error = capture(:stderr) { run_test_command('test/models/error_test.rb') } + assert_match "syntax error", error + end + + def test_run_models + create_test_file :models, 'foo' + create_test_file :models, 'bar' + create_test_file :controllers, 'foobar_controller' + run_test_command("test/models").tap do |output| + assert_match "FooTest", output + assert_match "BarTest", output + assert_match "2 runs, 2 assertions, 0 failures", output + end + end + + def test_run_helpers + create_test_file :helpers, 'foo_helper' + create_test_file :helpers, 'bar_helper' + create_test_file :controllers, 'foobar_controller' + run_test_command("test/helpers").tap do |output| + assert_match "FooHelperTest", output + assert_match "BarHelperTest", output + assert_match "2 runs, 2 assertions, 0 failures", output + end + end + + def test_run_units + skip "we no longer have the concept of unit tests. Just different directories..." + create_test_file :models, 'foo' + create_test_file :helpers, 'bar_helper' + create_test_file :unit, 'baz_unit' + create_test_file :controllers, 'foobar_controller' + run_test_units_command.tap do |output| + assert_match "FooTest", output + assert_match "BarHelperTest", output + assert_match "BazUnitTest", output + assert_match "3 runs, 3 assertions, 0 failures", output + end + end + + def test_run_controllers + create_test_file :controllers, 'foo_controller' + create_test_file :controllers, 'bar_controller' + create_test_file :models, 'foo' + run_test_command("test/controllers").tap do |output| + assert_match "FooControllerTest", output + assert_match "BarControllerTest", output + assert_match "2 runs, 2 assertions, 0 failures", output + end + end + + def test_run_mailers + create_test_file :mailers, 'foo_mailer' + create_test_file :mailers, 'bar_mailer' + create_test_file :models, 'foo' + run_test_command("test/mailers").tap do |output| + assert_match "FooMailerTest", output + assert_match "BarMailerTest", output + assert_match "2 runs, 2 assertions, 0 failures", output + end + end + + def test_run_jobs + create_test_file :jobs, 'foo_job' + create_test_file :jobs, 'bar_job' + create_test_file :models, 'foo' + run_test_command("test/jobs").tap do |output| + assert_match "FooJobTest", output + assert_match "BarJobTest", output + assert_match "2 runs, 2 assertions, 0 failures", output + end + end + + def test_run_functionals + skip "we no longer have the concept of functional tests. Just different directories..." + create_test_file :mailers, 'foo_mailer' + create_test_file :controllers, 'bar_controller' + create_test_file :functional, 'baz_functional' + create_test_file :models, 'foo' + run_test_functionals_command.tap do |output| + assert_match "FooMailerTest", output + assert_match "BarControllerTest", output + assert_match "BazFunctionalTest", output + assert_match "3 runs, 3 assertions, 0 failures", output + end + end + + def test_run_integration + create_test_file :integration, 'foo_integration' + create_test_file :models, 'foo' + run_test_command("test/integration").tap do |output| + assert_match "FooIntegration", output + assert_match "1 runs, 1 assertions, 0 failures", output + end + end + + def test_run_all_suites + suites = [:models, :helpers, :unit, :controllers, :mailers, :functional, :integration, :jobs] + suites.each { |suite| create_test_file suite, "foo_#{suite}" } + run_test_command('') .tap do |output| + suites.each { |suite| assert_match "Foo#{suite.to_s.camelize}Test", output } + assert_match "8 runs, 8 assertions, 0 failures", output + end + end + + def test_run_named_test + app_file 'test/unit/chu_2_koi_test.rb', <<-RUBY + require 'test_helper' + + class Chu2KoiTest < ActiveSupport::TestCase + def test_rikka + puts 'Rikka' + end + + def test_sanae + puts 'Sanae' + end + end + RUBY + + run_test_command('-n test_rikka test/unit/chu_2_koi_test.rb').tap do |output| + assert_match "Rikka", output + assert_no_match "Sanae", output + end + end + + def test_run_matched_test + app_file 'test/unit/chu_2_koi_test.rb', <<-RUBY + require 'test_helper' + + class Chu2KoiTest < ActiveSupport::TestCase + def test_rikka + puts 'Rikka' + end + + def test_sanae + puts 'Sanae' + end + end + RUBY + + run_test_command('-n /rikka/ test/unit/chu_2_koi_test.rb').tap do |output| + assert_match "Rikka", output + assert_no_match "Sanae", output + end + end + + def test_load_fixtures_when_running_test_suites + create_model_with_fixture + suites = [:models, :helpers, :controllers, :mailers, :integration] + + suites.each do |suite, directory| + directory ||= suite + create_fixture_test directory + assert_match "3 users", run_test_command("test/#{suite}") + Dir.chdir(app_path) { FileUtils.rm_f "test/#{directory}" } + end + end + + def test_run_with_model + skip "These feel a bit odd. Not sure we should keep supporting them." + create_model_with_fixture + create_fixture_test 'models', 'user' + assert_match "3 users", run_task(["test models/user"]) + assert_match "3 users", run_task(["test app/models/user.rb"]) + end + + def test_run_different_environment_using_env_var + skip "no longer possible. Running tests in a different environment should be explicit" + app_file 'test/unit/env_test.rb', <<-RUBY + require 'test_helper' + + class EnvTest < ActiveSupport::TestCase + def test_env + puts Rails.env + end + end + RUBY + + ENV['RAILS_ENV'] = 'development' + assert_match "development", run_test_command('test/unit/env_test.rb') + end + + def test_run_in_test_environment_by_default + create_env_test + + assert_match "Current Environment: test", run_test_command('test/unit/env_test.rb') + end + + def test_run_different_environment + create_env_test + + assert_match "Current Environment: development", + run_test_command("-e development test/unit/env_test.rb") + end + + def test_generated_scaffold_works_with_rails_test + create_scaffold + assert_match "0 failures, 0 errors, 0 skips", run_test_command('') + end + + def test_run_multiple_folders + create_test_file :models, 'account' + create_test_file :controllers, 'accounts_controller' + + run_test_command('test/models test/controllers').tap do |output| + assert_match 'AccountTest', output + assert_match 'AccountsControllerTest', output + assert_match '2 runs, 2 assertions, 0 failures, 0 errors, 0 skips', output + end + end + + def test_run_with_ruby_command + app_file 'test/models/post_test.rb', <<-RUBY + require 'test_helper' + + class PostTest < ActiveSupport::TestCase + test 'declarative syntax works' do + puts 'PostTest' + assert true + end + end + RUBY + + Dir.chdir(app_path) do + `ruby -Itest test/models/post_test.rb`.tap do |output| + assert_match 'PostTest', output + assert_no_match 'is already defined in', output + end + end + end + + def test_mix_files_and_line_filters + create_test_file :models, 'account' + app_file 'test/models/post_test.rb', <<-RUBY + require 'test_helper' + + class PostTest < ActiveSupport::TestCase + def test_post + puts 'PostTest' + assert true + end + + def test_line_filter_does_not_run_this + assert true + end + end + RUBY + + run_test_command('test/models/account_test.rb test/models/post_test.rb:4').tap do |output| + assert_match 'AccountTest', output + assert_match 'PostTest', output + assert_match '2 runs, 2 assertions', output + end + end + + def test_multiple_line_filters + create_test_file :models, 'account' + create_test_file :models, 'post' + + run_test_command('test/models/account_test.rb:4 test/models/post_test.rb:4').tap do |output| + assert_match 'AccountTest', output + assert_match 'PostTest', output + end + end + + def test_line_filter_without_line_runs_all_tests + create_test_file :models, 'account' + + run_test_command('test/models/account_test.rb:').tap do |output| + assert_match 'AccountTest', output + end + end + + def test_shows_filtered_backtrace_by_default + create_backtrace_test + + assert_match 'Rails::BacktraceCleaner', run_test_command('test/unit/backtrace_test.rb') + end + + def test_backtrace_option + create_backtrace_test + + assert_match 'Minitest::BacktraceFilter', run_test_command('test/unit/backtrace_test.rb -b') + assert_match 'Minitest::BacktraceFilter', + run_test_command('test/unit/backtrace_test.rb --backtrace') + end + + def test_show_full_backtrace_using_backtrace_environment_variable + create_backtrace_test + + switch_env 'BACKTRACE', 'true' do + assert_match 'Minitest::BacktraceFilter', run_test_command('test/unit/backtrace_test.rb') + end + end + + def test_run_app_without_rails_loaded + # Simulate a real Rails app boot. + app_file 'config/boot.rb', <<-RUBY + ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) + + require 'bundler/setup' # Set up gems listed in the Gemfile. + RUBY + + assert_match '0 runs, 0 assertions', run_test_command('') + end + + def test_output_inline_by_default + create_test_file :models, 'post', pass: false + + output = run_test_command('test/models/post_test.rb') + assert_match %r{Running:\n\nPostTest\nF\n\nwups!\n\nbin/rails test test/models/post_test.rb:6}, output + end + + def test_only_inline_failure_output + create_test_file :models, 'post', pass: false + + output = run_test_command('test/models/post_test.rb') + assert_match %r{Finished in.*\n\n1 runs, 1 assertions}, output + end + + def test_fail_fast + create_test_file :models, 'post', pass: false + + assert_match(/Interrupt/, + capture(:stderr) { run_test_command('test/models/post_test.rb --fail-fast') }) + end + + def test_raise_error_when_specified_file_does_not_exist + error = capture(:stderr) { run_test_command('test/not_exists.rb') } + assert_match(%r{cannot load such file.+test/not_exists\.rb}, error) + end + + private + def run_test_command(arguments = 'test/unit/test_test.rb') + Dir.chdir(app_path) { `bin/rails t #{arguments}` } + end + + def create_model_with_fixture + script 'generate model user name:string' + + app_file 'test/fixtures/users.yml', <<-YAML.strip_heredoc + vampire: + id: 1 + name: Koyomi Araragi + crab: + id: 2 + name: Senjougahara Hitagi + cat: + id: 3 + name: Tsubasa Hanekawa + YAML + + run_migration + end + + def create_fixture_test(path = :unit, name = 'test') + app_file "test/#{path}/#{name}_test.rb", <<-RUBY + require 'test_helper' + + class #{name.camelize}Test < ActiveSupport::TestCase + def test_fixture + puts "\#{User.count} users (\#{__FILE__})" + end + end + RUBY + end + + def create_backtrace_test + app_file 'test/unit/backtrace_test.rb', <<-RUBY + require 'test_helper' + + class BacktraceTest < ActiveSupport::TestCase + def test_backtrace + puts Minitest.backtrace_filter + end + end + RUBY + end + + def create_schema + app_file 'db/schema.rb', '' + end + + def create_test_file(path = :unit, name = 'test', pass: true) + app_file "test/#{path}/#{name}_test.rb", <<-RUBY + require 'test_helper' + + class #{name.camelize}Test < ActiveSupport::TestCase + def test_truth + puts "#{name.camelize}Test" + assert #{pass}, 'wups!' + end + end + RUBY + end + + def create_env_test + app_file 'test/unit/env_test.rb', <<-RUBY + require 'test_helper' + + class EnvTest < ActiveSupport::TestCase + def test_env + puts "Current Environment: \#{Rails.env}" + end + end + RUBY + end + + def create_scaffold + script 'generate scaffold user name:string' + Dir.chdir(app_path) { File.exist?('app/models/user.rb') } + run_migration + end + + def run_migration + Dir.chdir(app_path) { `bin/rake db:migrate` } + end + end +end diff --git a/railties/test/application/test_test.rb b/railties/test/application/test_test.rb new file mode 100644 index 0000000000..0e997f4ba7 --- /dev/null +++ b/railties/test/application/test_test.rb @@ -0,0 +1,307 @@ +require 'isolation/abstract_unit' + +module ApplicationTests + class TestTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + end + + def teardown + teardown_app + end + + test "truth" do + app_file 'test/unit/foo_test.rb', <<-RUBY + require 'test_helper' + + class FooTest < ActiveSupport::TestCase + def test_truth + assert true + end + end + RUBY + + assert_successful_test_run 'unit/foo_test.rb' + end + + test "integration test" do + controller 'posts', <<-RUBY + class PostsController < ActionController::Base + end + RUBY + + app_file 'app/views/posts/index.html.erb', <<-HTML + Posts#index + HTML + + app_file 'test/integration/posts_test.rb', <<-RUBY + require 'test_helper' + + class PostsTest < ActionDispatch::IntegrationTest + def test_index + get '/posts' + assert_response :success + assert_includes @response.body, 'Posts#index' + end + end + RUBY + + assert_successful_test_run 'integration/posts_test.rb' + end + + test "enable full backtraces on test failures" do + app_file 'test/unit/failing_test.rb', <<-RUBY + require 'test_helper' + + class FailingTest < ActiveSupport::TestCase + def test_failure + raise "fail" + end + end + RUBY + + output = run_test_file('unit/failing_test.rb', env: { "BACKTRACE" => "1" }) + assert_match %r{test/unit/failing_test\.rb}, output + assert_match %r{test/unit/failing_test\.rb:4}, output + end + + test "ruby schema migrations" do + output = script('generate model user name:string') + version = output.match(/(\d+)_create_users\.rb/)[1] + + app_file 'test/models/user_test.rb', <<-RUBY + require 'test_helper' + + class UserTest < ActiveSupport::TestCase + test "user" do + User.create! name: "Jon" + end + end + RUBY + app_file 'db/schema.rb', '' + + assert_unsuccessful_run "models/user_test.rb", "Migrations are pending" + + app_file 'db/schema.rb', <<-RUBY + ActiveRecord::Schema.define(version: #{version}) do + create_table :users do |t| + t.string :name + end + end + RUBY + + app_file 'config/initializers/disable_maintain_test_schema.rb', <<-RUBY + Rails.application.config.active_record.maintain_test_schema = false + RUBY + + assert_unsuccessful_run "models/user_test.rb", "Could not find table 'users'" + + File.delete "#{app_path}/config/initializers/disable_maintain_test_schema.rb" + + result = assert_successful_test_run('models/user_test.rb') + assert !result.include?("create_table(:users)") + end + + test "sql structure migrations" do + output = script('generate model user name:string') + version = output.match(/(\d+)_create_users\.rb/)[1] + + app_file 'test/models/user_test.rb', <<-RUBY + require 'test_helper' + + class UserTest < ActiveSupport::TestCase + test "user" do + User.create! name: "Jon" + end + end + RUBY + + app_file 'db/structure.sql', '' + app_file 'config/initializers/enable_sql_schema_format.rb', <<-RUBY + Rails.application.config.active_record.schema_format = :sql + RUBY + + assert_unsuccessful_run "models/user_test.rb", "Migrations are pending" + + app_file 'db/structure.sql', <<-SQL + CREATE TABLE "schema_migrations" ("version" varchar(255) NOT NULL); + CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version"); + CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255)); + INSERT INTO schema_migrations (version) VALUES ('#{version}'); + SQL + + app_file 'config/initializers/disable_maintain_test_schema.rb', <<-RUBY + Rails.application.config.active_record.maintain_test_schema = false + RUBY + + assert_unsuccessful_run "models/user_test.rb", "Could not find table 'users'" + + File.delete "#{app_path}/config/initializers/disable_maintain_test_schema.rb" + + assert_successful_test_run('models/user_test.rb') + end + + test "sql structure migrations when adding column to existing table" do + output_1 = script('generate model user name:string') + version_1 = output_1.match(/(\d+)_create_users\.rb/)[1] + + app_file 'test/models/user_test.rb', <<-RUBY + require 'test_helper' + class UserTest < ActiveSupport::TestCase + test "user" do + User.create! name: "Jon" + end + end + RUBY + + app_file 'config/initializers/enable_sql_schema_format.rb', <<-RUBY + Rails.application.config.active_record.schema_format = :sql + RUBY + + app_file 'db/structure.sql', <<-SQL + CREATE TABLE "schema_migrations" ("version" varchar(255) NOT NULL); + CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version"); + CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255)); + INSERT INTO schema_migrations (version) VALUES ('#{version_1}'); + SQL + + assert_successful_test_run('models/user_test.rb') + + output_2 = script('generate migration add_email_to_users') + version_2 = output_2.match(/(\d+)_add_email_to_users\.rb/)[1] + + app_file 'test/models/user_test.rb', <<-RUBY + require 'test_helper' + + class UserTest < ActiveSupport::TestCase + test "user" do + User.create! name: "Jon", email: "jon@doe.com" + end + end + RUBY + + app_file 'db/structure.sql', <<-SQL + CREATE TABLE "schema_migrations" ("version" varchar(255) NOT NULL); + CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version"); + CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255), "email" varchar(255)); + INSERT INTO schema_migrations (version) VALUES ('#{version_1}'); + INSERT INTO schema_migrations (version) VALUES ('#{version_2}'); + SQL + + assert_successful_test_run('models/user_test.rb') + end + + # TODO: would be nice if we could detect the schema change automatically. + # For now, the user has to synchronize the schema manually. + # This test-case serves as a reminder for this use-case. + test "manually synchronize test schema after rollback" do + output = script('generate model user name:string') + version = output.match(/(\d+)_create_users\.rb/)[1] + + app_file 'test/models/user_test.rb', <<-RUBY + require 'test_helper' + + class UserTest < ActiveSupport::TestCase + test "user" do + assert_equal ["id", "name"], User.columns_hash.keys + end + end + RUBY + app_file 'db/schema.rb', <<-RUBY + ActiveRecord::Schema.define(version: #{version}) do + create_table :users do |t| + t.string :name + end + end + RUBY + + assert_successful_test_run "models/user_test.rb" + + # Simulate `db:rollback` + edit of the migration file + `db:migrate` + app_file 'db/schema.rb', <<-RUBY + ActiveRecord::Schema.define(version: #{version}) do + create_table :users do |t| + t.string :name + t.integer :age + end + end + RUBY + + assert_successful_test_run "models/user_test.rb" + + Dir.chdir(app_path) { `bin/rake db:test:prepare` } + + assert_unsuccessful_run "models/user_test.rb", <<-ASSERTION +Expected: ["id", "name"] + Actual: ["id", "name", "age"] + ASSERTION + end + + test "hooks for plugins" do + output = script('generate model user name:string') + version = output.match(/(\d+)_create_users\.rb/)[1] + + app_file 'lib/tasks/hooks.rake', <<-RUBY + task :before_hook do + has_user_table = ActiveRecord::Base.connection.table_exists?('users') + puts "before: " + has_user_table.to_s + end + + task :after_hook do + has_user_table = ActiveRecord::Base.connection.table_exists?('users') + puts "after: " + has_user_table.to_s + end + + Rake::Task["db:test:prepare"].enhance [:before_hook] do + Rake::Task[:after_hook].invoke + end + RUBY + app_file 'test/models/user_test.rb', <<-RUBY + require 'test_helper' + class UserTest < ActiveSupport::TestCase + test "user" do + User.create! name: "Jon" + end + end + RUBY + + # Simulate `db:migrate` + app_file 'db/schema.rb', <<-RUBY + ActiveRecord::Schema.define(version: #{version}) do + create_table :users do |t| + t.string :name + end + end + RUBY + + output = assert_successful_test_run "models/user_test.rb" + assert_includes output, "before: false\nafter: true" + + # running tests again won't trigger a schema update + output = assert_successful_test_run "models/user_test.rb" + assert_not_includes output, "before:" + assert_not_includes output, "after:" + end + + private + def assert_unsuccessful_run(name, message) + result = run_test_file(name) + assert_not_equal 0, $?.to_i + assert result.include?(message) + result + end + + def assert_successful_test_run(name) + result = run_test_file(name) + assert_equal 0, $?.to_i, result + result + end + + def run_test_file(name, options = {}) + Dir.chdir(app_path) { `bin/rails test "#{app_path}/test/#{name}" 2>&1` } + end + end +end diff --git a/railties/test/application/url_generation_test.rb b/railties/test/application/url_generation_test.rb new file mode 100644 index 0000000000..894e18cb39 --- /dev/null +++ b/railties/test/application/url_generation_test.rb @@ -0,0 +1,59 @@ +require 'isolation/abstract_unit' + +module ApplicationTests + class UrlGenerationTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def app + Rails.application + end + + test "it works" do + boot_rails + require "rails" + require "action_controller/railtie" + require "action_view/railtie" + + class MyApp < Rails::Application + secrets.secret_key_base = "3b7cd727ee24e8444053437c36cc66c4" + config.session_store :cookie_store, key: "_myapp_session" + config.active_support.deprecation = :log + config.eager_load = false + end + + Rails.application.initialize! + + class ::ApplicationController < ActionController::Base + end + + class ::OmgController < ::ApplicationController + def index + render text: omg_path + end + end + + MyApp.routes.draw do + get "/" => "omg#index", as: :omg + end + + require 'rack/test' + extend Rack::Test::Methods + + get "/" + assert_equal "/", last_response.body + end + + def test_routes_know_the_relative_root + boot_rails + require "rails" + require "action_controller/railtie" + require "action_view/railtie" + + relative_url = '/hello' + ENV["RAILS_RELATIVE_URL_ROOT"] = relative_url + app = Class.new(Rails::Application) + assert_equal relative_url, app.routes.relative_url_root + ENV["RAILS_RELATIVE_URL_ROOT"] = nil + end + end +end diff --git a/railties/test/backtrace_cleaner_test.rb b/railties/test/backtrace_cleaner_test.rb new file mode 100644 index 0000000000..2dd74f8fd1 --- /dev/null +++ b/railties/test/backtrace_cleaner_test.rb @@ -0,0 +1,24 @@ +require 'abstract_unit' +require 'rails/backtrace_cleaner' + +class BacktraceCleanerVendorGemTest < ActiveSupport::TestCase + def setup + @cleaner = Rails::BacktraceCleaner.new + end + + test "should format installed gems correctly" do + @backtrace = [ "#{Gem.path[0]}/gems/nosuchgem-1.2.3/lib/foo.rb" ] + @result = @cleaner.clean(@backtrace, :all) + assert_equal "nosuchgem (1.2.3) lib/foo.rb", @result[0] + end + + test "should format installed gems not in Gem.default_dir correctly" do + @target_dir = Gem.path.detect { |p| p != Gem.default_dir } + # skip this test if default_dir is the only directory on Gem.path + if @target_dir + @backtrace = [ "#{@target_dir}/gems/nosuchgem-1.2.3/lib/foo.rb" ] + @result = @cleaner.clean(@backtrace, :all) + assert_equal "nosuchgem (1.2.3) lib/foo.rb", @result[0] + end + end +end diff --git a/railties/test/code_statistics_calculator_test.rb b/railties/test/code_statistics_calculator_test.rb new file mode 100644 index 0000000000..cecc3908b3 --- /dev/null +++ b/railties/test/code_statistics_calculator_test.rb @@ -0,0 +1,330 @@ +require 'abstract_unit' +require 'rails/code_statistics_calculator' + +class CodeStatisticsCalculatorTest < ActiveSupport::TestCase + def setup + @code_statistics_calculator = CodeStatisticsCalculator.new + end + + test 'calculate statistics using #add_by_file_path' do + code = <<-RUBY + def foo + puts 'foo' + # bar + end + RUBY + + temp_file 'stats.rb', code do |path| + @code_statistics_calculator.add_by_file_path path + + assert_equal 4, @code_statistics_calculator.lines + assert_equal 3, @code_statistics_calculator.code_lines + assert_equal 0, @code_statistics_calculator.classes + assert_equal 1, @code_statistics_calculator.methods + end + end + + test 'count number of methods in MiniTest file' do + code = <<-RUBY + class FooTest < ActionController::TestCase + test 'expectation' do + assert true + end + + def test_expectation + assert true + end + end + RUBY + + temp_file 'foo_test.rb', code do |path| + @code_statistics_calculator.add_by_file_path path + assert_equal 2, @code_statistics_calculator.methods + end + end + + test 'add statistics to another using #add' do + code_statistics_calculator_1 = CodeStatisticsCalculator.new(1, 2, 3, 4) + @code_statistics_calculator.add(code_statistics_calculator_1) + + assert_equal 1, @code_statistics_calculator.lines + assert_equal 2, @code_statistics_calculator.code_lines + assert_equal 3, @code_statistics_calculator.classes + assert_equal 4, @code_statistics_calculator.methods + + code_statistics_calculator_2 = CodeStatisticsCalculator.new(2, 3, 4, 5) + @code_statistics_calculator.add(code_statistics_calculator_2) + + assert_equal 3, @code_statistics_calculator.lines + assert_equal 5, @code_statistics_calculator.code_lines + assert_equal 7, @code_statistics_calculator.classes + assert_equal 9, @code_statistics_calculator.methods + end + + test 'accumulate statistics using #add_by_io' do + code_statistics_calculator_1 = CodeStatisticsCalculator.new(1, 2, 3, 4) + @code_statistics_calculator.add(code_statistics_calculator_1) + + code = <<-'CODE' + def foo + puts 'foo' + end + + def bar; end + class A; end + CODE + + @code_statistics_calculator.add_by_io(StringIO.new(code), :rb) + + assert_equal 7, @code_statistics_calculator.lines + assert_equal 7, @code_statistics_calculator.code_lines + assert_equal 4, @code_statistics_calculator.classes + assert_equal 6, @code_statistics_calculator.methods + end + + test 'calculate number of Ruby methods' do + code = <<-'CODE' + def foo + puts 'foo' + end + + def bar; end + + class Foo + def bar(abc) + end + end + CODE + + @code_statistics_calculator.add_by_io(StringIO.new(code), :rb) + + assert_equal 3, @code_statistics_calculator.methods + end + + test 'calculate Ruby LOCs' do + code = <<-'CODE' + def foo + puts 'foo' + end + + # def bar; end + + class A < B + end + CODE + + @code_statistics_calculator.add_by_io(StringIO.new(code), :rb) + + assert_equal 8, @code_statistics_calculator.lines + assert_equal 5, @code_statistics_calculator.code_lines + end + + test 'calculate number of Ruby classes' do + code = <<-'CODE' + class Foo < Bar + def foo + puts 'foo' + end + end + + class Z; end + + # class A + # end + CODE + + @code_statistics_calculator.add_by_io(StringIO.new(code), :rb) + + assert_equal 2, @code_statistics_calculator.classes + end + + test 'skip Ruby comments' do + code = <<-'CODE' +=begin + class Foo + def foo + puts 'foo' + end + end +=end + + # class A + # end + CODE + + @code_statistics_calculator.add_by_io(StringIO.new(code), :rb) + + assert_equal 10, @code_statistics_calculator.lines + assert_equal 0, @code_statistics_calculator.code_lines + assert_equal 0, @code_statistics_calculator.classes + assert_equal 0, @code_statistics_calculator.methods + end + + test 'calculate number of JS methods' do + code = <<-'CODE' + function foo(x, y, z) { + doX(); + } + + $(function () { + bar(); + }) + + var baz = function ( x ) { + } + CODE + + @code_statistics_calculator.add_by_io(StringIO.new(code), :js) + + assert_equal 3, @code_statistics_calculator.methods + end + + test 'calculate JS LOCs' do + code = <<-'CODE' + function foo() + alert('foo'); + end + + // var b = 2; + + var a = 1; + CODE + + @code_statistics_calculator.add_by_io(StringIO.new(code), :js) + + assert_equal 7, @code_statistics_calculator.lines + assert_equal 4, @code_statistics_calculator.code_lines + end + + test 'skip JS comments' do + code = <<-'CODE' + /* + * var f = function () { + 1 / 2; + } + */ + + // call(); + // + CODE + + @code_statistics_calculator.add_by_io(StringIO.new(code), :js) + + assert_equal 8, @code_statistics_calculator.lines + assert_equal 0, @code_statistics_calculator.code_lines + assert_equal 0, @code_statistics_calculator.classes + assert_equal 0, @code_statistics_calculator.methods + end + + test 'calculate number of CoffeeScript methods' do + code = <<-'CODE' + square = (x) -> x * x + + math = + cube: (x) -> x * square x + + fill = (container, liquid = "coffee") -> + "Filling the #{container} with #{liquid}..." + + $('.shopping_cart').bind 'click', (event) => + @customer.purchase @cart + CODE + + @code_statistics_calculator.add_by_io(StringIO.new(code), :coffee) + + assert_equal 4, @code_statistics_calculator.methods + end + + test 'calculate CoffeeScript LOCs' do + code = <<-'CODE' + # Assignment: + number = 42 + opposite = true + + ### + CoffeeScript Compiler v1.4.0 + Released under the MIT License + ### + + # Conditions: + number = -42 if opposite + CODE + + @code_statistics_calculator.add_by_io(StringIO.new(code), :coffee) + + assert_equal 11, @code_statistics_calculator.lines + assert_equal 3, @code_statistics_calculator.code_lines + end + + test 'calculate number of CoffeeScript classes' do + code = <<-'CODE' + class Animal + constructor: (@name) -> + + move: (meters) -> + alert @name + " moved #{meters}m." + + class Snake extends Animal + move: -> + alert "Slithering..." + super 5 + + # class Horse + CODE + + @code_statistics_calculator.add_by_io(StringIO.new(code), :coffee) + + assert_equal 2, @code_statistics_calculator.classes + end + + test 'skip CoffeeScript comments' do + code = <<-'CODE' +### +class Animal + constructor: (@name) -> + + move: (meters) -> + alert @name + " moved #{meters}m." + ### + + # class Horse + alert 'hello' + CODE + + @code_statistics_calculator.add_by_io(StringIO.new(code), :coffee) + + assert_equal 10, @code_statistics_calculator.lines + assert_equal 1, @code_statistics_calculator.code_lines + assert_equal 0, @code_statistics_calculator.classes + assert_equal 0, @code_statistics_calculator.methods + end + + test 'count rake tasks' do + code = <<-'CODE' + task :test_task do + puts 'foo' + end + + CODE + + @code_statistics_calculator.add_by_io(StringIO.new(code), :rake) + + assert_equal 4, @code_statistics_calculator.lines + assert_equal 3, @code_statistics_calculator.code_lines + assert_equal 0, @code_statistics_calculator.classes + assert_equal 0, @code_statistics_calculator.methods + end + + private + def temp_file(name, content) + dir = File.expand_path '../fixtures/tmp', __FILE__ + path = "#{dir}/#{name}" + + FileUtils.mkdir_p dir + File.write path, content + + yield path + ensure + FileUtils.rm_rf path + end +end diff --git a/railties/test/code_statistics_test.rb b/railties/test/code_statistics_test.rb new file mode 100644 index 0000000000..1b1ff80bc1 --- /dev/null +++ b/railties/test/code_statistics_test.rb @@ -0,0 +1,20 @@ +require 'abstract_unit' +require 'rails/code_statistics' + +class CodeStatisticsTest < ActiveSupport::TestCase + def setup + @tmp_path = File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'tmp')) + @dir_js = File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'tmp', 'lib.js')) + FileUtils.mkdir_p(@dir_js) + end + + def teardown + FileUtils.rm_rf(@tmp_path) + end + + test 'ignores directories that happen to have source files extensions' do + assert_nothing_raised do + @code_statistics = CodeStatistics.new(['tmp dir', @tmp_path]) + end + end +end diff --git a/railties/test/commands/console_test.rb b/railties/test/commands/console_test.rb new file mode 100644 index 0000000000..de0cf0ba9e --- /dev/null +++ b/railties/test/commands/console_test.rb @@ -0,0 +1,154 @@ +require 'abstract_unit' +require 'env_helpers' +require 'rails/commands/console' + +class Rails::ConsoleTest < ActiveSupport::TestCase + include EnvHelpers + + class FakeConsole + def self.started? + @started + end + + def self.start + @started = true + end + end + + def test_sandbox_option + console = Rails::Console.new(app, parse_arguments(["--sandbox"])) + assert console.sandbox? + end + + def test_short_version_of_sandbox_option + console = Rails::Console.new(app, parse_arguments(["-s"])) + assert console.sandbox? + end + + def test_no_options + console = Rails::Console.new(app, parse_arguments([])) + assert !console.sandbox? + end + + def test_start + start + + assert app.console.started? + assert_match(/Loading \w+ environment \(Rails/, output) + end + + def test_start_with_sandbox + start ["--sandbox"] + + + assert app.console.started? + assert app.sandbox + assert_match(/Loading \w+ environment in sandbox \(Rails/, output) + end + + def test_console_with_environment + start ["-e production"] + assert_match(/\sproduction\s/, output) + end + + def test_console_defaults_to_IRB + app = build_app(nil) + assert_equal IRB, Rails::Console.new(app).console + end + + def test_default_environment_with_no_rails_env + with_rails_env nil do + start + assert_match(/\sdevelopment\s/, output) + end + end + + def test_default_environment_with_rails_env + with_rails_env 'special-production' do + start + assert_match(/\sspecial-production\s/, output) + end + end + + def test_default_environment_with_rack_env + with_rack_env 'production' do + start + assert_match(/\sproduction\s/, output) + end + end + + def test_e_option + start ['-e', 'special-production'] + assert_match(/\sspecial-production\s/, output) + end + + def test_environment_option + start ['--environment=special-production'] + assert_match(/\sspecial-production\s/, output) + end + + def test_rails_env_is_production_when_first_argument_is_p + start ['p'] + assert_match(/\sproduction\s/, output) + end + + def test_rails_env_is_test_when_first_argument_is_t + start ['t'] + assert_match(/\stest\s/, output) + end + + def test_rails_env_is_development_when_argument_is_d + start ['d'] + assert_match(/\sdevelopment\s/, output) + end + + def test_rails_env_is_dev_when_argument_is_dev_and_dev_env_is_present + stubbed_console = Class.new(Rails::Console) do + def available_environments + ['dev'] + end + end + options = stubbed_console.parse_arguments(['dev']) + assert_match('dev', options[:environment]) + end + + attr_reader :output + private :output + + private + + def start(argv = []) + rails_console = Rails::Console.new(app, parse_arguments(argv)) + @output = capture(:stdout) { rails_console.start } + end + + def app + @app ||= build_app(FakeConsole) + end + + def build_app(console) + mocked_console = Class.new do + attr_reader :sandbox, :console + + def initialize(console) + @console = console + end + + def config + self + end + + def sandbox=(arg) + @sandbox = arg + end + + def load_console + end + end + mocked_console.new(console) + end + + def parse_arguments(args) + Rails::Console.parse_arguments(args) + end +end diff --git a/railties/test/commands/dbconsole_test.rb b/railties/test/commands/dbconsole_test.rb new file mode 100644 index 0000000000..7950ed6aa7 --- /dev/null +++ b/railties/test/commands/dbconsole_test.rb @@ -0,0 +1,264 @@ +require 'abstract_unit' +require 'minitest/mock' +require 'rails/commands/dbconsole' + +class Rails::DBConsoleTest < ActiveSupport::TestCase + + + def setup + Rails::DBConsole.const_set('APP_PATH', 'rails/all') + end + + def teardown + Rails::DBConsole.send(:remove_const, 'APP_PATH') + %w[PGUSER PGHOST PGPORT PGPASSWORD DATABASE_URL].each{|key| ENV.delete(key)} + end + + def test_config_with_db_config_only + config_sample = { + "test"=> { + "adapter"=> "sqlite3", + "host"=> "localhost", + "port"=> "9000", + "database"=> "foo_test", + "user"=> "foo", + "password"=> "bar", + "pool"=> "5", + "timeout"=> "3000" + } + } + app_db_config(config_sample) do + assert_equal config_sample["test"], Rails::DBConsole.new.config + end + end + + def test_config_with_no_db_config + app_db_config(nil) do + assert_raise(ActiveRecord::AdapterNotSpecified) { + Rails::DBConsole.new.config + } + end + end + + def test_config_with_database_url_only + ENV['DATABASE_URL'] = 'postgresql://foo:bar@localhost:9000/foo_test?pool=5&timeout=3000' + expected = { + "adapter" => "postgresql", + "host" => "localhost", + "port" => 9000, + "database" => "foo_test", + "username" => "foo", + "password" => "bar", + "pool" => "5", + "timeout" => "3000" + }.sort + + app_db_config(nil) do + assert_equal expected, Rails::DBConsole.new.config.sort + end + end + + def test_config_choose_database_url_if_exists + host = "database-url-host.com" + ENV['DATABASE_URL'] = "postgresql://foo:bar@#{host}:9000/foo_test?pool=5&timeout=3000" + sample_config = { + "test" => { + "adapter" => "postgresql", + "host" => "not-the-#{host}", + "port" => 9000, + "database" => "foo_test", + "username" => "foo", + "password" => "bar", + "pool" => "5", + "timeout" => "3000" + } + } + app_db_config(sample_config) do + assert_equal host, Rails::DBConsole.new.config["host"] + end + end + + def test_env + assert_equal "test", Rails::DBConsole.new.environment + + ENV['RAILS_ENV'] = nil + ENV['RACK_ENV'] = nil + + Rails.stub(:respond_to?, false) do + assert_equal "development", Rails::DBConsole.new.environment + + ENV['RACK_ENV'] = "rack_env" + assert_equal "rack_env", Rails::DBConsole.new.environment + + ENV['RAILS_ENV'] = "rails_env" + assert_equal "rails_env", Rails::DBConsole.new.environment + end + ensure + ENV['RAILS_ENV'] = "test" + ENV['RACK_ENV'] = nil + end + + def test_rails_env_is_development_when_argument_is_dev + Rails::DBConsole.stub(:available_environments, ['development', 'test']) do + options = Rails::DBConsole.send(:parse_arguments, ['dev']) + assert_match('development', options[:environment]) + end + end + + def test_rails_env_is_dev_when_argument_is_dev_and_dev_env_is_present + Rails::DBConsole.stub(:available_environments, ['dev']) do + options = Rails::DBConsole.send(:parse_arguments, ['dev']) + assert_match('dev', options[:environment]) + end + end + + def test_mysql + start(adapter: 'mysql', database: 'db') + assert !aborted + assert_equal [%w[mysql mysql5], 'db'], dbconsole.find_cmd_and_exec_args + end + + def test_mysql_full + start(adapter: 'mysql', database: 'db', host: 'locahost', port: 1234, socket: 'socket', username: 'user', password: 'qwerty', encoding: 'UTF-8') + assert !aborted + assert_equal [%w[mysql mysql5], '--host=locahost', '--port=1234', '--socket=socket', '--user=user', '--default-character-set=UTF-8', '-p', 'db'], dbconsole.find_cmd_and_exec_args + end + + def test_mysql_include_password + start({adapter: 'mysql', database: 'db', username: 'user', password: 'qwerty'}, ['-p']) + assert !aborted + assert_equal [%w[mysql mysql5], '--user=user', '--password=qwerty', 'db'], dbconsole.find_cmd_and_exec_args + end + + def test_postgresql + start(adapter: 'postgresql', database: 'db') + assert !aborted + assert_equal ['psql', 'db'], dbconsole.find_cmd_and_exec_args + end + + def test_postgresql_full + start(adapter: 'postgresql', database: 'db', username: 'user', password: 'q1w2e3', host: 'host', port: 5432) + assert !aborted + assert_equal ['psql', 'db'], dbconsole.find_cmd_and_exec_args + assert_equal 'user', ENV['PGUSER'] + assert_equal 'host', ENV['PGHOST'] + assert_equal '5432', ENV['PGPORT'] + assert_not_equal 'q1w2e3', ENV['PGPASSWORD'] + end + + def test_postgresql_include_password + start({adapter: 'postgresql', database: 'db', username: 'user', password: 'q1w2e3'}, ['-p']) + assert !aborted + assert_equal ['psql', 'db'], dbconsole.find_cmd_and_exec_args + assert_equal 'user', ENV['PGUSER'] + assert_equal 'q1w2e3', ENV['PGPASSWORD'] + end + + def test_sqlite3 + start(adapter: 'sqlite3', database: 'db.sqlite3') + assert !aborted + assert_equal ['sqlite3', Rails.root.join('db.sqlite3').to_s], dbconsole.find_cmd_and_exec_args + end + + def test_sqlite3_mode + start({adapter: 'sqlite3', database: 'db.sqlite3'}, ['--mode', 'html']) + assert !aborted + assert_equal ['sqlite3', '-html', Rails.root.join('db.sqlite3').to_s], dbconsole.find_cmd_and_exec_args + end + + def test_sqlite3_header + start({adapter: 'sqlite3', database: 'db.sqlite3'}, ['--header']) + assert_equal ['sqlite3', '-header', Rails.root.join('db.sqlite3').to_s], dbconsole.find_cmd_and_exec_args + end + + def test_sqlite3_db_absolute_path + start(adapter: 'sqlite3', database: '/tmp/db.sqlite3') + assert !aborted + assert_equal ['sqlite3', '/tmp/db.sqlite3'], dbconsole.find_cmd_and_exec_args + end + + def test_sqlite3_db_without_defined_rails_root + Rails.stub(:respond_to?, false) do + start(adapter: 'sqlite3', database: 'config/db.sqlite3') + assert !aborted + assert_equal ['sqlite3', Rails.root.join('../config/db.sqlite3').to_s], dbconsole.find_cmd_and_exec_args + end + end + + def test_oracle + start(adapter: 'oracle', database: 'db', username: 'user', password: 'secret') + assert !aborted + assert_equal ['sqlplus', 'user@db'], dbconsole.find_cmd_and_exec_args + end + + def test_oracle_include_password + start({adapter: 'oracle', database: 'db', username: 'user', password: 'secret'}, ['-p']) + assert !aborted + assert_equal ['sqlplus', 'user/secret@db'], dbconsole.find_cmd_and_exec_args + end + + def test_unknown_command_line_client + start(adapter: 'unknown', database: 'db') + assert aborted + assert_match(/Unknown command-line client for db/, output) + end + + def test_print_help_short + stdout = capture(:stdout) do + start({}, ['-h']) + end + assert aborted + assert_equal '', output + assert_match(/Usage:.*dbconsole/, stdout) + end + + def test_print_help_long + stdout = capture(:stdout) do + start({}, ['--help']) + end + assert aborted + assert_equal '', output + assert_match(/Usage:.*dbconsole/, stdout) + end + + attr_reader :aborted, :output + private :aborted, :output + + private + + def app_db_config(results) + Rails.application.config.stub(:database_configuration, results || {}) do + yield + end + end + + def dbconsole + @dbconsole ||= Class.new(Rails::DBConsole) do + attr_reader :find_cmd_and_exec_args + + def find_cmd_and_exec(*args) + @find_cmd_and_exec_args = args + end + end.new(nil) + end + + def start(config = {}, argv = []) + dbconsole.stub(:config, config.stringify_keys) do + dbconsole.stub(:arguments, argv) do + capture_abort { dbconsole.start } + end + end + end + + def capture_abort + @aborted = false + @output = capture(:stderr) do + begin + yield + rescue SystemExit + @aborted = true + end + end + end + +end diff --git a/railties/test/commands/dev_cache_test.rb b/railties/test/commands/dev_cache_test.rb new file mode 100644 index 0000000000..1b7a72e7fc --- /dev/null +++ b/railties/test/commands/dev_cache_test.rb @@ -0,0 +1,32 @@ +require_relative '../isolation/abstract_unit' + +module CommandsTests + class DevCacheTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + end + + def teardown + teardown_app + end + + test 'dev:cache creates file and outputs message' do + Dir.chdir(app_path) do + output = `rails dev:cache` + assert File.exist?('tmp/caching-dev.txt') + assert_match(%r{Development mode is now being cached}, output) + end + end + + test 'dev:cache deletes file and outputs message' do + Dir.chdir(app_path) do + `rails dev:cache` # Create caching file. + output = `rails dev:cache` # Delete caching file. + assert_not File.exist?('tmp/caching-dev.txt') + assert_match(%r{Development mode is no longer being cached}, output) + end + end + end +end diff --git a/railties/test/commands/server_test.rb b/railties/test/commands/server_test.rb new file mode 100644 index 0000000000..3be4a74f74 --- /dev/null +++ b/railties/test/commands/server_test.rb @@ -0,0 +1,111 @@ +require 'abstract_unit' +require 'env_helpers' +require 'rails/commands/server' + +class Rails::ServerTest < ActiveSupport::TestCase + include EnvHelpers + + def test_environment_with_server_option + args = ["thin", "-e", "production"] + options = Rails::Server::Options.new.parse!(args) + assert_equal 'production', options[:environment] + assert_equal 'thin', options[:server] + end + + def test_environment_without_server_option + args = ["-e", "production"] + options = Rails::Server::Options.new.parse!(args) + assert_equal 'production', options[:environment] + assert_nil options[:server] + end + + def test_server_option_without_environment + args = ["thin"] + options = Rails::Server::Options.new.parse!(args) + assert_nil options[:environment] + assert_equal 'thin', options[:server] + end + + def test_environment_with_rails_env + with_rack_env nil do + with_rails_env 'production' do + server = Rails::Server.new + assert_equal 'production', server.options[:environment] + end + end + end + + def test_environment_with_rack_env + with_rails_env nil do + with_rack_env 'production' do + server = Rails::Server.new + assert_equal 'production', server.options[:environment] + end + end + end + + def test_environment_with_port + switch_env "PORT", "1234" do + server = Rails::Server.new + assert_equal 1234, server.options[:Port] + end + end + + def test_caching_without_option + args = [] + options = Rails::Server::Options.new.parse!(args) + assert_equal nil, options[:caching] + end + + def test_caching_with_option + args = ["--dev-caching"] + options = Rails::Server::Options.new.parse!(args) + assert_equal true, options[:caching] + + args = ["--no-dev-caching"] + options = Rails::Server::Options.new.parse!(args) + assert_equal false, options[:caching] + end + + def test_log_stdout + with_rack_env nil do + with_rails_env nil do + args = [] + options = Rails::Server::Options.new.parse!(args) + assert_equal true, options[:log_stdout] + + args = ["-e", "development"] + options = Rails::Server::Options.new.parse!(args) + assert_equal true, options[:log_stdout] + + args = ["-e", "production"] + options = Rails::Server::Options.new.parse!(args) + assert_equal false, options[:log_stdout] + + with_rack_env 'development' do + args = [] + options = Rails::Server::Options.new.parse!(args) + assert_equal true, options[:log_stdout] + end + + with_rack_env 'production' do + args = [] + options = Rails::Server::Options.new.parse!(args) + assert_equal false, options[:log_stdout] + end + + with_rails_env 'development' do + args = [] + options = Rails::Server::Options.new.parse!(args) + assert_equal true, options[:log_stdout] + end + + with_rails_env 'production' do + args = [] + options = Rails::Server::Options.new.parse!(args) + assert_equal false, options[:log_stdout] + end + end + end + end +end diff --git a/railties/test/configuration/middleware_stack_proxy_test.rb b/railties/test/configuration/middleware_stack_proxy_test.rb new file mode 100644 index 0000000000..d5072614cf --- /dev/null +++ b/railties/test/configuration/middleware_stack_proxy_test.rb @@ -0,0 +1,61 @@ +require 'active_support' +require 'active_support/testing/autorun' +require 'rails/configuration' +require 'active_support/test_case' +require 'minitest/mock' + +module Rails + module Configuration + class MiddlewareStackProxyTest < ActiveSupport::TestCase + def setup + @stack = MiddlewareStackProxy.new + end + + def test_playback_insert_before + @stack.insert_before :foo + assert_playback :insert_before, :foo + end + + def test_playback_insert_after + @stack.insert_after :foo + assert_playback :insert_after, :foo + end + + def test_playback_swap + @stack.swap :foo + assert_playback :swap, :foo + end + + def test_playback_use + @stack.use :foo + assert_playback :use, :foo + end + + def test_playback_delete + @stack.delete :foo + assert_playback :delete, :foo + end + + def test_order + @stack.swap :foo + @stack.delete :foo + + mock = Minitest::Mock.new + mock.expect :send, nil, [:swap, :foo] + mock.expect :send, nil, [:delete, :foo] + + @stack.merge_into mock + mock.verify + end + + private + + def assert_playback(msg_name, args) + mock = Minitest::Mock.new + mock.expect :send, nil, [msg_name, args] + @stack.merge_into(mock) + mock.verify + end + end + end +end diff --git a/railties/test/engine_test.rb b/railties/test/engine_test.rb new file mode 100644 index 0000000000..f46fb748f5 --- /dev/null +++ b/railties/test/engine_test.rb @@ -0,0 +1,25 @@ +require 'abstract_unit' + +class EngineTest < ActiveSupport::TestCase + test "reports routes as available only if they're actually present" do + engine = Class.new(Rails::Engine) do + def initialize(*args) + @routes = nil + super + end + end + + assert !engine.routes? + end + + def test_application_can_be_subclassed + klass = Class.new(Rails::Application) do + attr_reader :hello + def initialize + @hello = "world" + super + end + end + assert_equal "world", klass.instance.hello + end +end diff --git a/railties/test/env_helpers.rb b/railties/test/env_helpers.rb new file mode 100644 index 0000000000..330fe150ca --- /dev/null +++ b/railties/test/env_helpers.rb @@ -0,0 +1,30 @@ +require 'rails' + +module EnvHelpers + private + + def with_rails_env(env) + Rails.instance_variable_set :@_env, nil + switch_env 'RAILS_ENV', env do + switch_env 'RACK_ENV', nil do + yield + end + end + end + + def with_rack_env(env) + Rails.instance_variable_set :@_env, nil + switch_env 'RACK_ENV', env do + switch_env 'RAILS_ENV', nil do + yield + end + end + end + + def switch_env(key, value) + old, ENV[key] = ENV[key], value + yield + ensure + ENV[key] = old + end +end diff --git a/railties/test/fixtures/about_yml_plugins/bad_about_yml/about.yml b/railties/test/fixtures/about_yml_plugins/bad_about_yml/about.yml new file mode 100644 index 0000000000..fe80872a16 --- /dev/null +++ b/railties/test/fixtures/about_yml_plugins/bad_about_yml/about.yml @@ -0,0 +1 @@ +# an empty YAML file - any content in here seems to get parsed as a string
\ No newline at end of file diff --git a/railties/test/fixtures/about_yml_plugins/bad_about_yml/init.rb b/railties/test/fixtures/about_yml_plugins/bad_about_yml/init.rb new file mode 100644 index 0000000000..d4262f8971 --- /dev/null +++ b/railties/test/fixtures/about_yml_plugins/bad_about_yml/init.rb @@ -0,0 +1 @@ +# intentionally empty
\ No newline at end of file diff --git a/railties/test/fixtures/about_yml_plugins/plugin_without_about_yml/init.rb b/railties/test/fixtures/about_yml_plugins/plugin_without_about_yml/init.rb new file mode 100644 index 0000000000..d4262f8971 --- /dev/null +++ b/railties/test/fixtures/about_yml_plugins/plugin_without_about_yml/init.rb @@ -0,0 +1 @@ +# intentionally empty
\ No newline at end of file diff --git a/railties/test/fixtures/lib/create_test_dummy_template.rb b/railties/test/fixtures/lib/create_test_dummy_template.rb new file mode 100644 index 0000000000..e4378bbd1a --- /dev/null +++ b/railties/test/fixtures/lib/create_test_dummy_template.rb @@ -0,0 +1 @@ +create_dummy_app("spec/dummy") diff --git a/railties/test/fixtures/lib/generators/active_record/fixjour_generator.rb b/railties/test/fixtures/lib/generators/active_record/fixjour_generator.rb new file mode 100644 index 0000000000..a7d079a1bc --- /dev/null +++ b/railties/test/fixtures/lib/generators/active_record/fixjour_generator.rb @@ -0,0 +1,8 @@ +require 'rails/generators/active_record' + +module ActiveRecord + module Generators + class FixjourGenerator < Base + end + end +end diff --git a/railties/test/fixtures/lib/generators/fixjour_generator.rb b/railties/test/fixtures/lib/generators/fixjour_generator.rb new file mode 100644 index 0000000000..ef3e9edbed --- /dev/null +++ b/railties/test/fixtures/lib/generators/fixjour_generator.rb @@ -0,0 +1,2 @@ +class FixjourGenerator < Rails::Generators::NamedBase +end diff --git a/railties/test/fixtures/lib/generators/model_generator.rb b/railties/test/fixtures/lib/generators/model_generator.rb new file mode 100644 index 0000000000..9098a8a354 --- /dev/null +++ b/railties/test/fixtures/lib/generators/model_generator.rb @@ -0,0 +1 @@ +raise "I should never be loaded"
\ No newline at end of file diff --git a/railties/test/fixtures/lib/generators/usage_template/USAGE b/railties/test/fixtures/lib/generators/usage_template/USAGE new file mode 100644 index 0000000000..bcd63c52e2 --- /dev/null +++ b/railties/test/fixtures/lib/generators/usage_template/USAGE @@ -0,0 +1 @@ +:: <%= 1 + 1 %> ::
\ No newline at end of file diff --git a/railties/test/fixtures/lib/generators/usage_template/usage_template_generator.rb b/railties/test/fixtures/lib/generators/usage_template/usage_template_generator.rb new file mode 100644 index 0000000000..078b0f9412 --- /dev/null +++ b/railties/test/fixtures/lib/generators/usage_template/usage_template_generator.rb @@ -0,0 +1,5 @@ +require 'rails/generators' + +class UsageTemplateGenerator < Rails::Generators::Base + source_root File.expand_path("templates", File.dirname(__FILE__)) +end diff --git a/railties/test/fixtures/lib/rails/generators/foobar/foobar_generator.rb b/railties/test/fixtures/lib/rails/generators/foobar/foobar_generator.rb new file mode 100644 index 0000000000..d1de8c56fa --- /dev/null +++ b/railties/test/fixtures/lib/rails/generators/foobar/foobar_generator.rb @@ -0,0 +1,4 @@ +module Foobar + class FoobarGenerator < Rails::Generators::Base + end +end diff --git a/railties/test/fixtures/lib/template.rb b/railties/test/fixtures/lib/template.rb new file mode 100644 index 0000000000..c14a1a8784 --- /dev/null +++ b/railties/test/fixtures/lib/template.rb @@ -0,0 +1 @@ +say "It works from file!" diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb new file mode 100644 index 0000000000..b4fbea4af4 --- /dev/null +++ b/railties/test/generators/actions_test.rb @@ -0,0 +1,316 @@ +require 'generators/generators_test_helper' +require 'rails/generators/rails/app/app_generator' +require 'env_helpers' + +class ActionsTest < Rails::Generators::TestCase + include GeneratorsTestHelper + include EnvHelpers + + tests Rails::Generators::AppGenerator + arguments [destination_root] + + def setup + Rails.application = TestApp::Application + super + end + + def teardown + Rails.application = TestApp::Application.instance + end + + def test_invoke_other_generator_with_shortcut + action :invoke, 'model', ['my_model'] + assert_file 'app/models/my_model.rb', /MyModel/ + end + + def test_invoke_other_generator_with_full_namespace + action :invoke, 'rails:model', ['my_model'] + assert_file 'app/models/my_model.rb', /MyModel/ + end + + def test_create_file_should_write_data_to_file_path + action :create_file, 'lib/test_file.rb', 'heres test data' + assert_file 'lib/test_file.rb', 'heres test data' + end + + def test_create_file_should_write_block_contents_to_file_path + action(:create_file, 'lib/test_file.rb'){ 'heres block data' } + assert_file 'lib/test_file.rb', 'heres block data' + end + + def test_add_source_adds_source_to_gemfile + run_generator + action :add_source, 'http://gems.github.com' + assert_file 'Gemfile', /source 'http:\/\/gems\.github\.com'/ + end + + def test_add_source_with_block_adds_source_to_gemfile_with_gem + run_generator + action :add_source, 'http://gems.github.com' do + gem 'rspec-rails' + end + assert_file 'Gemfile', /source 'http:\/\/gems\.github\.com' do\n gem 'rspec-rails'\nend/ + end + + def test_gem_should_put_gem_dependency_in_gemfile + run_generator + action :gem, 'will-paginate' + assert_file 'Gemfile', /gem 'will\-paginate'/ + end + + def test_gem_with_version_should_include_version_in_gemfile + run_generator + + action :gem, 'rspec', '>=2.0.0.a5' + + assert_file 'Gemfile', /gem 'rspec', '>=2.0.0.a5'/ + end + + def test_gem_should_insert_on_separate_lines + run_generator + + File.open('Gemfile', 'a') {|f| f.write('# Some content...') } + + action :gem, 'rspec' + action :gem, 'rspec-rails' + + assert_file 'Gemfile', /^gem 'rspec'$/ + assert_file 'Gemfile', /^gem 'rspec-rails'$/ + end + + def test_gem_should_include_options + run_generator + + action :gem, 'rspec', github: 'dchelimsky/rspec', tag: '1.2.9.rc1' + + assert_file 'Gemfile', /gem 'rspec', github: 'dchelimsky\/rspec', tag: '1\.2\.9\.rc1'/ + end + + def test_gem_with_non_string_options + run_generator + + action :gem, 'rspec', require: false + action :gem, 'rspec-rails', group: [:development, :test] + + assert_file 'Gemfile', /^gem 'rspec', require: false$/ + assert_file 'Gemfile', /^gem 'rspec-rails', group: \[:development, :test\]$/ + end + + def test_gem_falls_back_to_inspect_if_string_contains_single_quote + run_generator + + action :gem, 'rspec', ">=2.0'0" + + assert_file 'Gemfile', /^gem 'rspec', ">=2\.0'0"$/ + end + + def test_gem_group_should_wrap_gems_in_a_group + run_generator + + action :gem_group, :development, :test do + gem 'rspec-rails' + end + + action :gem_group, :test do + gem 'fakeweb' + end + + assert_file 'Gemfile', /\ngroup :development, :test do\n gem 'rspec-rails'\nend\n\ngroup :test do\n gem 'fakeweb'\nend/ + end + + def test_environment_should_include_data_in_environment_initializer_block + run_generator + autoload_paths = 'config.autoload_paths += %w["#{Rails.root}/app/extras"]' + action :environment, autoload_paths + assert_file 'config/application.rb', / class Application < Rails::Application\n #{Regexp.escape(autoload_paths)}/ + end + + def test_environment_should_include_data_in_environment_initializer_block_with_env_option + run_generator + autoload_paths = 'config.autoload_paths += %w["#{Rails.root}/app/extras"]' + action :environment, autoload_paths, env: 'development' + assert_file "config/environments/development.rb", /Rails\.application\.configure do\n #{Regexp.escape(autoload_paths)}/ + end + + def test_environment_with_block_should_include_block_contents_in_environment_initializer_block + run_generator + + action :environment do + _ = '# This wont be added'# assignment to silence parse-time warning "unused literal ignored" + '# This will be added' + end + + assert_file 'config/application.rb' do |content| + assert_match(/# This will be added/, content) + assert_no_match(/# This wont be added/, content) + end + end + + def test_git_with_symbol_should_run_command_using_git_scm + assert_called_with(generator, :run, ['git init']) do + action :git, :init + end + end + + def test_git_with_hash_should_run_each_command_using_git_scm + assert_called_with(generator, :run, [ ["git rm README"], ["git add ."] ]) do + action :git, rm: 'README', add: '.' + end + end + + def test_vendor_should_write_data_to_file_in_vendor + action :vendor, 'vendor_file.rb', '# vendor data' + assert_file 'vendor/vendor_file.rb', '# vendor data' + end + + def test_lib_should_write_data_to_file_in_lib + action :lib, 'my_library.rb', 'class MyLibrary' + assert_file 'lib/my_library.rb', 'class MyLibrary' + end + + def test_rakefile_should_write_date_to_file_in_lib_tasks + action :rakefile, 'myapp.rake', 'task run: [:environment]' + assert_file 'lib/tasks/myapp.rake', 'task run: [:environment]' + end + + def test_initializer_should_write_date_to_file_in_config_initializers + action :initializer, 'constants.rb', 'MY_CONSTANT = 42' + assert_file 'config/initializers/constants.rb', 'MY_CONSTANT = 42' + end + + def test_generate_should_run_script_generate_with_argument_and_options + assert_called_with(generator, :run_ruby_script, ['bin/rails generate model MyModel', verbose: false]) do + action :generate, 'model', 'MyModel' + end + end + + def test_rake_should_run_rake_command_with_default_env + assert_called_with(generator, :run, ["rake log:clear RAILS_ENV=development", verbose: false]) do + with_rails_env nil do + action :rake, 'log:clear' + end + end + end + + def test_rake_with_env_option_should_run_rake_command_in_env + assert_called_with(generator, :run, ['rake log:clear RAILS_ENV=production', verbose: false]) do + action :rake, 'log:clear', env: 'production' + end + end + + def test_rake_with_rails_env_variable_should_run_rake_command_in_env + assert_called_with(generator, :run, ['rake log:clear RAILS_ENV=production', verbose: false]) do + with_rails_env "production" do + action :rake, 'log:clear' + end + end + end + + def test_env_option_should_win_over_rails_env_variable_when_running_rake + assert_called_with(generator, :run, ['rake log:clear RAILS_ENV=production', verbose: false]) do + with_rails_env "staging" do + action :rake, 'log:clear', env: 'production' + end + end + end + + def test_rake_with_sudo_option_should_run_rake_command_with_sudo + assert_called_with(generator, :run, ["sudo rake log:clear RAILS_ENV=development", verbose: false]) do + with_rails_env nil do + action :rake, 'log:clear', sudo: true + end + end + end + + def test_capify_should_run_the_capify_command + assert_called_with(generator, :run, ['capify .', verbose: false]) do + action :capify! + end + end + + def test_route_should_add_data_to_the_routes_block_in_config_routes + run_generator + route_command = "route '/login', controller: 'sessions', action: 'new'" + action :route, route_command + assert_file 'config/routes.rb', /#{Regexp.escape(route_command)}/ + end + + def test_route_should_be_idempotent + run_generator + route_path = File.expand_path('config/routes.rb', destination_root) + + # runs first time, not asserting + action :route, "root 'welcome#index'" + content_1 = File.read(route_path) + + # runs second time + action :route, "root 'welcome#index'" + content_2 = File.read(route_path) + + assert_equal content_1, content_2 + end + + def test_route_should_add_data_with_an_new_line + run_generator + action :route, "root 'welcome#index'" + route_path = File.expand_path("config/routes.rb", destination_root) + content = File.read(route_path) + + # Remove all of the comments and blank lines from the routes file + content.gsub!(/^ \#.*\n/, '') + content.gsub!(/^\n/, '') + + File.open(route_path, "wb") { |file| file.write(content) } + assert_file "config/routes.rb", /\.routes\.draw do\n root 'welcome#index'\nend\n\z/ + + action :route, "resources :product_lines" + + routes = <<-F +Rails.application.routes.draw do + resources :product_lines + root 'welcome#index' +end +F + assert_file "config/routes.rb", routes + end + + def test_readme + run_generator + assert_called(Rails::Generators::AppGenerator, :source_root, times: 2, returns: destination_root) do + assert_match "application up and running", action(:readme, "README.md") + end + end + + def test_readme_with_quiet + generator(default_arguments, quiet: true) + run_generator + assert_called(Rails::Generators::AppGenerator, :source_root, times: 2, returns: destination_root) do + assert_no_match "application up and running", action(:readme, "README.md") + end + end + + def test_log + assert_equal("YES\n", action(:log, "YES")) + end + + def test_log_with_status + assert_equal(" yes YES\n", action(:log, :yes, "YES")) + end + + def test_log_with_quiet + generator(default_arguments, quiet: true) + assert_equal("", action(:log, "YES")) + end + + def test_log_with_status_with_quiet + generator(default_arguments, quiet: true) + assert_equal("", action(:log, :yes, "YES")) + end + + protected + + def action(*args, &block) + capture(:stdout){ generator.send(*args, &block) } + end + +end 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..be2cd3a853 --- /dev/null +++ b/railties/test/generators/api_app_generator_test.rb @@ -0,0 +1,98 @@ +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_no_match(/gem 'web-console'/, 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 + config/initializers/request_forgery_protection.rb + lib/assets + vendor/assets + test/helpers + tmp/cache/assets) + end +end diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb new file mode 100644 index 0000000000..dd2e931c5c --- /dev/null +++ b/railties/test/generators/app_generator_test.rb @@ -0,0 +1,696 @@ +require 'generators/generators_test_helper' +require 'rails/generators/rails/app/app_generator' +require 'generators/shared_generator_tests' + +DEFAULT_APP_FILES = %w( + .gitignore + README.md + Gemfile + Rakefile + config.ru + app/assets/javascripts + app/assets/stylesheets + app/assets/images + app/controllers + app/controllers/concerns + app/helpers + app/mailers + app/models + app/models/concerns + app/jobs + app/views/layouts + bin/bundle + bin/rails + bin/rake + bin/setup + config/environments + config/initializers + config/locales + db + lib + lib/tasks + lib/assets + log + test/test_helper.rb + test/fixtures + test/fixtures/files + test/controllers + test/models + test/helpers + test/mailers + test/integration + vendor + vendor/assets + vendor/assets/stylesheets + vendor/assets/javascripts + tmp + tmp/cache + tmp/cache/assets +) + +class AppGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper + arguments [destination_root] + + # brings setup, teardown, and some tests + include SharedGeneratorTests + + def default_files + ::DEFAULT_APP_FILES + end + + def test_assets + run_generator + + assert_file("app/views/layouts/application.html.erb", /stylesheet_link_tag\s+'application', media: 'all', 'data-turbolinks-track' => true/) + assert_file("app/views/layouts/application.html.erb", /javascript_include_tag\s+'application', 'data-turbolinks-track' => true/) + assert_file("app/assets/stylesheets/application.css") + assert_file("app/assets/javascripts/application.js") + end + + def test_application_job_file_present + run_generator + assert_file("app/jobs/application_job.rb") + end + + def test_invalid_application_name_raises_an_error + content = capture(:stderr){ run_generator [File.join(destination_root, "43-things")] } + assert_equal "Invalid application name 43-things. Please give a name which does not start with numbers.\n", content + end + + def test_invalid_application_name_is_fixed + run_generator [File.join(destination_root, "things-43")] + assert_file "things-43/config/environment.rb", /Rails\.application\.initialize!/ + assert_file "things-43/config/application.rb", /^module Things43$/ + end + + def test_application_new_exits_with_non_zero_code_on_invalid_application_name + quietly { system 'rails new test --no-rc' } + assert_equal false, $?.success? + end + + def test_application_new_exits_with_message_and_non_zero_code_when_generating_inside_existing_rails_directory + app_root = File.join(destination_root, 'myfirstapp') + run_generator [app_root] + output = nil + Dir.chdir(app_root) do + output = `rails new mysecondapp` + end + assert_equal "Can't initialize a new Rails application within the directory of another, please change to a non-Rails directory first.\nType 'rails' for help.\n", output + assert_equal false, $?.success? + end + + def test_application_new_show_help_message_inside_existing_rails_directory + app_root = File.join(destination_root, 'myfirstapp') + run_generator [app_root] + output = Dir.chdir(app_root) do + `rails new --help` + end + assert_match(/rails new APP_PATH \[options\]/, output) + assert_equal true, $?.success? + end + + def test_application_name_is_detected_if_it_exists_and_app_folder_renamed + app_root = File.join(destination_root, "myapp") + app_moved_root = File.join(destination_root, "myapp_moved") + + run_generator [app_root] + + stub_rails_application(app_moved_root) do + Rails.application.stub(:is_a?, -> *args { Rails::Application }) do + FileUtils.mv(app_root, app_moved_root) + + # make sure we are in correct dir + FileUtils.cd(app_moved_root) + + generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, + destination_root: app_moved_root, shell: @shell + generator.send(:app_const) + quietly { generator.send(:update_config_files) } + assert_file "myapp_moved/config/environment.rb", /Rails\.application\.initialize!/ + assert_file "myapp_moved/config/initializers/session_store.rb", /_myapp_session/ + end + end + end + + def test_rails_update_generates_correct_session_key + app_root = File.join(destination_root, 'myapp') + run_generator [app_root] + + stub_rails_application(app_root) do + generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell + generator.send(:app_const) + quietly { generator.send(:update_config_files) } + assert_file "myapp/config/initializers/session_store.rb", /_myapp_session/ + end + end + + def test_new_application_use_json_serialzier + run_generator + + assert_file("config/initializers/cookies_serializer.rb", /Rails\.application\.config\.action_dispatch\.cookies_serializer = :json/) + end + + def test_rails_update_keep_the_cookie_serializer_if_it_is_already_configured + app_root = File.join(destination_root, 'myapp') + run_generator [app_root] + + stub_rails_application(app_root) do + generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell + generator.send(:app_const) + quietly { generator.send(:update_config_files) } + assert_file("#{app_root}/config/initializers/cookies_serializer.rb", /Rails\.application\.config\.action_dispatch\.cookies_serializer = :json/) + end + end + + def test_rails_update_does_not_create_callback_terminator_initializer + app_root = File.join(destination_root, 'myapp') + run_generator [app_root] + + FileUtils.rm("#{app_root}/config/initializers/callback_terminator.rb") + + stub_rails_application(app_root) do + generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell + generator.send(:app_const) + quietly { generator.send(:update_config_files) } + assert_no_file "#{app_root}/config/initializers/callback_terminator.rb" + end + end + + def test_rails_update_does_not_remove_callback_terminator_initializer_if_already_present + app_root = File.join(destination_root, 'myapp') + run_generator [app_root] + + FileUtils.touch("#{app_root}/config/initializers/callback_terminator.rb") + + stub_rails_application(app_root) do + generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell + generator.send(:app_const) + quietly { generator.send(:update_config_files) } + assert_file "#{app_root}/config/initializers/callback_terminator.rb" + end + end + + def test_rails_update_set_the_cookie_serializer_to_marchal_if_it_is_not_already_configured + app_root = File.join(destination_root, 'myapp') + run_generator [app_root] + + FileUtils.rm("#{app_root}/config/initializers/cookies_serializer.rb") + + stub_rails_application(app_root) do + generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell + generator.send(:app_const) + quietly { generator.send(:update_config_files) } + assert_file("#{app_root}/config/initializers/cookies_serializer.rb", /Rails\.application\.config\.action_dispatch\.cookies_serializer = :marshal/) + end + end + + def test_rails_update_does_not_create_active_record_belongs_to_required_by_default + app_root = File.join(destination_root, 'myapp') + run_generator [app_root] + + FileUtils.rm("#{app_root}/config/initializers/active_record_belongs_to_required_by_default.rb") + + stub_rails_application(app_root) do + generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell + generator.send(:app_const) + quietly { generator.send(:update_config_files) } + assert_no_file "#{app_root}/config/initializers/active_record_belongs_to_required_by_default.rb" + end + end + + def test_rails_update_does_not_remove_active_record_belongs_to_required_by_default_if_already_present + app_root = File.join(destination_root, 'myapp') + run_generator [app_root] + + FileUtils.touch("#{app_root}/config/initializers/active_record_belongs_to_required_by_default.rb") + + stub_rails_application(app_root) do + generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell + generator.send(:app_const) + quietly { generator.send(:update_config_files) } + assert_file "#{app_root}/config/initializers/active_record_belongs_to_required_by_default.rb" + end + end + + def test_application_names_are_not_singularized + run_generator [File.join(destination_root, "hats")] + assert_file "hats/config/environment.rb", /Rails\.application\.initialize!/ + end + + def test_gemfile_has_no_whitespace_errors + run_generator + absolute = File.expand_path("Gemfile", destination_root) + File.open(absolute, 'r') do |f| + f.each_line do |line| + assert_no_match %r{/^[ \t]+$/}, line + end + end + end + + def test_config_database_is_added_by_default + run_generator + assert_file "config/database.yml", /sqlite3/ + if defined?(JRUBY_VERSION) + assert_gem "activerecord-jdbcsqlite3-adapter" + else + assert_gem "sqlite3" + end + end + + def test_config_another_database + run_generator([destination_root, "-d", "mysql"]) + assert_file "config/database.yml", /mysql/ + if defined?(JRUBY_VERSION) + assert_gem "activerecord-jdbcmysql-adapter" + else + assert_gem "mysql2", "'>= 0.3.18', '< 0.5'" + end + end + + def test_config_database_app_name_with_period + run_generator [File.join(destination_root, "common.usage.com"), "-d", "postgresql"] + assert_file "common.usage.com/config/database.yml", /common_usage_com/ + end + + def test_config_postgresql_database + run_generator([destination_root, "-d", "postgresql"]) + assert_file "config/database.yml", /postgresql/ + if defined?(JRUBY_VERSION) + assert_gem "activerecord-jdbcpostgresql-adapter" + else + assert_gem "pg", "'~> 0.18'" + end + end + + def test_config_jdbcmysql_database + run_generator([destination_root, "-d", "jdbcmysql"]) + assert_file "config/database.yml", /mysql/ + assert_gem "activerecord-jdbcmysql-adapter" + end + + def test_config_jdbcsqlite3_database + run_generator([destination_root, "-d", "jdbcsqlite3"]) + assert_file "config/database.yml", /sqlite3/ + assert_gem "activerecord-jdbcsqlite3-adapter" + end + + def test_config_jdbcpostgresql_database + run_generator([destination_root, "-d", "jdbcpostgresql"]) + assert_file "config/database.yml", /postgresql/ + assert_gem "activerecord-jdbcpostgresql-adapter" + end + + def test_config_jdbc_database + run_generator([destination_root, "-d", "jdbc"]) + assert_file "config/database.yml", /jdbc/ + assert_file "config/database.yml", /mssql/ + assert_gem "activerecord-jdbc-adapter" + end + + if defined?(JRUBY_VERSION) + def test_config_jdbc_database_when_no_option_given + run_generator + assert_file "config/database.yml", /sqlite3/ + assert_gem "activerecord-jdbcsqlite3-adapter" + end + end + + def test_generator_without_skips + run_generator + assert_file "config/application.rb", /\s+require\s+["']rails\/all["']/ + assert_file "config/environments/development.rb" do |content| + assert_match(/config\.action_mailer\.raise_delivery_errors = false/, content) + end + assert_file "config/environments/test.rb" do |content| + assert_match(/config\.action_mailer\.delivery_method = :test/, content) + end + assert_file "config/environments/production.rb" do |content| + assert_match(/# config\.action_mailer\.raise_delivery_errors = false/, content) + end + end + + def test_generator_if_skip_active_record_is_given + run_generator [destination_root, "--skip-active-record"] + assert_no_file "config/database.yml" + assert_no_file "config/initializers/active_record_belongs_to_required_by_default.rb" + assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/ + assert_file "test/test_helper.rb" do |helper_content| + assert_no_match(/fixtures :all/, helper_content) + end + end + + def test_generator_if_skip_action_mailer_is_given + run_generator [destination_root, "--skip-action-mailer"] + assert_file "config/application.rb", /#\s+require\s+["']action_mailer\/railtie["']/ + assert_file "config/environments/development.rb" do |content| + assert_no_match(/config\.action_mailer/, content) + end + assert_file "config/environments/test.rb" do |content| + assert_no_match(/config\.action_mailer/, content) + end + assert_file "config/environments/production.rb" do |content| + assert_no_match(/config\.action_mailer/, content) + end + end + + def test_generator_if_skip_sprockets_is_given + run_generator [destination_root, "--skip-sprockets"] + assert_no_file "config/initializers/assets.rb" + assert_file "config/application.rb" do |content| + assert_match(/#\s+require\s+["']sprockets\/railtie["']/, content) + end + assert_file "Gemfile" do |content| + assert_no_match(/sass-rails/, content) + assert_no_match(/uglifier/, content) + assert_match(/coffee-rails/, content) + end + assert_file "config/environments/development.rb" do |content| + assert_no_match(/config\.assets\.debug = true/, content) + end + assert_file "config/environments/production.rb" do |content| + assert_no_match(/config\.assets\.digest = true/, content) + assert_no_match(/config\.assets\.js_compressor = :uglifier/, content) + assert_no_match(/config\.assets\.css_compressor = :sass/, content) + end + end + + def test_inclusion_of_javascript_runtime + run_generator + if defined?(JRUBY_VERSION) + assert_gem "therubyrhino" + else + assert_file "Gemfile", /# gem 'therubyracer', platforms: :ruby/ + end + end + + def test_jquery_is_the_default_javascript_library + run_generator + assert_file "app/assets/javascripts/application.js" do |contents| + assert_match %r{^//= require jquery}, contents + assert_match %r{^//= require jquery_ujs}, contents + end + assert_gem "jquery-rails" + end + + def test_other_javascript_libraries + run_generator [destination_root, '-j', 'prototype'] + assert_file "app/assets/javascripts/application.js" do |contents| + assert_match %r{^//= require prototype}, contents + assert_match %r{^//= require prototype_ujs}, contents + end + assert_gem "prototype-rails" + end + + def test_javascript_is_skipped_if_required + run_generator [destination_root, "--skip-javascript"] + + assert_no_file "app/assets/javascripts" + assert_no_file "vendor/assets/javascripts" + + assert_file "app/views/layouts/application.html.erb" do |contents| + assert_match(/stylesheet_link_tag\s+'application', media: 'all' %>/, contents) + assert_no_match(/javascript_include_tag\s+'application' \%>/, contents) + end + + assert_file "Gemfile" do |content| + assert_no_match(/coffee-rails/, content) + assert_no_match(/jquery-rails/, content) + end + end + + def test_inclusion_of_jbuilder + run_generator + assert_gem 'jbuilder' + end + + def test_inclusion_of_a_debugger + run_generator + if defined?(JRUBY_VERSION) || RUBY_ENGINE == "rbx" + assert_file "Gemfile" do |content| + assert_no_match(/byebug/, content) + end + else + assert_gem 'byebug' + end + end + + def test_template_from_dir_pwd + FileUtils.cd(Rails.root) + assert_match(/It works from file!/, run_generator([destination_root, "-m", "lib/template.rb"])) + end + + def test_usage_read_from_file + assert_called(File, :read, returns: "USAGE FROM FILE") do + assert_equal "USAGE FROM FILE", Rails::Generators::AppGenerator.desc + end + end + + def test_default_usage + assert_called(Rails::Generators::AppGenerator, :usage_path, returns: nil) do + assert_match(/Create rails files for app generator/, Rails::Generators::AppGenerator.desc) + end + end + + def test_default_namespace + assert_match "rails:app", Rails::Generators::AppGenerator.namespace + end + + def test_file_is_added_for_backwards_compatibility + action :file, 'lib/test_file.rb', 'heres test data' + assert_file 'lib/test_file.rb', 'heres test data' + end + + def test_tests_are_removed_from_frameworks_if_skip_test_is_given + run_generator [destination_root, "--skip-test"] + assert_file "config/application.rb", /#\s+require\s+["']rails\/test_unit\/railtie["']/ + end + + def test_no_active_record_or_tests_if_skips_given + run_generator [destination_root, "--skip-test", "--skip-active-record"] + assert_file "config/application.rb", /#\s+require\s+["']rails\/test_unit\/railtie["']/ + assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/ + assert_file "config/application.rb", /\s+require\s+["']active_job\/railtie["']/ + end + + def test_new_hash_style + run_generator + assert_file "config/initializers/session_store.rb" do |file| + assert_match(/config.session_store :cookie_store, key: '_.+_session'/, file) + end + end + + def test_pretend_option + output = run_generator [File.join(destination_root, "myapp"), "--pretend"] + assert_no_match(/run bundle install/, output) + end + + def test_application_name_with_spaces + path = File.join(destination_root, "foo bar") + + # This also applies to MySQL apps but not with SQLite + run_generator [path, "-d", 'postgresql'] + + assert_file "foo bar/config/database.yml", /database: foo_bar_development/ + assert_file "foo bar/config/initializers/session_store.rb", /key: '_foo_bar/ + end + + def test_web_console + run_generator + assert_gem 'web-console' + end + + def test_web_console_with_dev_option + run_generator [destination_root, "--dev"] + + assert_file "Gemfile" do |content| + assert_match(/gem 'web-console',\s+github: 'rails\/web-console'/, content) + assert_no_match(/gem 'web-console', '~> 3.0'/, content) + end + end + + def test_web_console_with_edge_option + run_generator [destination_root, "--edge"] + + assert_file "Gemfile" do |content| + assert_match(/gem 'web-console',\s+github: 'rails\/web-console'/, content) + assert_no_match(/gem 'web-console', '~> 3.0'/, content) + end + end + + def test_spring + run_generator + assert_gem 'spring' + end + + def test_spring_binstubs + jruby_skip "spring doesn't run on JRuby" + command_check = -> command do + @binstub_called ||= 0 + + case command + when 'install' + # Called when running bundle, we just want to stub it so nothing to do here. + when 'exec spring binstub --all' + @binstub_called += 1 + assert_equal 1, @binstub_called, "exec spring binstub --all expected to be called once, but was called #{@install_called} times." + end + end + + generator.stub :bundle_command, command_check do + quietly { generator.invoke_all } + end + end + + def test_spring_no_fork + jruby_skip "spring doesn't run on JRuby" + assert_called_with(Process, :respond_to?, [:fork], returns: false) do + run_generator + + assert_file "Gemfile" do |content| + assert_no_match(/spring/, content) + end + end + end + + def test_skip_spring + run_generator [destination_root, "--skip-spring"] + + assert_file "Gemfile" do |content| + assert_no_match(/spring/, content) + end + end + + def test_spring_with_dev_option + run_generator [destination_root, "--dev"] + + assert_file "Gemfile" do |content| + assert_no_match(/spring/, content) + end + end + + def test_generator_if_skip_turbolinks_is_given + run_generator [destination_root, "--skip-turbolinks"] + + assert_file "Gemfile" do |content| + assert_no_match(/turbolinks/, content) + end + assert_file "app/views/layouts/application.html.erb" do |content| + assert_no_match(/data-turbolinks-track/, content) + end + assert_file "app/assets/javascripts/application.js" do |content| + assert_no_match(/turbolinks/, content) + end + end + + def test_gitignore_when_sqlite3 + run_generator + + assert_file '.gitignore' do |content| + assert_match(/sqlite3/, content) + end + end + + def test_gitignore_when_no_active_record + run_generator [destination_root, '--skip-active-record'] + + assert_file '.gitignore' do |content| + assert_no_match(/sqlite/i, content) + end + end + + def test_gitignore_when_non_sqlite3_db + run_generator([destination_root, "-d", "mysql"]) + + assert_file '.gitignore' do |content| + assert_no_match(/sqlite/i, content) + end + end + + def test_create_keeps + run_generator + folders_with_keep = %w( + app/assets/images + app/mailers + app/models + app/controllers/concerns + app/models/concerns + lib/tasks + lib/assets + log + test/fixtures + test/fixtures/files + test/controllers + test/mailers + test/models + test/helpers + test/integration + tmp + vendor/assets/stylesheets + ) + folders_with_keep.each do |folder| + assert_file("#{folder}/.keep") + end + end + + def test_psych_gem + run_generator + gem_regex = /gem 'psych',\s+'~> 2.0',\s+platforms: :rbx/ + + assert_file "Gemfile" do |content| + if defined?(Rubinius) + assert_match(gem_regex, content) + else + assert_no_match(gem_regex, content) + end + end + end + + def test_after_bundle_callback + path = 'http://example.org/rails_template' + template = %{ after_bundle { run 'echo ran after_bundle' } } + template.instance_eval "def read; self; end" # Make the string respond to read + + check_open = -> *args do + assert_equal [ path, 'Accept' => 'application/x-thor-template' ], args + template + end + + sequence = ['install', 'exec spring binstub --all', 'echo ran after_bundle'] + ensure_bundler_first = -> command do + @sequence_step ||= 0 + + assert_equal sequence[@sequence_step], command, "commands should be called in sequence #{sequence}" + @sequence_step += 1 + end + + generator([destination_root], template: path).stub(:open, check_open, template) do + generator.stub(:bundle_command, ensure_bundler_first) do + generator.stub(:run, ensure_bundler_first) do + quietly { generator.invoke_all } + end + end + end + end + + protected + + def stub_rails_application(root) + Rails.application.config.root = root + Rails.application.class.stub(:name, "Myapp") do + yield + end + end + + def action(*args, &block) + capture(:stdout) { generator.send(*args, &block) } + end + + def assert_gem(gem, constraint = nil) + if constraint + assert_file "Gemfile", /^\s*gem\s+["']#{gem}["'], #{constraint}$*/ + else + assert_file "Gemfile", /^\s*gem\s+["']#{gem}["']$*/ + end + end +end diff --git a/railties/test/generators/argv_scrubber_test.rb b/railties/test/generators/argv_scrubber_test.rb new file mode 100644 index 0000000000..31e07bc8da --- /dev/null +++ b/railties/test/generators/argv_scrubber_test.rb @@ -0,0 +1,136 @@ +require 'active_support/test_case' +require 'active_support/testing/autorun' +require 'rails/generators/rails/app/app_generator' +require 'tempfile' + +module Rails + module Generators + class ARGVScrubberTest < ActiveSupport::TestCase # :nodoc: + # Future people who read this... These tests are just to surround the + # current behavior of the ARGVScrubber, they do not mean that the class + # *must* act this way, I just want to prevent regressions. + + def test_version + ['-v', '--version'].each do |str| + scrubber = ARGVScrubber.new [str] + output = nil + exit_code = nil + scrubber.extend(Module.new { + define_method(:puts) { |string| output = string } + define_method(:exit) { |code| exit_code = code } + }) + scrubber.prepare! + assert_equal "Rails #{Rails::VERSION::STRING}", output + assert_equal 0, exit_code + end + end + + def test_default_help + argv = ['zomg', 'how', 'are', 'you'] + scrubber = ARGVScrubber.new argv + args = scrubber.prepare! + assert_equal ['--help'] + argv.drop(1), args + end + + def test_prepare_returns_args + scrubber = ARGVScrubber.new ['hi mom'] + args = scrubber.prepare! + assert_equal '--help', args.first + end + + def test_no_mutations + scrubber = ARGVScrubber.new ['hi mom'].freeze + args = scrubber.prepare! + assert_equal '--help', args.first + end + + def test_new_command_no_rc + scrubber = Class.new(ARGVScrubber) { + def self.default_rc_file + File.join(Dir.tmpdir, 'whatever') + end + }.new ['new'] + args = scrubber.prepare! + assert_equal [], args + end + + def test_new_homedir_rc + file = Tempfile.new 'myrcfile' + file.puts '--hello-world' + file.flush + + message = nil + scrubber = Class.new(ARGVScrubber) { + define_singleton_method(:default_rc_file) do + file.path + end + define_method(:puts) { |msg| message = msg } + }.new ['new'] + args = scrubber.prepare! + assert_equal ['--hello-world'], args + assert_match 'hello-world', message + assert_match file.path, message + ensure + file.close + file.unlink + end + + def test_rc_whitespace_separated + file = Tempfile.new 'myrcfile' + file.puts '--hello --world' + file.flush + + message = nil + scrubber = Class.new(ARGVScrubber) { + define_method(:puts) { |msg| message = msg } + }.new ['new', "--rc=#{file.path}"] + args = scrubber.prepare! + assert_equal ['--hello', '--world'], args + ensure + file.close + file.unlink + end + + def test_new_rc_option + file = Tempfile.new 'myrcfile' + file.puts '--hello-world' + file.flush + + message = nil + scrubber = Class.new(ARGVScrubber) { + define_method(:puts) { |msg| message = msg } + }.new ['new', "--rc=#{file.path}"] + args = scrubber.prepare! + assert_equal ['--hello-world'], args + assert_match 'hello-world', message + assert_match file.path, message + ensure + file.close + file.unlink + end + + def test_new_rc_option_and_custom_options + file = Tempfile.new 'myrcfile' + file.puts '--hello' + file.puts '--world' + file.flush + + scrubber = Class.new(ARGVScrubber) { + define_method(:puts) { |msg| } + }.new ['new', 'tenderapp', '--love', "--rc=#{file.path}"] + + args = scrubber.prepare! + assert_equal ["tenderapp", "--hello", "--world", "--love"], args + ensure + file.close + file.unlink + end + + def test_no_rc + scrubber = ARGVScrubber.new ['new', '--no-rc'] + args = scrubber.prepare! + assert_equal [], args + end + end + end +end diff --git a/railties/test/generators/assets_generator_test.rb b/railties/test/generators/assets_generator_test.rb new file mode 100644 index 0000000000..a2b94f2e50 --- /dev/null +++ b/railties/test/generators/assets_generator_test.rb @@ -0,0 +1,19 @@ +require 'generators/generators_test_helper' +require 'rails/generators/rails/assets/assets_generator' + +class AssetsGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper + arguments %w(posts) + + def test_assets + run_generator + assert_file "app/assets/javascripts/posts.js" + assert_file "app/assets/stylesheets/posts.css" + end + + def test_skipping_assets + run_generator ["posts", "--no-stylesheets", "--no-javascripts"] + assert_no_file "app/assets/javascripts/posts.js" + assert_no_file "app/assets/stylesheets/posts.css" + end +end diff --git a/railties/test/generators/controller_generator_test.rb b/railties/test/generators/controller_generator_test.rb new file mode 100644 index 0000000000..1351151afb --- /dev/null +++ b/railties/test/generators/controller_generator_test.rb @@ -0,0 +1,103 @@ +require 'generators/generators_test_helper' +require 'rails/generators/rails/controller/controller_generator' + +class ControllerGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper + arguments %w(Account foo bar) + + setup :copy_routes + + def test_help_does_not_show_invoked_generators_options_if_they_already_exist + content = run_generator ["--help"] + assert_no_match(/Helper options\:/, content) + end + + def test_controller_skeleton_is_created + run_generator + assert_file "app/controllers/account_controller.rb", /class AccountController < ApplicationController/ + end + + def test_check_class_collision + Object.send :const_set, :ObjectController, Class.new + content = capture(:stderr){ run_generator ["object"] } + assert_match(/The name 'ObjectController' is either already used in your application or reserved/, content) + ensure + Object.send :remove_const, :ObjectController + end + + def test_invokes_helper + run_generator + assert_file "app/helpers/account_helper.rb" + end + + def test_does_not_invoke_helper_if_required + run_generator ["account", "--skip-helper"] + assert_no_file "app/helpers/account_helper.rb" + end + + def test_invokes_assets + run_generator + assert_file "app/assets/javascripts/account.js" + assert_file "app/assets/stylesheets/account.css" + end + + def test_does_not_invoke_assets_if_required + run_generator ["account", "--skip-assets"] + assert_no_file "app/assets/javascripts/account.js" + assert_no_file "app/assets/stylesheets/account.css" + end + + def test_invokes_default_test_framework + run_generator + assert_file "test/controllers/account_controller_test.rb" + end + + def test_does_not_invoke_test_framework_if_required + run_generator ["account", "--no-test-framework"] + assert_no_file "test/controllers/account_controller_test.rb" + end + + def test_invokes_default_template_engine + run_generator + assert_file "app/views/account/foo.html.erb", %r(app/views/account/foo\.html\.erb) + assert_file "app/views/account/bar.html.erb", %r(app/views/account/bar\.html\.erb) + end + + def test_add_routes + run_generator + assert_file "config/routes.rb", /get 'account\/foo'/, /get 'account\/bar'/ + end + + def test_skip_routes + run_generator ["account", "foo", "--skip-routes"] + assert_file "config/routes.rb" do |routes| + assert_no_match(/get 'account\/foo'/, routes) + end + end + + def test_invokes_default_template_engine_even_with_no_action + run_generator ["account"] + assert_file "app/views/account" + end + + def test_template_engine_with_class_path + run_generator ["admin/account"] + assert_file "app/views/admin/account" + end + + def test_actions_are_turned_into_methods + run_generator + + assert_file "app/controllers/account_controller.rb" do |controller| + assert_instance_method :foo, controller + assert_instance_method :bar, controller + end + end + + def test_namespaced_routes_are_created_in_routes + run_generator ["admin/dashboard", "index"] + assert_file "config/routes.rb" do |route| + assert_match(/^ namespace :admin do\n get 'dashboard\/index'\n end$/, route) + end + end +end diff --git a/railties/test/generators/create_migration_test.rb b/railties/test/generators/create_migration_test.rb new file mode 100644 index 0000000000..e16a77479a --- /dev/null +++ b/railties/test/generators/create_migration_test.rb @@ -0,0 +1,134 @@ +require 'generators/generators_test_helper' +require 'rails/generators/rails/migration/migration_generator' + +class CreateMigrationTest < Rails::Generators::TestCase + include GeneratorsTestHelper + + class Migrator < Rails::Generators::MigrationGenerator + include Rails::Generators::Migration + + def self.next_migration_number(dirname) + current_migration_number(dirname) + 1 + end + end + + tests Migrator + + def default_destination_path + "db/migrate/create_articles.rb" + end + + def create_migration(destination_path = default_destination_path, config = {}, generator_options = {}, &block) + migration_name = File.basename(destination_path, '.rb') + generator([migration_name], generator_options) + generator.set_migration_assigns!(destination_path) + + dir, base = File.split(destination_path) + timestamped_destination_path = File.join(dir, ["%migration_number%", base].join('_')) + + @migration = Rails::Generators::Actions::CreateMigration.new(generator, timestamped_destination_path, block || "contents", config) + end + + def migration_exists!(*args) + @existing_migration = create_migration(*args) + invoke! + @generator = nil + end + + def invoke! + capture(:stdout) { @migration.invoke! } + end + + def revoke! + capture(:stdout) { @migration.revoke! } + end + + def test_invoke + create_migration + + assert_match(/create db\/migrate\/1_create_articles.rb\n/, invoke!) + assert_file @migration.destination + end + + def test_invoke_pretended + create_migration(default_destination_path, {}, { pretend: true }) + + assert_no_file @migration.destination + end + + def test_invoke_when_exists + migration_exists! + create_migration + + assert_equal @existing_migration.destination, @migration.existing_migration + end + + def test_invoke_when_exists_identical + migration_exists! + create_migration + + assert_match(/identical db\/migrate\/1_create_articles.rb\n/, invoke!) + assert @migration.identical? + end + + def test_invoke_when_exists_not_identical + migration_exists! + create_migration { "different content" } + + assert_raise(Rails::Generators::Error) { invoke! } + end + + def test_invoke_forced_when_exists_not_identical + dest = "db/migrate/migration.rb" + migration_exists!(dest) + create_migration(dest, force: true) { "different content" } + + stdout = invoke! + assert_match(/remove db\/migrate\/1_migration.rb\n/, stdout) + assert_match(/create db\/migrate\/2_migration.rb\n/, stdout) + assert_file @migration.destination + assert_no_file @existing_migration.destination + end + + def test_invoke_forced_pretended_when_exists_not_identical + migration_exists! + create_migration(default_destination_path, { force: true }, { pretend: true }) do + "different content" + end + + stdout = invoke! + assert_match(/remove db\/migrate\/1_create_articles.rb\n/, stdout) + assert_match(/create db\/migrate\/2_create_articles.rb\n/, stdout) + assert_no_file @migration.destination + end + + def test_invoke_skipped_when_exists_not_identical + migration_exists! + create_migration(default_destination_path, {}, { skip: true }) { "different content" } + + assert_match(/skip db\/migrate\/2_create_articles.rb\n/, invoke!) + assert_no_file @migration.destination + end + + def test_revoke + migration_exists! + create_migration + + assert_match(/remove db\/migrate\/1_create_articles.rb\n/, revoke!) + assert_no_file @existing_migration.destination + end + + def test_revoke_pretended + migration_exists! + create_migration(default_destination_path, {}, { pretend: true }) + + assert_match(/remove db\/migrate\/1_create_articles.rb\n/, revoke!) + assert_file @existing_migration.destination + end + + def test_revoke_when_no_exists + create_migration + + assert_match(/remove db\/migrate\/1_create_articles.rb\n/, revoke!) + end +end diff --git a/railties/test/generators/generated_attribute_test.rb b/railties/test/generators/generated_attribute_test.rb new file mode 100644 index 0000000000..ee7c009305 --- /dev/null +++ b/railties/test/generators/generated_attribute_test.rb @@ -0,0 +1,152 @@ +require 'generators/generators_test_helper' +require 'rails/generators/generated_attribute' + +class GeneratedAttributeTest < Rails::Generators::TestCase + include GeneratorsTestHelper + + def test_field_type_returns_number_field + assert_field_type :integer, :number_field + end + + def test_field_type_returns_text_field + %w(float decimal string).each do |attribute_type| + assert_field_type attribute_type, :text_field + end + end + + def test_field_type_returns_datetime_select + %w(datetime timestamp).each do |attribute_type| + assert_field_type attribute_type, :datetime_select + end + end + + def test_field_type_returns_time_select + assert_field_type :time, :time_select + end + + def test_field_type_returns_date_select + assert_field_type :date, :date_select + end + + def test_field_type_returns_text_area + assert_field_type :text, :text_area + end + + def test_field_type_returns_check_box + assert_field_type :boolean, :check_box + end + + def test_field_type_with_unknown_type_returns_text_field + %w(foo bar baz).each do |attribute_type| + assert_field_type attribute_type, :text_field + end + end + + def test_default_value_is_integer + assert_field_default_value :integer, 1 + end + + def test_default_value_is_float + assert_field_default_value :float, 1.5 + end + + def test_default_value_is_decimal + assert_field_default_value :decimal, '9.99' + end + + def test_default_value_is_datetime + %w(datetime timestamp time).each do |attribute_type| + assert_field_default_value attribute_type, Time.now.to_s(:db) + end + end + + def test_default_value_is_date + assert_field_default_value :date, Date.today.to_s(:db) + end + + def test_default_value_is_string + assert_field_default_value :string, 'MyString' + end + + def test_default_value_for_type + att = Rails::Generators::GeneratedAttribute.parse("type:string") + assert_equal("", att.default) + end + + def test_default_value_is_text + assert_field_default_value :text, 'MyText' + end + + def test_default_value_is_boolean + assert_field_default_value :boolean, false + end + + def test_default_value_is_nil + %w(references belongs_to).each do |attribute_type| + assert_field_default_value attribute_type, nil + end + end + + def test_default_value_is_empty_string + %w(foo bar baz).each do |attribute_type| + assert_field_default_value attribute_type, '' + end + end + + def test_human_name + assert_equal( + 'Full name', + create_generated_attribute(:string, 'full_name').human_name + ) + end + + def test_reference_is_true + %w(references belongs_to).each do |attribute_type| + assert create_generated_attribute(attribute_type).reference? + end + end + + def test_reference_is_false + %w(foo bar baz).each do |attribute_type| + assert !create_generated_attribute(attribute_type).reference? + end + end + + def test_polymorphic_reference_is_true + %w(references belongs_to).each do |attribute_type| + assert create_generated_attribute("#{attribute_type}{polymorphic}").polymorphic? + end + end + + def test_polymorphic_reference_is_false + %w(foo bar baz).each do |attribute_type| + assert !create_generated_attribute("#{attribute_type}{polymorphic}").polymorphic? + end + end + + def test_blank_type_defaults_to_string_raises_exception + assert_equal :string, create_generated_attribute(nil, 'title').type + assert_equal :string, create_generated_attribute("", 'title').type + end + + def test_handles_index_names_for_references + assert_equal "post", create_generated_attribute('string', 'post').index_name + assert_equal "post_id", create_generated_attribute('references', 'post').index_name + assert_equal "post_id", create_generated_attribute('belongs_to', 'post').index_name + assert_equal ["post_id", "post_type"], create_generated_attribute('references{polymorphic}', 'post').index_name + end + + def test_handles_column_names_for_references + assert_equal "post", create_generated_attribute('string', 'post').column_name + assert_equal "post_id", create_generated_attribute('references', 'post').column_name + assert_equal "post_id", create_generated_attribute('belongs_to', 'post').column_name + end + + def test_parse_required_attribute_with_index + att = Rails::Generators::GeneratedAttribute.parse("supplier:references{required}:index") + assert_equal "supplier", att.name + assert_equal :references, att.type + assert att.has_index? + assert att.required? + end +end diff --git a/railties/test/generators/generator_generator_test.rb b/railties/test/generators/generator_generator_test.rb new file mode 100644 index 0000000000..dcfeaaa8e0 --- /dev/null +++ b/railties/test/generators/generator_generator_test.rb @@ -0,0 +1,71 @@ +require 'generators/generators_test_helper' +require 'rails/generators/rails/generator/generator_generator' + +class GeneratorGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper + arguments %w(awesome) + + def test_generator_skeleton_is_created + run_generator + + %w( + lib/generators/awesome + lib/generators/awesome/USAGE + lib/generators/awesome/templates + ).each{ |path| assert_file path } + + assert_file "lib/generators/awesome/awesome_generator.rb", + /class AwesomeGenerator < Rails::Generators::NamedBase/ + assert_file "test/lib/generators/awesome_generator_test.rb", + /class AwesomeGeneratorTest < Rails::Generators::TestCase/, + /require 'generators\/awesome\/awesome_generator'/ + end + + def test_namespaced_generator_skeleton + run_generator ["rails/awesome"] + + %w( + lib/generators/rails/awesome + lib/generators/rails/awesome/USAGE + lib/generators/rails/awesome/templates + ).each{ |path| assert_file path } + + assert_file "lib/generators/rails/awesome/awesome_generator.rb", + /class Rails::AwesomeGenerator < Rails::Generators::NamedBase/ + assert_file "test/lib/generators/rails/awesome_generator_test.rb", + /class Rails::AwesomeGeneratorTest < Rails::Generators::TestCase/, + /require 'generators\/rails\/awesome\/awesome_generator'/ + end + + def test_generator_skeleton_is_created_without_file_name_namespace + run_generator ["awesome", "--namespace", "false"] + + %w( + lib/generators/ + lib/generators/USAGE + lib/generators/templates + ).each{ |path| assert_file path } + + assert_file "lib/generators/awesome_generator.rb", + /class AwesomeGenerator < Rails::Generators::NamedBase/ + assert_file "test/lib/generators/awesome_generator_test.rb", + /class AwesomeGeneratorTest < Rails::Generators::TestCase/, + /require 'generators\/awesome_generator'/ + end + + def test_namespaced_generator_skeleton_without_file_name_namespace + run_generator ["rails/awesome", "--namespace", "false"] + + %w( + lib/generators/rails + lib/generators/rails/USAGE + lib/generators/rails/templates + ).each{ |path| assert_file path } + + assert_file "lib/generators/rails/awesome_generator.rb", + /class Rails::AwesomeGenerator < Rails::Generators::NamedBase/ + assert_file "test/lib/generators/rails/awesome_generator_test.rb", + /class Rails::AwesomeGeneratorTest < Rails::Generators::TestCase/, + /require 'generators\/rails\/awesome_generator'/ + end +end diff --git a/railties/test/generators/generator_test.rb b/railties/test/generators/generator_test.rb new file mode 100644 index 0000000000..7871399dd7 --- /dev/null +++ b/railties/test/generators/generator_test.rb @@ -0,0 +1,85 @@ +require 'active_support/test_case' +require 'active_support/testing/autorun' +require 'rails/generators/app_base' + +module Rails + module Generators + class GeneratorTest < ActiveSupport::TestCase + def make_builder_class + Class.new(AppBase) do + add_shared_options_for "application" + + # include a module to get around thor's method_added hook + include(Module.new { + def gemfile_entries; super; end + def invoke_all; super; self; end + def add_gem_entry_filter; super; end + def gemfile_entry(*args); super; end + }) + end + end + + def test_construction + klass = make_builder_class + assert klass.start(['new', 'blah']) + end + + def test_add_gem + klass = make_builder_class + generator = klass.start(['new', 'blah']) + generator.gemfile_entry 'tenderlove' + assert_includes generator.gemfile_entries.map(&:name), 'tenderlove' + end + + def test_add_gem_with_version + klass = make_builder_class + generator = klass.start(['new', 'blah']) + generator.gemfile_entry 'tenderlove', '2.0.0' + assert generator.gemfile_entries.find { |gfe| + gfe.name == 'tenderlove' && gfe.version == '2.0.0' + } + end + + def test_add_github_gem + klass = make_builder_class + generator = klass.start(['new', 'blah']) + generator.gemfile_entry 'tenderlove', github: 'hello world' + assert generator.gemfile_entries.find { |gfe| + gfe.name == 'tenderlove' && gfe.options[:github] == 'hello world' + } + end + + def test_add_path_gem + klass = make_builder_class + generator = klass.start(['new', 'blah']) + generator.gemfile_entry 'tenderlove', path: 'hello world' + assert generator.gemfile_entries.find { |gfe| + gfe.name == 'tenderlove' && gfe.options[:path] == 'hello world' + } + end + + def test_filter + klass = make_builder_class + generator = klass.start(['new', 'blah']) + gems = generator.gemfile_entries + generator.add_gem_entry_filter { |gem| + gem.name != gems.first.name + } + assert_equal gems.drop(1), generator.gemfile_entries + end + + def test_two_filters + klass = make_builder_class + generator = klass.start(['new', 'blah']) + gems = generator.gemfile_entries + generator.add_gem_entry_filter { |gem| + gem.name != gems.first.name + } + generator.add_gem_entry_filter { |gem| + gem.name != gems[1].name + } + assert_equal gems.drop(2), generator.gemfile_entries + end + end + end +end diff --git a/railties/test/generators/generators_test_helper.rb b/railties/test/generators/generators_test_helper.rb new file mode 100644 index 0000000000..b19a5a7144 --- /dev/null +++ b/railties/test/generators/generators_test_helper.rb @@ -0,0 +1,50 @@ +require 'abstract_unit' +require 'active_support/core_ext/module/remove_method' +require 'active_support/testing/stream' +require 'active_support/testing/method_call_assertions' +require 'rails/generators' +require 'rails/generators/test_case' + +module Rails + class << self + remove_possible_method :root + def root + @root ||= Pathname.new(File.expand_path('../../fixtures', __FILE__)) + end + end +end +Rails.application.config.root = Rails.root +Rails.application.config.generators.templates = [File.join(Rails.root, "lib", "templates")] + +# Call configure to load the settings from +# Rails.application.config.generators to Rails::Generators +Rails.application.load_generators + +require 'active_record' +require 'action_dispatch' +require 'action_view' + +module GeneratorsTestHelper + include ActiveSupport::Testing::Stream + include ActiveSupport::Testing::MethodCallAssertions + + def self.included(base) + base.class_eval do + destination File.join(Rails.root, "tmp") + setup :prepare_destination + + begin + base.tests Rails::Generators.const_get(base.name.sub(/Test$/, '')) + rescue + end + end + end + + def copy_routes + routes = File.expand_path("../../../lib/rails/generators/rails/app/templates/config/routes.rb", __FILE__) + destination = File.join(destination_root, "config") + FileUtils.mkdir_p(destination) + FileUtils.cp routes, destination + end + +end diff --git a/railties/test/generators/helper_generator_test.rb b/railties/test/generators/helper_generator_test.rb new file mode 100644 index 0000000000..add04f21a4 --- /dev/null +++ b/railties/test/generators/helper_generator_test.rb @@ -0,0 +1,39 @@ +require 'generators/generators_test_helper' +require 'rails/generators/rails/helper/helper_generator' + +ObjectHelper = Class.new +AnotherObjectHelperTest = Class.new + +class HelperGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper + arguments %w(admin) + + def test_helper_skeleton_is_created + run_generator + assert_file "app/helpers/admin_helper.rb", /module AdminHelper/ + end + + def test_check_class_collision + content = capture(:stderr){ run_generator ["object"] } + assert_match(/The name 'ObjectHelper' is either already used in your application or reserved/, content) + end + + def test_namespaced_and_not_namespaced_helpers + run_generator ["products"] + + # We have to require the generated helper to show the problem because + # the test helpers just check for generated files and contents but + # do not actually load them. But they have to be loaded (as in a real environment) + # to make the second generator run fail + require "#{destination_root}/app/helpers/products_helper" + + assert_nothing_raised do + begin + run_generator ["admin::products"] + ensure + # cleanup + Object.send(:remove_const, :ProductsHelper) + end + end + end +end diff --git a/railties/test/generators/integration_test_generator_test.rb b/railties/test/generators/integration_test_generator_test.rb new file mode 100644 index 0000000000..d05ed76d24 --- /dev/null +++ b/railties/test/generators/integration_test_generator_test.rb @@ -0,0 +1,12 @@ +require 'generators/generators_test_helper' +require 'rails/generators/rails/integration_test/integration_test_generator' + +class IntegrationTestGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper + arguments %w(integration) + + def test_integration_test_skeleton_is_created + run_generator + assert_file "test/integration/integration_test.rb", /class IntegrationTest < ActionDispatch::IntegrationTest/ + end +end diff --git a/railties/test/generators/job_generator_test.rb b/railties/test/generators/job_generator_test.rb new file mode 100644 index 0000000000..7fd8f2062f --- /dev/null +++ b/railties/test/generators/job_generator_test.rb @@ -0,0 +1,29 @@ +require 'generators/generators_test_helper' +require 'rails/generators/job/job_generator' + +class JobGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper + + def test_job_skeleton_is_created + run_generator ["refresh_counters"] + assert_file "app/jobs/refresh_counters_job.rb" do |job| + assert_match(/class RefreshCountersJob < ApplicationJob/, job) + end + end + + def test_job_queue_param + run_generator ["refresh_counters", "--queue", "important"] + assert_file "app/jobs/refresh_counters_job.rb" do |job| + assert_match(/class RefreshCountersJob < ApplicationJob/, job) + assert_match(/queue_as :important/, job) + end + end + + def test_job_namespace + run_generator ["admin/refresh_counters", "--queue", "admin"] + assert_file "app/jobs/admin/refresh_counters_job.rb" do |job| + assert_match(/class Admin::RefreshCountersJob < ApplicationJob/, job) + assert_match(/queue_as :admin/, job) + end + end +end diff --git a/railties/test/generators/mailer_generator_test.rb b/railties/test/generators/mailer_generator_test.rb new file mode 100644 index 0000000000..f01e8cd2d9 --- /dev/null +++ b/railties/test/generators/mailer_generator_test.rb @@ -0,0 +1,186 @@ +require 'generators/generators_test_helper' +require 'rails/generators/mailer/mailer_generator' + +class MailerGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper + arguments %w(notifier foo bar) + + def test_mailer_skeleton_is_created + run_generator + assert_file "app/mailers/notifier_mailer.rb" do |mailer| + assert_match(/class NotifierMailer < ApplicationMailer/, mailer) + assert_no_match(/default from: "from@example.com"/, mailer) + assert_no_match(/layout :mailer_notifier/, mailer) + end + end + + def test_application_mailer_skeleton_is_created + run_generator + assert_file "app/mailers/application_mailer.rb" do |mailer| + assert_match(/class ApplicationMailer < ActionMailer::Base/, mailer) + assert_match(/default from: "from@example.com"/, mailer) + assert_match(/layout 'mailer'/, mailer) + end + end + + def test_mailer_with_i18n_helper + run_generator + assert_file "app/mailers/notifier_mailer.rb" do |mailer| + assert_match(/en\.notifier_mailer\.foo\.subject/, mailer) + assert_match(/en\.notifier_mailer\.bar\.subject/, mailer) + end + end + + def test_check_class_collision + Object.send :const_set, :NotifierMailer, Class.new + content = capture(:stderr){ run_generator } + assert_match(/The name 'NotifierMailer' is either already used in your application or reserved/, content) + ensure + Object.send :remove_const, :NotifierMailer + end + + def test_invokes_default_test_framework + run_generator + assert_file "test/mailers/notifier_mailer_test.rb" do |test| + assert_match(/class NotifierMailerTest < ActionMailer::TestCase/, test) + assert_match(/test "foo"/, test) + assert_match(/test "bar"/, test) + end + assert_file "test/mailers/previews/notifier_mailer_preview.rb" do |preview| + assert_match(/\# Preview all emails at http:\/\/localhost\:3000\/rails\/mailers\/notifier_mailer/, preview) + assert_match(/class NotifierMailerPreview < ActionMailer::Preview/, preview) + assert_match(/\# Preview this email at http:\/\/localhost\:3000\/rails\/mailers\/notifier_mailer\/foo/, preview) + assert_instance_method :foo, preview do |foo| + assert_match(/NotifierMailer.foo/, foo) + end + assert_match(/\# Preview this email at http:\/\/localhost\:3000\/rails\/mailers\/notifier_mailer\/bar/, preview) + assert_instance_method :bar, preview do |bar| + assert_match(/NotifierMailer.bar/, bar) + end + end + end + + def test_check_test_class_collision + Object.send :const_set, :NotifierMailerTest, Class.new + content = capture(:stderr){ run_generator } + assert_match(/The name 'NotifierMailerTest' is either already used in your application or reserved/, content) + ensure + Object.send :remove_const, :NotifierMailerTest + end + + def test_check_preview_class_collision + Object.send :const_set, :NotifierMailerPreview, Class.new + content = capture(:stderr){ run_generator } + assert_match(/The name 'NotifierMailerPreview' is either already used in your application or reserved/, content) + ensure + Object.send :remove_const, :NotifierMailerPreview + end + + def test_invokes_default_text_template_engine + run_generator + assert_file "app/views/notifier_mailer/foo.text.erb" do |view| + assert_match(%r(\sapp/views/notifier_mailer/foo\.text\.erb), view) + assert_match(/<%= @greeting %>/, view) + end + + assert_file "app/views/notifier_mailer/bar.text.erb" do |view| + assert_match(%r(\sapp/views/notifier_mailer/bar\.text\.erb), view) + assert_match(/<%= @greeting %>/, view) + end + + assert_file "app/views/layouts/mailer.text.erb" do |view| + assert_match(/<%= yield %>/, view) + end + end + + def test_invokes_default_html_template_engine + run_generator + assert_file "app/views/notifier_mailer/foo.html.erb" do |view| + assert_match(%r(\sapp/views/notifier_mailer/foo\.html\.erb), view) + assert_match(/<%= @greeting %>/, view) + end + + assert_file "app/views/notifier_mailer/bar.html.erb" do |view| + assert_match(%r(\sapp/views/notifier_mailer/bar\.html\.erb), view) + assert_match(/<%= @greeting %>/, view) + end + + assert_file "app/views/layouts/mailer.html.erb" do |view| + assert_match(%r{<html>\n <body>\n <%= yield %>\n </body>\n</html>}, view) + end + end + + def test_invokes_default_template_engine_even_with_no_action + run_generator ["notifier"] + assert_file "app/views/notifier_mailer" + assert_file "app/views/layouts/mailer.text.erb" + assert_file "app/views/layouts/mailer.html.erb" + end + + def test_logs_if_the_template_engine_cannot_be_found + content = run_generator ["notifier", "foo", "bar", "--template-engine=haml"] + assert_match(/haml \[not found\]/, content) + end + + def test_mailer_with_namedspaced_mailer + run_generator ["Farm::Animal", "moos"] + assert_file "app/mailers/farm/animal_mailer.rb" do |mailer| + assert_match(/class Farm::AnimalMailer < ApplicationMailer/, mailer) + assert_match(/en\.farm\.animal_mailer\.moos\.subject/, mailer) + end + assert_file "test/mailers/previews/farm/animal_mailer_preview.rb" do |preview| + assert_match(/\# Preview all emails at http:\/\/localhost\:3000\/rails\/mailers\/farm\/animal_mailer/, preview) + assert_match(/class Farm::AnimalMailerPreview < ActionMailer::Preview/, preview) + assert_match(/\# Preview this email at http:\/\/localhost\:3000\/rails\/mailers\/farm\/animal_mailer\/moos/, preview) + end + assert_file "app/views/farm/animal_mailer/moos.text.erb" + assert_file "app/views/farm/animal_mailer/moos.html.erb" + end + + def test_actions_are_turned_into_methods + run_generator + + assert_file "app/mailers/notifier_mailer.rb" do |mailer| + assert_instance_method :foo, mailer do |foo| + assert_match(/mail to: "to@example.org"/, foo) + assert_match(/@greeting = "Hi"/, foo) + end + + assert_instance_method :bar, mailer do |bar| + assert_match(/mail to: "to@example.org"/, bar) + assert_match(/@greeting = "Hi"/, bar) + end + end + end + + def test_mailer_on_revoke + run_generator + run_generator ["notifier"], behavior: :revoke + + assert_no_file "app/mailers/notifier.rb" + assert_no_file "app/views/notifier/foo.text.erb" + assert_no_file "app/views/notifier/bar.text.erb" + assert_no_file "app/views/notifier/foo.html.erb" + assert_no_file "app/views/notifier/bar.html.erb" + + assert_file "app/mailers/application_mailer.rb" + assert_file "app/views/layouts/mailer.text.erb" + assert_file "app/views/layouts/mailer.html.erb" + end + + def test_mailer_suffix_is_not_duplicated + run_generator ["notifier_mailer"] + + assert_no_file "app/mailers/notifier_mailer_mailer.rb" + assert_file "app/mailers/notifier_mailer.rb" + + assert_no_file "app/views/notifier_mailer_mailer/" + assert_file "app/views/notifier_mailer/" + + assert_no_file "test/mailers/notifier_mailer_mailer_test.rb" + assert_file "test/mailers/notifier_mailer_test.rb" + + assert_no_file "test/mailers/previews/notifier_mailer_mailer_preview.rb" + assert_file "test/mailers/previews/notifier_mailer_preview.rb" + end +end diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb new file mode 100644 index 0000000000..199743a396 --- /dev/null +++ b/railties/test/generators/migration_generator_test.rb @@ -0,0 +1,321 @@ +require 'generators/generators_test_helper' +require 'rails/generators/rails/migration/migration_generator' + +class MigrationGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper + + def test_migration + migration = "change_title_body_from_posts" + run_generator [migration] + assert_migration "db/migrate/#{migration}.rb", /class ChangeTitleBodyFromPosts < ActiveRecord::Migration/ + end + + def test_migrations_generated_simultaneously + migrations = ["change_title_body_from_posts", "change_email_from_comments"] + + first_migration_number, second_migration_number = migrations.collect do |migration| + run_generator [migration] + file_name = migration_file_name "db/migrate/#{migration}.rb" + + File.basename(file_name).split('_').first + end + + assert_not_equal first_migration_number, second_migration_number + end + + def test_migration_with_class_name + migration = "ChangeTitleBodyFromPosts" + run_generator [migration] + assert_migration "db/migrate/change_title_body_from_posts.rb", /class #{migration} < ActiveRecord::Migration/ + end + + def test_migration_with_invalid_file_name + migration = "add_something:datetime" + assert_raise ActiveRecord::IllegalMigrationNameError do + run_generator [migration] + end + end + + def test_add_migration_with_attributes + migration = "add_title_body_to_posts" + run_generator [migration, "title:string", "body:text"] + + assert_migration "db/migrate/#{migration}.rb" do |content| + assert_method :change, content do |change| + assert_match(/add_column :posts, :title, :string/, change) + assert_match(/add_column :posts, :body, :text/, change) + end + end + end + + def test_remove_migration_with_indexed_attribute + migration = "remove_title_body_from_posts" + run_generator [migration, "title:string:index", "body:text"] + + assert_migration "db/migrate/#{migration}.rb" do |content| + assert_method :change, content do |change| + assert_match(/remove_column :posts, :title, :string/, change) + assert_match(/remove_column :posts, :body, :text/, change) + assert_match(/remove_index :posts, :title/, change) + end + end + end + + def test_remove_migration_with_attributes + migration = "remove_title_body_from_posts" + run_generator [migration, "title:string", "body:text"] + + assert_migration "db/migrate/#{migration}.rb" do |content| + assert_method :change, content do |change| + assert_match(/remove_column :posts, :title, :string/, change) + assert_match(/remove_column :posts, :body, :text/, change) + end + end + end + + def test_remove_migration_with_references_options + migration = "remove_references_from_books" + run_generator [migration, "author:belongs_to", "distributor:references{polymorphic}"] + + assert_migration "db/migrate/#{migration}.rb" do |content| + assert_method :change, content do |change| + assert_match(/remove_reference :books, :author, index: true/, change) + assert_match(/remove_reference :books, :distributor, polymorphic: true, index: true/, change) + end + end + end + + def test_remove_migration_with_references_removes_foreign_keys + migration = "remove_references_from_books" + run_generator [migration, "author:belongs_to", "distributor:references{polymorphic}"] + + assert_migration "db/migrate/#{migration}.rb" do |content| + assert_method :change, content do |change| + assert_match(/remove_reference :books, :author,.*\sforeign_key: true/, change) + assert_match(/remove_reference :books, :distributor/, change) # sanity check + assert_no_match(/remove_reference :books, :distributor,.*\sforeign_key: true/, change) + end + end + end + + def test_add_migration_with_attributes_and_indices + migration = "add_title_with_index_and_body_to_posts" + run_generator [migration, "title:string:index", "body:text", "user_id:integer:uniq"] + + assert_migration "db/migrate/#{migration}.rb" do |content| + assert_method :change, content do |change| + assert_match(/add_column :posts, :title, :string/, change) + assert_match(/add_column :posts, :body, :text/, change) + assert_match(/add_column :posts, :user_id, :integer/, change) + assert_match(/add_index :posts, :title/, change) + assert_match(/add_index :posts, :user_id, unique: true/, change) + end + end + end + + def test_add_migration_with_attributes_and_wrong_index_declaration + migration = "add_title_and_content_to_books" + run_generator [migration, "title:string:inex", "content:text", "user_id:integer:unik"] + + assert_migration "db/migrate/#{migration}.rb" do |content| + assert_method :change, content do |change| + assert_match(/add_column :books, :title, :string/, change) + assert_match(/add_column :books, :content, :text/, change) + assert_match(/add_column :books, :user_id, :integer/, change) + end + assert_no_match(/add_index :books, :title/, content) + assert_no_match(/add_index :books, :user_id/, content) + end + end + + def test_add_migration_with_attributes_without_type_and_index + migration = "add_title_with_index_and_body_to_posts" + run_generator [migration, "title:index", "body:text", "user_uuid:uniq"] + + assert_migration "db/migrate/#{migration}.rb" do |content| + assert_method :change, content do |change| + assert_match(/add_column :posts, :title, :string/, change) + assert_match(/add_column :posts, :body, :text/, change) + assert_match(/add_column :posts, :user_uuid, :string/, change) + assert_match(/add_index :posts, :title/, change) + assert_match(/add_index :posts, :user_uuid, unique: true/, change) + end + end + end + + def test_add_migration_with_attributes_index_declaration_and_attribute_options + migration = "add_title_and_content_to_books" + run_generator [migration, "title:string{40}:index", "content:string{255}", "price:decimal{1,2}:index", "discount:decimal{3.4}:uniq"] + + assert_migration "db/migrate/#{migration}.rb" do |content| + assert_method :change, content do |change| + assert_match(/add_column :books, :title, :string, limit: 40/, change) + assert_match(/add_column :books, :content, :string, limit: 255/, change) + assert_match(/add_column :books, :price, :decimal, precision: 1, scale: 2/, change) + assert_match(/add_column :books, :discount, :decimal, precision: 3, scale: 4/, change) + end + assert_match(/add_index :books, :title/, content) + assert_match(/add_index :books, :price/, content) + assert_match(/add_index :books, :discount, unique: true/, content) + end + end + + def test_add_migration_with_references_options + migration = "add_references_to_books" + run_generator [migration, "author:belongs_to", "distributor:references{polymorphic}"] + + assert_migration "db/migrate/#{migration}.rb" do |content| + assert_method :change, content do |change| + assert_match(/add_reference :books, :author, index: true/, change) + assert_match(/add_reference :books, :distributor, polymorphic: true, index: true/, change) + end + end + end + + def test_add_migration_with_required_references + migration = "add_references_to_books" + run_generator [migration, "author:belongs_to{required}", "distributor:references{polymorphic,required}"] + + assert_migration "db/migrate/#{migration}.rb" do |content| + assert_method :change, content do |change| + assert_match(/add_reference :books, :author, index: true, null: false/, change) + assert_match(/add_reference :books, :distributor, polymorphic: true, index: true, null: false/, change) + end + end + end + + def test_add_migration_with_references_adds_foreign_keys + migration = "add_references_to_books" + run_generator [migration, "author:belongs_to", "distributor:references{polymorphic}"] + + assert_migration "db/migrate/#{migration}.rb" do |content| + assert_method :change, content do |change| + assert_match(/add_reference :books, :author,.*\sforeign_key: true/, change) + assert_match(/add_reference :books, :distributor/, change) # sanity check + assert_no_match(/add_reference :books, :distributor,.*\sforeign_key: true/, change) + end + end + end + + def test_create_join_table_migration + migration = "add_media_join_table" + run_generator [migration, "artist_id", "musics:uniq"] + + assert_migration "db/migrate/#{migration}.rb" do |content| + assert_method :change, content do |change| + assert_match(/create_join_table :artists, :musics/, change) + assert_match(/# t.index \[:artist_id, :music_id\]/, change) + assert_match(/ t.index \[:music_id, :artist_id\], unique: true/, change) + end + end + end + + def test_create_table_migration + run_generator ["create_books", "title:string", "content:text"] + assert_migration "db/migrate/create_books.rb" do |content| + assert_method :change, content do |change| + assert_match(/create_table :books/, change) + assert_match(/ t\.string :title/, change) + assert_match(/ t\.text :content/, change) + end + end + end + + def test_add_uuid_to_create_table_migration + run_generator ["create_books", "--primary_key_type=uuid"] + assert_migration "db/migrate/create_books.rb" do |content| + assert_method :change, content do |change| + assert_match(/create_table :books, id: :uuid/, change) + end + end + end + + def test_should_create_empty_migrations_if_name_not_start_with_add_or_remove_or_create + migration = "delete_books" + run_generator [migration, "title:string", "content:text"] + + assert_migration "db/migrate/#{migration}.rb" do |content| + assert_method :change, content do |change| + assert_match(/^\s*$/, change) + end + end + end + + def test_properly_identifies_usage_file + assert generator_class.send(:usage_path) + end + + def test_migration_with_singular_table_name + with_singular_table_name do + migration = "add_title_body_to_post" + run_generator [migration, 'title:string'] + assert_migration "db/migrate/#{migration}.rb" do |content| + assert_method :change, content do |change| + assert_match(/add_column :post, :title, :string/, change) + end + end + end + end + + def test_create_join_table_migration_with_singular_table_name + with_singular_table_name do + migration = "add_media_join_table" + run_generator [migration, "artist_id", "music:uniq"] + + assert_migration "db/migrate/#{migration}.rb" do |content| + assert_method :change, content do |change| + assert_match(/create_join_table :artist, :music/, change) + assert_match(/# t.index \[:artist_id, :music_id\]/, change) + assert_match(/ t.index \[:music_id, :artist_id\], unique: true/, change) + end + end + end + end + + def test_create_table_migration_with_singular_table_name + with_singular_table_name do + run_generator ["create_book", "title:string", "content:text"] + assert_migration "db/migrate/create_book.rb" do |content| + assert_method :change, content do |change| + assert_match(/create_table :book/, change) + assert_match(/ t\.string :title/, change) + assert_match(/ t\.text :content/, change) + end + end + end + end + + def test_create_table_migration_with_token_option + run_generator ["create_users", "token:token", "auth_token:token"] + assert_migration "db/migrate/create_users.rb" do |content| + assert_method :change, content do |change| + assert_match(/create_table :users/, change) + assert_match(/ t\.string :token/, change) + assert_match(/ t\.string :auth_token/, change) + assert_match(/add_index :users, :token, unique: true/, change) + assert_match(/add_index :users, :auth_token, unique: true/, change) + end + end + end + + def test_add_migration_with_token_option + migration = "add_token_to_users" + run_generator [migration, "auth_token:token"] + assert_migration "db/migrate/#{migration}.rb" do |content| + assert_method :change, content do |change| + assert_match(/add_column :users, :auth_token, :string/, change) + assert_match(/add_index :users, :auth_token, unique: true/, change) + end + end + end + + private + + def with_singular_table_name + old_state = ActiveRecord::Base.pluralize_table_names + ActiveRecord::Base.pluralize_table_names = false + yield + ensure + ActiveRecord::Base.pluralize_table_names = old_state + end +end diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb new file mode 100644 index 0000000000..64b9a480f3 --- /dev/null +++ b/railties/test/generators/model_generator_test.rb @@ -0,0 +1,476 @@ +require 'generators/generators_test_helper' +require 'rails/generators/rails/model/model_generator' +require 'active_support/core_ext/string/strip' + +class ModelGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper + arguments %w(Account name:string age:integer) + + def test_help_shows_invoked_generators_options + content = run_generator ["--help"] + assert_match(/ActiveRecord options:/, content) + assert_match(/TestUnit options:/, content) + end + + def test_model_with_missing_attribute_type + run_generator ["post", "title", "body:text", "author"] + + assert_migration "db/migrate/create_posts.rb" do |m| + assert_method :change, m do |up| + assert_match(/t\.string :title/, up) + assert_match(/t\.text :body/, up) + assert_match(/t\.string :author/, up) + end + end + end + + def test_invokes_default_orm + run_generator + assert_file "app/models/account.rb", /class Account < ActiveRecord::Base/ + end + + def test_model_with_parent_option + run_generator ["account", "--parent", "Admin::Account"] + assert_file "app/models/account.rb", /class Account < Admin::Account/ + assert_no_migration "db/migrate/create_accounts.rb" + end + + def test_plural_names_are_singularized + content = run_generator ["accounts".freeze] + assert_file "app/models/account.rb", /class Account < ActiveRecord::Base/ + assert_file "test/models/account_test.rb", /class AccountTest/ + assert_match(/\[WARNING\] The model name 'accounts' was recognized as a plural, using the singular 'account' instead\. Override with --force-plural or setup custom inflection rules for this noun before running the generator\./, content) + end + + def test_model_with_underscored_parent_option + run_generator ["account", "--parent", "admin/account"] + assert_file "app/models/account.rb", /class Account < Admin::Account/ + end + + def test_model_with_namespace + run_generator ["admin/account"] + assert_file "app/models/admin.rb", /module Admin/ + assert_file "app/models/admin.rb", /def self\.table_name_prefix/ + assert_file "app/models/admin.rb", /'admin_'/ + assert_file "app/models/admin/account.rb", /class Admin::Account < ActiveRecord::Base/ + end + + def test_migration + run_generator + assert_migration "db/migrate/create_accounts.rb", /class CreateAccounts < ActiveRecord::Migration/ + end + + def test_migration_with_namespace + run_generator ["Gallery::Image"] + assert_migration "db/migrate/create_gallery_images", /class CreateGalleryImages < ActiveRecord::Migration/ + assert_no_migration "db/migrate/create_images" + end + + def test_migration_with_nested_namespace + run_generator ["Admin::Gallery::Image"] + assert_no_migration "db/migrate/create_images" + assert_no_migration "db/migrate/create_gallery_images" + assert_migration "db/migrate/create_admin_gallery_images", /class CreateAdminGalleryImages < ActiveRecord::Migration/ + assert_migration "db/migrate/create_admin_gallery_images", /create_table :admin_gallery_images/ + end + + def test_migration_with_nested_namespace_without_pluralization + ActiveRecord::Base.pluralize_table_names = false + run_generator ["Admin::Gallery::Image"] + assert_no_migration "db/migrate/create_images" + assert_no_migration "db/migrate/create_gallery_images" + assert_no_migration "db/migrate/create_admin_gallery_images" + assert_migration "db/migrate/create_admin_gallery_image", /class CreateAdminGalleryImage < ActiveRecord::Migration/ + assert_migration "db/migrate/create_admin_gallery_image", /create_table :admin_gallery_image/ + ensure + ActiveRecord::Base.pluralize_table_names = true + end + + def test_migration_with_namespaces_in_model_name_without_plurization + ActiveRecord::Base.pluralize_table_names = false + run_generator ["Gallery::Image"] + assert_migration "db/migrate/create_gallery_image", /class CreateGalleryImage < ActiveRecord::Migration/ + assert_no_migration "db/migrate/create_gallery_images" + ensure + ActiveRecord::Base.pluralize_table_names = true + end + + def test_migration_without_pluralization + ActiveRecord::Base.pluralize_table_names = false + run_generator + assert_migration "db/migrate/create_account", /class CreateAccount < ActiveRecord::Migration/ + assert_no_migration "db/migrate/create_accounts" + ensure + ActiveRecord::Base.pluralize_table_names = true + end + + def test_migration_is_skipped + run_generator ["account", "--no-migration"] + assert_no_migration "db/migrate/create_accounts.rb" + end + + def test_migration_with_attributes + run_generator ["product", "name:string", "supplier_id:integer"] + + assert_migration "db/migrate/create_products.rb" do |m| + assert_method :change, m do |up| + assert_match(/create_table :products/, up) + assert_match(/t\.string :name/, up) + assert_match(/t\.integer :supplier_id/, up) + end + end + end + + def test_migration_with_attributes_and_with_index + run_generator ["product", "name:string:index", "supplier_id:integer:index", "user_id:integer:uniq", "order_id:uniq"] + + assert_migration "db/migrate/create_products.rb" do |m| + assert_method :change, m do |up| + assert_match(/create_table :products/, up) + assert_match(/t\.string :name/, up) + assert_match(/t\.integer :supplier_id/, up) + assert_match(/t\.integer :user_id/, up) + assert_match(/t\.string :order_id/, up) + + assert_match(/add_index :products, :name/, up) + assert_match(/add_index :products, :supplier_id/, up) + assert_match(/add_index :products, :user_id, unique: true/, up) + assert_match(/add_index :products, :order_id, unique: true/, up) + end + end + end + + def test_migration_with_attributes_and_with_wrong_index_declaration + run_generator ["product", "name:string", "supplier_id:integer:inex", "user_id:integer:unqu"] + + assert_migration "db/migrate/create_products.rb" do |m| + assert_method :change, m do |up| + assert_match(/create_table :products/, up) + assert_match(/t\.string :name/, up) + assert_match(/t\.integer :supplier_id/, up) + assert_match(/t\.integer :user_id/, up) + + assert_no_match(/add_index :products, :name/, up) + assert_no_match(/add_index :products, :supplier_id/, up) + assert_no_match(/add_index :products, :user_id/, up) + end + end + end + + def test_migration_with_missing_attribute_type_and_with_index + run_generator ["product", "name:index", "supplier_id:integer:index", "year:integer"] + + assert_migration "db/migrate/create_products.rb" do |m| + assert_method :change, m do |up| + assert_match(/create_table :products/, up) + assert_match(/t\.string :name/, up) + assert_match(/t\.integer :supplier_id/, up) + + assert_match(/add_index :products, :name/, up) + assert_match(/add_index :products, :supplier_id/, up) + assert_no_match(/add_index :products, :year/, up) + end + end + end + + def test_add_migration_with_attributes_index_declaration_and_attribute_options + run_generator ["product", "title:string{40}:index", "content:string{255}", "price:decimal{5,2}:index", "discount:decimal{5,2}:uniq", "supplier:references{polymorphic}"] + + assert_migration "db/migrate/create_products.rb" do |content| + assert_method :change, content do |up| + assert_match(/create_table :products/, up) + assert_match(/t.string :title, limit: 40/, up) + assert_match(/t.string :content, limit: 255/, up) + assert_match(/t.decimal :price, precision: 5, scale: 2/, up) + assert_match(/t.references :supplier, polymorphic: true/, up) + end + assert_match(/add_index :products, :title/, content) + assert_match(/add_index :products, :price/, content) + assert_match(/add_index :products, :discount, unique: true/, content) + end + end + + def test_migration_without_timestamps + ActiveRecord::Base.timestamped_migrations = false + run_generator ["account"] + assert_file "db/migrate/001_create_accounts.rb", /class CreateAccounts < ActiveRecord::Migration/ + + run_generator ["project"] + assert_file "db/migrate/002_create_projects.rb", /class CreateProjects < ActiveRecord::Migration/ + ensure + ActiveRecord::Base.timestamped_migrations = true + end + + def test_model_with_references_attribute_generates_belongs_to_associations + run_generator ["product", "name:string", "supplier:references"] + assert_file "app/models/product.rb", /belongs_to :supplier/ + end + + def test_model_with_belongs_to_attribute_generates_belongs_to_associations + run_generator ["product", "name:string", "supplier:belongs_to"] + assert_file "app/models/product.rb", /belongs_to :supplier/ + end + + def test_model_with_polymorphic_references_attribute_generates_belongs_to_associations + run_generator ["product", "name:string", "supplier:references{polymorphic}"] + assert_file "app/models/product.rb", /belongs_to :supplier, polymorphic: true/ + end + + def test_model_with_polymorphic_belongs_to_attribute_generates_belongs_to_associations + run_generator ["product", "name:string", "supplier:belongs_to{polymorphic}"] + assert_file "app/models/product.rb", /belongs_to :supplier, polymorphic: true/ + end + + def test_migration_with_timestamps + run_generator + assert_migration "db/migrate/create_accounts.rb", /t.timestamps/ + end + + def test_migration_timestamps_are_skipped + run_generator ["account", "--no-timestamps"] + + assert_migration "db/migrate/create_accounts.rb" do |m| + assert_method :change, m do |up| + assert_no_match(/t.timestamps/, up) + end + end + end + + def test_migration_is_skipped_with_skip_option + run_generator + output = run_generator ["Account", "--skip"] + assert_match %r{skip\s+db/migrate/\d+_create_accounts.rb}, output + end + + def test_migration_is_ignored_as_identical_with_skip_option + run_generator ["Account"] + output = run_generator ["Account", "--skip"] + assert_match %r{identical\s+db/migrate/\d+_create_accounts.rb}, output + end + + def test_migration_is_skipped_on_skip_behavior + run_generator + output = run_generator ["Account"], behavior: :skip + assert_match %r{skip\s+db/migrate/\d+_create_accounts.rb}, output + end + + def test_migration_error_is_not_shown_on_revoke + run_generator + error = capture(:stderr){ run_generator ["Account"], behavior: :revoke } + assert_no_match(/Another migration is already named create_accounts/, error) + end + + def test_migration_is_removed_on_revoke + run_generator + run_generator ["Account"], behavior: :revoke + assert_no_migration "db/migrate/create_accounts.rb" + end + + def test_existing_migration_is_removed_on_force + run_generator + old_migration = Dir["#{destination_root}/db/migrate/*_create_accounts.rb"].first + error = capture(:stderr) { run_generator ["Account", "--force"] } + assert_no_match(/Another migration is already named create_accounts/, error) + assert_no_file old_migration + assert_migration "db/migrate/create_accounts.rb" + end + + def test_invokes_default_test_framework + run_generator + assert_file "test/models/account_test.rb", /class AccountTest < ActiveSupport::TestCase/ + + assert_file "test/fixtures/accounts.yml", /name: MyString/, /age: 1/ + assert_generated_fixture("test/fixtures/accounts.yml", + {"one"=>{"name"=>"MyString", "age"=>1}, "two"=>{"name"=>"MyString", "age"=>1}}) + end + + def test_fixtures_use_the_references_ids + run_generator ["LineItem", "product:references", "cart:belongs_to"] + + assert_file "test/fixtures/line_items.yml", /product: \n cart: / + assert_generated_fixture("test/fixtures/line_items.yml", + {"one"=>{"product"=>nil, "cart"=>nil}, "two"=>{"product"=>nil, "cart"=>nil}}) + end + + def test_fixtures_use_the_references_ids_and_type + run_generator ["LineItem", "product:references{polymorphic}", "cart:belongs_to"] + + assert_file "test/fixtures/line_items.yml", /product: \n product_type: Product\n cart: / + assert_generated_fixture("test/fixtures/line_items.yml", + {"one"=>{"product"=>nil, "product_type"=>"Product", "cart"=>nil}, + "two"=>{"product"=>nil, "product_type"=>"Product", "cart"=>nil}}) + end + + def test_fixtures_respect_reserved_yml_keywords + run_generator ["LineItem", "no:integer", "Off:boolean", "ON:boolean"] + + assert_generated_fixture("test/fixtures/line_items.yml", + {"one"=>{"no"=>1, "Off"=>false, "ON"=>false}, "two"=>{"no"=>1, "Off"=>false, "ON"=>false}}) + end + + def test_fixture_is_skipped + run_generator ["account", "--skip-fixture"] + assert_no_file "test/fixtures/accounts.yml" + end + + def test_fixture_is_skipped_if_fixture_replacement_is_given + content = run_generator ["account", "-r", "factory_girl"] + assert_match(/factory_girl \[not found\]/, content) + assert_no_file "test/fixtures/accounts.yml" + end + + def test_fixture_without_pluralization + original_pluralize_table_name = ActiveRecord::Base.pluralize_table_names + ActiveRecord::Base.pluralize_table_names = false + run_generator + assert_generated_fixture("test/fixtures/account.yml", + {"one"=>{"name"=>"MyString", "age"=>1}, "two"=>{"name"=>"MyString", "age"=>1}}) + ensure + ActiveRecord::Base.pluralize_table_names = original_pluralize_table_name + end + + def test_check_class_collision + content = capture(:stderr){ run_generator ["object"] } + assert_match(/The name 'Object' is either already used in your application or reserved/, content) + end + + def test_index_is_added_for_belongs_to_association + run_generator ["account", "supplier:belongs_to"] + + assert_migration "db/migrate/create_accounts.rb" do |m| + assert_method :change, m do |up| + assert_match(/index: true/, up) + end + end + end + + def test_index_is_added_for_references_association + run_generator ["account", "supplier:references"] + + assert_migration "db/migrate/create_accounts.rb" do |m| + assert_method :change, m do |up| + assert_match(/index: true/, up) + end + end + end + + def test_index_is_skipped_for_belongs_to_association + run_generator ["account", "supplier:belongs_to", "--no-indexes"] + + assert_migration "db/migrate/create_accounts.rb" do |m| + assert_method :change, m do |up| + assert_no_match(/index: true/, up) + end + end + end + + def test_index_is_skipped_for_references_association + run_generator ["account", "supplier:references", "--no-indexes"] + + assert_migration "db/migrate/create_accounts.rb" do |m| + assert_method :change, m do |up| + assert_no_match(/index: true/, up) + end + end + end + + def test_add_uuid_to_create_table_migration + run_generator ["account", "--primary_key_type=uuid"] + assert_migration "db/migrate/create_accounts.rb" do |content| + assert_method :change, content do |change| + assert_match(/create_table :accounts, id: :uuid/, change) + end + end + end + + def test_required_belongs_to_adds_required_association + run_generator ["account", "supplier:references{required}"] + + expected_file = <<-FILE.strip_heredoc + class Account < ActiveRecord::Base + belongs_to :supplier, required: true + end + FILE + assert_file "app/models/account.rb", expected_file + end + + def test_required_polymorphic_belongs_to_generages_correct_model + run_generator ["account", "supplier:references{required,polymorphic}"] + + expected_file = <<-FILE.strip_heredoc + class Account < ActiveRecord::Base + belongs_to :supplier, polymorphic: true, required: true + end + FILE + assert_file "app/models/account.rb", expected_file + end + + def test_required_and_polymorphic_are_order_independent + run_generator ["account", "supplier:references{polymorphic.required}"] + + expected_file = <<-FILE.strip_heredoc + class Account < ActiveRecord::Base + belongs_to :supplier, polymorphic: true, required: true + end + FILE + assert_file "app/models/account.rb", expected_file + end + + def test_required_adds_null_false_to_column + run_generator ["account", "supplier:references{required}"] + + assert_migration "db/migrate/create_accounts.rb" do |m| + assert_method :change, m do |up| + assert_match(/t\.references :supplier,.*\snull: false/, up) + end + end + end + + def test_foreign_key_is_not_added_for_non_references + run_generator ["account", "supplier:string"] + + assert_migration "db/migrate/create_accounts.rb" do |m| + assert_method :change, m do |up| + assert_no_match(/foreign_key/, up) + end + end + end + + def test_foreign_key_is_added_for_references + run_generator ["account", "supplier:belongs_to", "user:references"] + + assert_migration "db/migrate/create_accounts.rb" do |m| + assert_method :change, m do |up| + assert_match(/t\.belongs_to :supplier,.*\sforeign_key: true/, up) + assert_match(/t\.references :user,.*\sforeign_key: true/, up) + end + end + end + + def test_foreign_key_is_skipped_for_polymorphic_references + run_generator ["account", "supplier:belongs_to{polymorphic}"] + + assert_migration "db/migrate/create_accounts.rb" do |m| + assert_method :change, m do |up| + assert_no_match(/foreign_key/, up) + end + end + end + + def test_token_option_adds_has_secure_token + run_generator ["user", "token:token", "auth_token:token"] + expected_file = <<-FILE.strip_heredoc + class User < ActiveRecord::Base + has_secure_token + has_secure_token :auth_token + end + FILE + assert_file "app/models/user.rb", expected_file + end + + private + def assert_generated_fixture(path, parsed_contents) + fixture_file = File.new File.expand_path(path, destination_root) + assert_equal(parsed_contents, YAML.load(fixture_file)) + end +end diff --git a/railties/test/generators/named_base_test.rb b/railties/test/generators/named_base_test.rb new file mode 100644 index 0000000000..291f5e06c3 --- /dev/null +++ b/railties/test/generators/named_base_test.rb @@ -0,0 +1,139 @@ +require 'generators/generators_test_helper' +require 'rails/generators/rails/scaffold_controller/scaffold_controller_generator' + +class NamedBaseTest < Rails::Generators::TestCase + include GeneratorsTestHelper + tests Rails::Generators::ScaffoldControllerGenerator + + def test_named_generator_with_underscore + g = generator ['line_item'] + assert_name g, 'line_item', :name + assert_name g, %w(), :class_path + assert_name g, 'LineItem', :class_name + assert_name g, 'line_item', :file_path + assert_name g, 'line_item', :file_name + assert_name g, 'Line item', :human_name + assert_name g, 'line_item', :singular_name + assert_name g, 'line_items', :plural_name + assert_name g, 'line_item', :i18n_scope + assert_name g, 'line_items', :table_name + end + + def test_named_generator_attributes + g = generator ['admin/foo'] + assert_name g, 'admin/foo', :name + assert_name g, %w(admin), :class_path + assert_name g, 'Admin::Foo', :class_name + assert_name g, 'admin/foo', :file_path + assert_name g, 'foo', :file_name + assert_name g, 'Foo', :human_name + assert_name g, 'foo', :singular_name + assert_name g, 'foos', :plural_name + assert_name g, 'admin.foo', :i18n_scope + assert_name g, 'admin_foos', :table_name + end + + def test_named_generator_attributes_as_ruby + g = generator ['Admin::Foo'] + assert_name g, 'Admin::Foo', :name + assert_name g, %w(admin), :class_path + assert_name g, 'Admin::Foo', :class_name + assert_name g, 'admin/foo', :file_path + assert_name g, 'foo', :file_name + assert_name g, 'foo', :singular_name + assert_name g, 'Foo', :human_name + assert_name g, 'foos', :plural_name + assert_name g, 'admin.foo', :i18n_scope + assert_name g, 'admin_foos', :table_name + end + + def test_named_generator_attributes_without_pluralized + original_pluralize_table_names = ActiveRecord::Base.pluralize_table_names + ActiveRecord::Base.pluralize_table_names = false + + g = generator ['admin/foo'] + assert_name g, 'admin_foo', :table_name + ensure + ActiveRecord::Base.pluralize_table_names = original_pluralize_table_names + end + + def test_scaffold_plural_names + g = generator ['admin/foo'] + assert_name g, 'admin/foos', :controller_name + assert_name g, %w(admin), :controller_class_path + assert_name g, 'Admin::Foos', :controller_class_name + assert_name g, 'admin/foos', :controller_file_path + assert_name g, 'foos', :controller_file_name + assert_name g, 'admin.foos', :controller_i18n_scope + end + + def test_scaffold_plural_names_as_ruby + g = generator ['Admin::Foo'] + assert_name g, 'Admin::Foos', :controller_name + assert_name g, %w(admin), :controller_class_path + assert_name g, 'Admin::Foos', :controller_class_name + assert_name g, 'admin/foos', :controller_file_path + assert_name g, 'foos', :controller_file_name + assert_name g, 'admin.foos', :controller_i18n_scope + end + + def test_application_name + g = generator ['Admin::Foo'] + Rails.stub(:application, Object.new) do + assert_name g, "object", :application_name + end + + Rails.stub(:application, nil) do + assert_name g, "application", :application_name + end + end + + def test_index_helper + g = generator ['Post'] + assert_name g, 'posts', :index_helper + end + + def test_index_helper_to_pluralize_once + g = generator ['Stadium'] + assert_name g, 'stadia', :index_helper + end + + def test_index_helper_with_uncountable + g = generator ['Sheep'] + assert_name g, 'sheep_index', :index_helper + end + + def test_hide_namespace + g = generator ['Hidden'] + g.class.stub(:namespace, 'hidden') do + assert !Rails::Generators.hidden_namespaces.include?('hidden') + g.class.hide! + assert Rails::Generators.hidden_namespaces.include?('hidden') + end + end + + def test_scaffold_plural_names_with_model_name_option + g = generator ['Admin::Foo'], model_name: 'User' + assert_name g, 'user', :singular_name + assert_name g, 'User', :name + assert_name g, 'user', :file_path + assert_name g, 'User', :class_name + assert_name g, 'user', :file_name + assert_name g, 'User', :human_name + assert_name g, 'users', :plural_name + assert_name g, 'user', :i18n_scope + assert_name g, 'users', :table_name + assert_name g, 'Admin::Foos', :controller_name + assert_name g, %w(admin), :controller_class_path + assert_name g, 'Admin::Foos', :controller_class_name + assert_name g, 'admin/foos', :controller_file_path + assert_name g, 'foos', :controller_file_name + assert_name g, 'admin.foos', :controller_i18n_scope + end + + protected + + def assert_name(generator, value, method) + assert_equal value, generator.send(method) + end +end diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb new file mode 100644 index 0000000000..590f06e19a --- /dev/null +++ b/railties/test/generators/namespaced_generators_test.rb @@ -0,0 +1,423 @@ +require 'generators/generators_test_helper' +require 'rails/generators/rails/controller/controller_generator' +require 'rails/generators/rails/model/model_generator' +require 'rails/generators/mailer/mailer_generator' +require 'rails/generators/rails/scaffold/scaffold_generator' + +class NamespacedGeneratorTestCase < Rails::Generators::TestCase + include GeneratorsTestHelper + + def setup + super + Rails::Generators.namespace = TestApp + end +end + +class NamespacedControllerGeneratorTest < NamespacedGeneratorTestCase + arguments %w(Account foo bar) + tests Rails::Generators::ControllerGenerator + + setup :copy_routes + + def test_namespaced_controller_skeleton_is_created + run_generator + assert_file "app/controllers/test_app/account_controller.rb", + /require_dependency "test_app\/application_controller"/, + /module TestApp/, + / class AccountController < ApplicationController/ + + assert_file "test/controllers/test_app/account_controller_test.rb", + /module TestApp/, + / class AccountControllerTest/ + end + + def test_skipping_namespace + run_generator ["Account", "--skip-namespace"] + assert_file "app/controllers/account_controller.rb", /class AccountController < ApplicationController/ + assert_file "app/helpers/account_helper.rb", /module AccountHelper/ + end + + def test_namespaced_controller_with_additional_namespace + run_generator ["admin/account"] + assert_file "app/controllers/test_app/admin/account_controller.rb", /module TestApp/, / class Admin::AccountController < ApplicationController/ do |contents| + assert_match %r(require_dependency "test_app/application_controller"), contents + end + end + + def test_helper_is_also_namespaced + run_generator + assert_file "app/helpers/test_app/account_helper.rb", /module TestApp/, / module AccountHelper/ + end + + def test_invokes_default_test_framework + run_generator + assert_file "test/controllers/test_app/account_controller_test.rb" + end + + def test_invokes_default_template_engine + run_generator + assert_file "app/views/test_app/account/foo.html.erb", %r(app/views/test_app/account/foo\.html\.erb) + assert_file "app/views/test_app/account/bar.html.erb", %r(app/views/test_app/account/bar\.html\.erb) + end + + def test_routes_should_not_be_namespaced + run_generator + assert_file "config/routes.rb", /get 'account\/foo'/, /get 'account\/bar'/ + end + + def test_invokes_default_template_engine_even_with_no_action + run_generator ["account"] + assert_file "app/views/test_app/account" + end + + def test_namespaced_controller_dont_indent_blank_lines + run_generator + assert_file "app/controllers/test_app/account_controller.rb" do |content| + content.split("\n").each do |line| + assert_no_match(/^\s+$/, line, "Don't indent blank lines") + end + end + end +end + +class NamespacedModelGeneratorTest < NamespacedGeneratorTestCase + arguments %w(Account name:string age:integer) + tests Rails::Generators::ModelGenerator + + def test_module_file_is_not_created + run_generator + assert_no_file "app/models/test_app.rb" + end + + def test_adds_namespace_to_model + run_generator + assert_file "app/models/test_app/account.rb", /module TestApp/, / class Account < ActiveRecord::Base/ + end + + def test_model_with_namespace + run_generator ["admin/account"] + assert_file "app/models/test_app/admin.rb", /module TestApp/, /module Admin/ + assert_file "app/models/test_app/admin.rb", /def self\.table_name_prefix/ + assert_file "app/models/test_app/admin.rb", /'test_app_admin_'/ + assert_file "app/models/test_app/admin/account.rb", /module TestApp/, /class Admin::Account < ActiveRecord::Base/ + end + + def test_migration + run_generator + assert_migration "db/migrate/create_test_app_accounts.rb", /create_table :test_app_accounts/, /class CreateTestAppAccounts < ActiveRecord::Migration/ + end + + def test_migration_with_namespace + run_generator ["Gallery::Image"] + assert_migration "db/migrate/create_test_app_gallery_images", /class CreateTestAppGalleryImages < ActiveRecord::Migration/ + assert_no_migration "db/migrate/create_test_app_images" + end + + def test_migration_with_nested_namespace + run_generator ["Admin::Gallery::Image"] + assert_no_migration "db/migrate/create_images" + assert_no_migration "db/migrate/create_gallery_images" + assert_migration "db/migrate/create_test_app_admin_gallery_images", /class CreateTestAppAdminGalleryImages < ActiveRecord::Migration/ + assert_migration "db/migrate/create_test_app_admin_gallery_images", /create_table :test_app_admin_gallery_images/ + end + + def test_migration_with_nested_namespace_without_pluralization + ActiveRecord::Base.pluralize_table_names = false + run_generator ["Admin::Gallery::Image"] + assert_no_migration "db/migrate/create_images" + assert_no_migration "db/migrate/create_gallery_images" + assert_no_migration "db/migrate/create_test_app_admin_gallery_images" + assert_migration "db/migrate/create_test_app_admin_gallery_image", /class CreateTestAppAdminGalleryImage < ActiveRecord::Migration/ + assert_migration "db/migrate/create_test_app_admin_gallery_image", /create_table :test_app_admin_gallery_image/ + ensure + ActiveRecord::Base.pluralize_table_names = true + end + + def test_invokes_default_test_framework + run_generator + assert_file "test/models/test_app/account_test.rb", /module TestApp/, /class AccountTest < ActiveSupport::TestCase/ + assert_file "test/fixtures/test_app/accounts.yml", /name: MyString/, /age: 1/ + end +end + +class NamespacedMailerGeneratorTest < NamespacedGeneratorTestCase + arguments %w(notifier foo bar) + tests Rails::Generators::MailerGenerator + + def test_mailer_skeleton_is_created + run_generator + assert_file "app/mailers/test_app/notifier_mailer.rb" do |mailer| + assert_match(/module TestApp/, mailer) + assert_match(/class NotifierMailer < ApplicationMailer/, mailer) + assert_no_match(/default from: "from@example.com"/, mailer) + end + end + + def test_mailer_with_i18n_helper + run_generator + assert_file "app/mailers/test_app/notifier_mailer.rb" do |mailer| + assert_match(/en\.notifier_mailer\.foo\.subject/, mailer) + assert_match(/en\.notifier_mailer\.bar\.subject/, mailer) + end + end + + def test_invokes_default_test_framework + run_generator + assert_file "test/mailers/test_app/notifier_mailer_test.rb" do |test| + assert_match(/module TestApp/, test) + assert_match(/class NotifierMailerTest < ActionMailer::TestCase/, test) + assert_match(/test "foo"/, test) + assert_match(/test "bar"/, test) + end + end + + def test_invokes_default_template_engine + run_generator + assert_file "app/views/test_app/notifier_mailer/foo.text.erb" do |view| + assert_match(%r(app/views/test_app/notifier_mailer/foo\.text\.erb), view) + assert_match(/<%= @greeting %>/, view) + end + + assert_file "app/views/test_app/notifier_mailer/bar.text.erb" do |view| + assert_match(%r(app/views/test_app/notifier_mailer/bar\.text\.erb), view) + assert_match(/<%= @greeting %>/, view) + end + end + + def test_invokes_default_template_engine_even_with_no_action + run_generator ["notifier"] + assert_file "app/views/test_app/notifier_mailer" + end +end + +class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase + include GeneratorsTestHelper + arguments %w(product_line title:string price:integer) + tests Rails::Generators::ScaffoldGenerator + + setup :copy_routes + + def test_scaffold_on_invoke + run_generator + + # Model + assert_file "app/models/test_app/product_line.rb", /module TestApp\n class ProductLine < ActiveRecord::Base/ + assert_file "test/models/test_app/product_line_test.rb", /module TestApp\n class ProductLineTest < ActiveSupport::TestCase/ + assert_file "test/fixtures/test_app/product_lines.yml" + assert_migration "db/migrate/create_test_app_product_lines.rb" + + # Route + assert_file "config/routes.rb" do |route| + assert_match(/resources :product_lines$/, route) + end + + # Controller + assert_file "app/controllers/test_app/product_lines_controller.rb", + /require_dependency "test_app\/application_controller"/, + /module TestApp/, + /class ProductLinesController < ApplicationController/ + + assert_file "test/controllers/test_app/product_lines_controller_test.rb", + /module TestApp\n class ProductLinesControllerTest < ActionController::TestCase/ + + # Views + %w(index edit new show _form).each do |view| + assert_file "app/views/test_app/product_lines/#{view}.html.erb" + end + assert_no_file "app/views/layouts/test_app/product_lines.html.erb" + + # Helpers + assert_file "app/helpers/test_app/product_lines_helper.rb" + + # Stylesheets + assert_file "app/assets/stylesheets/scaffold.css" + end + + def test_scaffold_on_revoke + run_generator + run_generator ["product_line"], behavior: :revoke + + # Model + assert_no_file "app/models/test_app/product_line.rb" + assert_no_file "test/models/test_app/product_line_test.rb" + assert_no_file "test/fixtures/test_app/product_lines.yml" + assert_no_migration "db/migrate/create_test_app_product_lines.rb" + + # Route + assert_file "config/routes.rb" do |route| + assert_no_match(/resources :product_lines$/, route) + end + + # Controller + assert_no_file "app/controllers/test_app/product_lines_controller.rb" + assert_no_file "test/controllers/test_app/product_lines_controller_test.rb" + + # Views + assert_no_file "app/views/test_app/product_lines" + assert_no_file "app/views/test_app/layouts/product_lines.html.erb" + + # Helpers + assert_no_file "app/helpers/test_app/product_lines_helper.rb" + + # Stylesheets (should not be removed) + assert_file "app/assets/stylesheets/scaffold.css" + end + + def test_scaffold_with_namespace_on_invoke + run_generator [ "admin/role", "name:string", "description:string" ] + + # Model + assert_file "app/models/test_app/admin.rb", /module TestApp\n module Admin/ + assert_file "app/models/test_app/admin/role.rb", /module TestApp\n class Admin::Role < ActiveRecord::Base/ + assert_file "test/models/test_app/admin/role_test.rb", /module TestApp\n class Admin::RoleTest < ActiveSupport::TestCase/ + assert_file "test/fixtures/test_app/admin/roles.yml" + assert_migration "db/migrate/create_test_app_admin_roles.rb" + + # Route + assert_file "config/routes.rb" do |route| + assert_match(/^ namespace :admin do\n resources :roles\n end$/, route) + end + + # Controller + assert_file "app/controllers/test_app/admin/roles_controller.rb" do |content| + assert_match(/module TestApp\n class Admin::RolesController < ApplicationController/, content) + assert_match(%r(require_dependency "test_app/application_controller"), content) + end + + assert_file "test/controllers/test_app/admin/roles_controller_test.rb", + /module TestApp\n class Admin::RolesControllerTest < ActionController::TestCase/ + + # Views + %w(index edit new show _form).each do |view| + assert_file "app/views/test_app/admin/roles/#{view}.html.erb" + end + assert_no_file "app/views/layouts/admin/roles.html.erb" + + # Helpers + assert_file "app/helpers/test_app/admin/roles_helper.rb" + + # Stylesheets + assert_file "app/assets/stylesheets/scaffold.css" + end + + def test_scaffold_with_namespace_on_revoke + run_generator [ "admin/role", "name:string", "description:string" ] + run_generator [ "admin/role" ], behavior: :revoke + + # Model + assert_file "app/models/test_app/admin.rb" # ( should not be remove ) + assert_no_file "app/models/test_app/admin/role.rb" + assert_no_file "test/models/test_app/admin/role_test.rb" + assert_no_file "test/fixtures/test_app/admin/roles.yml" + assert_no_migration "db/migrate/create_test_app_admin_roles.rb" + + # Route + assert_file "config/routes.rb" do |route| + assert_no_match(/^ namespace :admin do\n resources :roles\n end$$/, route) + end + + # Controller + assert_no_file "app/controllers/test_app/admin/roles_controller.rb" + assert_no_file "test/controllers/test_app/admin/roles_controller_test.rb" + + # Views + assert_no_file "app/views/test_app/admin/roles" + assert_no_file "app/views/layouts/test_app/admin/roles.html.erb" + + # Helpers + assert_no_file "app/helpers/test_app/admin/roles_helper.rb" + + # Stylesheets (should not be removed) + assert_file "app/assets/stylesheets/scaffold.css" + end + + def test_scaffold_with_nested_namespace_on_invoke + run_generator [ "admin/user/special/role", "name:string", "description:string" ] + + # Model + assert_file "app/models/test_app/admin/user/special.rb", /module TestApp\n module Admin/ + assert_file "app/models/test_app/admin/user/special/role.rb", /module TestApp\n class Admin::User::Special::Role < ActiveRecord::Base/ + assert_file "test/models/test_app/admin/user/special/role_test.rb", /module TestApp\n class Admin::User::Special::RoleTest < ActiveSupport::TestCase/ + assert_file "test/fixtures/test_app/admin/user/special/roles.yml" + assert_migration "db/migrate/create_test_app_admin_user_special_roles.rb" + + # Route + assert_file "config/routes.rb" do |route| + assert_match(/^ namespace :admin do\n namespace :user do\n namespace :special do\n resources :roles\n end\n end\n end$/, route) + end + + # Controller + assert_file "app/controllers/test_app/admin/user/special/roles_controller.rb" do |content| + assert_match(/module TestApp\n class Admin::User::Special::RolesController < ApplicationController/, content) + end + + assert_file "test/controllers/test_app/admin/user/special/roles_controller_test.rb", + /module TestApp\n class Admin::User::Special::RolesControllerTest < ActionController::TestCase/ + + # Views + %w(index edit new show _form).each do |view| + assert_file "app/views/test_app/admin/user/special/roles/#{view}.html.erb" + end + assert_no_file "app/views/layouts/admin/user/special/roles.html.erb" + + # Helpers + assert_file "app/helpers/test_app/admin/user/special/roles_helper.rb" + + # Stylesheets + assert_file "app/assets/stylesheets/scaffold.css" + end + + def test_scaffold_with_nested_namespace_on_revoke + run_generator [ "admin/user/special/role", "name:string", "description:string" ] + run_generator [ "admin/user/special/role" ], behavior: :revoke + + # Model + assert_file "app/models/test_app/admin/user/special.rb" # ( should not be remove ) + assert_no_file "app/models/test_app/admin/user/special/role.rb" + assert_no_file "test/models/test_app/admin/user/special/role_test.rb" + assert_no_file "test/fixtures/test_app/admin/user/special/roles.yml" + assert_no_migration "db/migrate/create_test_app_admin_user_special_roles.rb" + + # Route + assert_file "config/routes.rb" do |route| + assert_no_match(/^ namespace :admin do\n namespace :user do\n namespace :special do\n resources :roles\n end\n end\n end$/, route) + end + + # Controller + assert_no_file "app/controllers/test_app/admin/user/special/roles_controller.rb" + assert_no_file "test/controllers/test_app/admin/user/special/roles_controller_test.rb" + + # Views + assert_no_file "app/views/test_app/admin/user/special/roles" + + # Helpers + assert_no_file "app/helpers/test_app/admin/user/special/roles_helper.rb" + + # Stylesheets (should not be removed) + assert_file "app/assets/stylesheets/scaffold.css" + end + + def test_api_scaffold_with_namespace_on_invoke + run_generator [ "admin/role", "name:string", "description:string", "--api" ] + + # Model + assert_file "app/models/test_app/admin.rb", /module TestApp\n module Admin/ + assert_file "app/models/test_app/admin/role.rb", /module TestApp\n class Admin::Role < ActiveRecord::Base/ + assert_file "test/models/test_app/admin/role_test.rb", /module TestApp\n class Admin::RoleTest < ActiveSupport::TestCase/ + assert_file "test/fixtures/test_app/admin/roles.yml" + assert_migration "db/migrate/create_test_app_admin_roles.rb" + + # Route + assert_file "config/routes.rb" do |route| + assert_match(/^ namespace :admin do\n resources :roles\n end$/, route) + end + + # Controller + assert_file "app/controllers/test_app/admin/roles_controller.rb" do |content| + assert_match(/module TestApp\n class Admin::RolesController < ApplicationController/, content) + assert_match(%r(require_dependency "test_app/application_controller"), content) + end + assert_file "test/controllers/test_app/admin/roles_controller_test.rb", + /module TestApp\n class Admin::RolesControllerTest < ActionController::TestCase/ + end +end diff --git a/railties/test/generators/orm_test.rb b/railties/test/generators/orm_test.rb new file mode 100644 index 0000000000..88ae930554 --- /dev/null +++ b/railties/test/generators/orm_test.rb @@ -0,0 +1,38 @@ +require "generators/generators_test_helper" +require "rails/generators/rails/scaffold_controller/scaffold_controller_generator" + +# Mock out two ORMs +module ORMWithGenerators + module Generators + class ActiveModel + def initialize(name) + end + end + end +end + +module ORMWithoutGenerators + # No generators +end + +class OrmTest < Rails::Generators::TestCase + include GeneratorsTestHelper + tests Rails::Generators::ScaffoldControllerGenerator + + def test_orm_class_returns_custom_generator_if_supported_custom_orm_set + g = generator ["Foo"], orm: "ORMWithGenerators" + assert_equal ORMWithGenerators::Generators::ActiveModel, g.send(:orm_class) + end + + def test_orm_class_returns_rails_generator_if_unsupported_custom_orm_set + g = generator ["Foo"], orm: "ORMWithoutGenerators" + assert_equal Rails::Generators::ActiveModel, g.send(:orm_class) + end + + def test_orm_instance_returns_orm_class_instance_with_name + g = generator ["Foo"] + orm_instance = g.send(:orm_instance) + assert g.send(:orm_class) === orm_instance + assert_equal "foo", orm_instance.name + end +end diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb new file mode 100644 index 0000000000..60390cfa01 --- /dev/null +++ b/railties/test/generators/plugin_generator_test.rb @@ -0,0 +1,636 @@ +require 'generators/generators_test_helper' +require 'rails/generators/rails/plugin/plugin_generator' +require 'generators/shared_generator_tests' + +DEFAULT_PLUGIN_FILES = %w( + .gitignore + Gemfile + Rakefile + README.rdoc + bukkits.gemspec + MIT-LICENSE + lib + lib/bukkits.rb + lib/tasks/bukkits_tasks.rake + lib/bukkits/version.rb + test/bukkits_test.rb + test/test_helper.rb + test/dummy +) + +class PluginGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper + destination File.join(Rails.root, "tmp/bukkits") + arguments [destination_root] + + # brings setup, teardown, and some tests + include SharedGeneratorTests + + def test_invalid_plugin_name_raises_an_error + content = capture(:stderr){ run_generator [File.join(destination_root, "my_plugin-31fr-extension")] } + assert_equal "Invalid plugin name my_plugin-31fr-extension. Please give a name which does not contain a namespace starting with numeric characters.\n", content + + content = capture(:stderr){ run_generator [File.join(destination_root, "things4.3")] } + assert_equal "Invalid plugin name things4.3. Please give a name which uses only alphabetic, numeric, \"_\" or \"-\" characters.\n", content + + content = capture(:stderr){ run_generator [File.join(destination_root, "43things")] } + assert_equal "Invalid plugin name 43things. Please give a name which does not start with numbers.\n", content + + content = capture(:stderr){ run_generator [File.join(destination_root, "plugin")] } + assert_equal "Invalid plugin name plugin. Please give a name which does not match one of the reserved rails words: application, destroy, plugin, runner, test\n", content + + content = capture(:stderr){ run_generator [File.join(destination_root, "Digest")] } + assert_equal "Invalid plugin name Digest, constant Digest is already in use. Please choose another plugin name.\n", content + end + + def test_correct_file_in_lib_folder_of_hyphenated_plugin_name + run_generator [File.join(destination_root, "hyphenated-name")] + assert_no_file "hyphenated-name/lib/hyphenated-name.rb" + assert_no_file "hyphenated-name/lib/hyphenated_name.rb" + assert_file "hyphenated-name/lib/hyphenated/name.rb", /module Hyphenated\n module Name\n # Your code goes here...\n end\nend/ + end + + def test_correct_file_in_lib_folder_of_camelcase_plugin_name + run_generator [File.join(destination_root, "CamelCasedName")] + assert_no_file "CamelCasedName/lib/CamelCasedName.rb" + assert_file "CamelCasedName/lib/camel_cased_name.rb", /module CamelCasedName/ + end + + def test_generating_without_options + run_generator + assert_file "README.rdoc", /Bukkits/ + assert_no_file "config/routes.rb" + assert_no_file "app/assets/config/bukkits_manifest.js" + assert_file "test/test_helper.rb" do |content| + assert_match(/require.+test\/dummy\/config\/environment/, content) + assert_match(/ActiveRecord::Migrator\.migrations_paths.+test\/dummy\/db\/migrate/, content) + assert_match(/Minitest\.backtrace_filter = Minitest::BacktraceFilter\.new/, content) + assert_match(/Rails::TestUnitReporter\.executable = 'bin\/test'/, content) + end + assert_file "test/bukkits_test.rb", /assert_kind_of Module, Bukkits/ + assert_file 'bin/test' + assert_no_file 'bin/rails' + end + + def test_generating_test_files_in_full_mode + run_generator [destination_root, "--full"] + assert_directory "test/integration/" + + assert_file "test/integration/navigation_test.rb", /ActionDispatch::IntegrationTest/ + end + + def test_inclusion_of_a_debugger + run_generator [destination_root, '--full'] + if defined?(JRUBY_VERSION) || RUBY_ENGINE == "rbx" + assert_file "Gemfile" do |content| + assert_no_match(/byebug/, content) + end + else + assert_file "Gemfile", /# gem 'byebug'/ + end + end + + def test_generating_test_files_in_full_mode_without_unit_test_files + run_generator [destination_root, "-T", "--full"] + + assert_no_directory "test/integration/" + assert_no_file "test" + assert_file "Rakefile" do |contents| + assert_no_match(/APP_RAKEFILE/, contents) + end + end + + def test_generating_adds_dummy_app_rake_tasks_without_unit_test_files + run_generator [destination_root, "-T", "--mountable", '--dummy-path', 'my_dummy_app'] + assert_file "Rakefile", /APP_RAKEFILE/ + end + + def test_generating_adds_dummy_app_without_javascript_and_assets_deps + run_generator + + assert_file "test/dummy/app/assets/stylesheets/application.css" + + assert_file "test/dummy/app/assets/javascripts/application.js" do |contents| + assert_no_match(/jquery/, contents) + end + end + + def test_ensure_that_plugin_options_are_not_passed_to_app_generator + FileUtils.cd(Rails.root) + assert_no_match(/It works from file!.*It works_from_file/, run_generator([destination_root, "-m", "lib/template.rb"])) + end + + def test_ensure_that_test_dummy_can_be_generated_from_a_template + FileUtils.cd(Rails.root) + run_generator([destination_root, "-m", "lib/create_test_dummy_template.rb", "--skip-test"]) + assert_file "spec/dummy" + assert_no_file "test" + end + + def test_database_entry_is_generated_for_sqlite3_by_default_in_full_mode + run_generator([destination_root, "--full"]) + assert_file "test/dummy/config/database.yml", /sqlite/ + assert_file "bukkits.gemspec", /sqlite3/ + end + + def test_config_another_database + run_generator([destination_root, "-d", "mysql", "--full"]) + assert_file "test/dummy/config/database.yml", /mysql/ + assert_file "bukkits.gemspec", /mysql/ + end + + def test_dont_generate_development_dependency + run_generator [destination_root, "--skip-active-record"] + + assert_file "bukkits.gemspec" do |contents| + assert_no_match(/s.add_development_dependency "sqlite3"/, contents) + end + end + + def test_app_generator_without_skips + run_generator + assert_file "test/dummy/config/application.rb", /\s+require\s+["']rails\/all["']/ + assert_file "test/dummy/config/environments/development.rb" do |content| + assert_match(/config\.action_mailer\.raise_delivery_errors = false/, content) + end + assert_file "test/dummy/config/environments/test.rb" do |content| + assert_match(/config\.action_mailer\.delivery_method = :test/, content) + end + assert_file "test/dummy/config/environments/production.rb" do |content| + assert_match(/# config\.action_mailer\.raise_delivery_errors = false/, content) + end + end + + def test_active_record_is_removed_from_frameworks_if_skip_active_record_is_given + run_generator [destination_root, "--skip-active-record"] + assert_file "test/dummy/config/application.rb", /#\s+require\s+["']active_record\/railtie["']/ + end + + def test_ensure_that_skip_active_record_option_is_passed_to_app_generator + run_generator [destination_root, "--skip_active_record"] + assert_no_file "test/dummy/config/database.yml" + assert_file "test/test_helper.rb" do |contents| + assert_no_match(/ActiveRecord/, contents) + end + end + + def test_action_mailer_is_removed_from_frameworks_if_skip_action_mailer_is_given + run_generator [destination_root, "--skip-action-mailer"] + assert_file "test/dummy/config/application.rb", /#\s+require\s+["']action_mailer\/railtie["']/ + assert_file "test/dummy/config/environments/development.rb" do |content| + assert_no_match(/config\.action_mailer/, content) + end + assert_file "test/dummy/config/environments/test.rb" do |content| + assert_no_match(/config\.action_mailer/, content) + end + assert_file "test/dummy/config/environments/production.rb" do |content| + assert_no_match(/config\.action_mailer/, content) + end + end + + def test_ensure_that_database_option_is_passed_to_app_generator + run_generator [destination_root, "--database", "postgresql"] + assert_file "test/dummy/config/database.yml", /postgres/ + end + + def test_generation_runs_bundle_install_with_full_and_mountable + result = run_generator [destination_root, "--mountable", "--full", "--dev"] + assert_match(/run bundle install/, result) + assert $?.success?, "Command failed: #{result}" + assert_file "#{destination_root}/Gemfile.lock" do |contents| + assert_match(/bukkits/, contents) + end + end + + def test_skipping_javascripts_without_mountable_option + run_generator + assert_no_file "app/assets/javascripts/bukkits/application.js" + end + + def test_javascripts_generation + run_generator [destination_root, "--mountable"] + assert_file "app/assets/javascripts/bukkits/application.js" + end + + def test_skip_javascripts + run_generator [destination_root, "--skip-javascript", "--mountable"] + assert_no_file "app/assets/javascripts/bukkits/application.js" + end + + def test_template_from_dir_pwd + FileUtils.cd(Rails.root) + assert_match(/It works from file!/, run_generator([destination_root, "-m", "lib/template.rb"])) + end + + def test_ensure_that_tests_work + run_generator + FileUtils.cd destination_root + quietly { system 'bundle install' } + assert_match(/1 runs, 1 assertions, 0 failures, 0 errors/, `bin/test 2>&1`) + end + + def test_ensure_that_tests_works_in_full_mode + run_generator [destination_root, "--full", "--skip_active_record"] + FileUtils.cd destination_root + quietly { system 'bundle install' } + assert_match(/1 runs, 1 assertions, 0 failures, 0 errors/, `bundle exec rake test 2>&1`) + end + + def test_ensure_that_migration_tasks_work_with_mountable_option + run_generator [destination_root, "--mountable"] + FileUtils.cd destination_root + quietly { system 'bundle install' } + output = `bundle exec rake db:migrate 2>&1` + assert $?.success?, "Command failed: #{output}" + end + + def test_creating_engine_in_full_mode + run_generator [destination_root, "--full"] + assert_file "app/assets/javascripts/bukkits" + assert_file "app/assets/stylesheets/bukkits" + assert_file "app/assets/images/bukkits" + assert_file "app/models" + assert_file "app/controllers" + assert_file "app/views" + assert_file "app/helpers" + assert_file "app/mailers" + assert_file "bin/rails" + assert_file "config/routes.rb", /Rails.application.routes.draw do/ + assert_file "lib/bukkits/engine.rb", /module Bukkits\n class Engine < ::Rails::Engine\n end\nend/ + assert_file "lib/bukkits.rb", /require "bukkits\/engine"/ + end + + def test_creating_engine_with_hyphenated_name_in_full_mode + run_generator [File.join(destination_root, "hyphenated-name"), "--full"] + assert_file "hyphenated-name/app/assets/javascripts/hyphenated/name" + assert_file "hyphenated-name/app/assets/stylesheets/hyphenated/name" + assert_file "hyphenated-name/app/assets/images/hyphenated/name" + assert_file "hyphenated-name/app/models" + assert_file "hyphenated-name/app/controllers" + assert_file "hyphenated-name/app/views" + assert_file "hyphenated-name/app/helpers" + assert_file "hyphenated-name/app/mailers" + assert_file "hyphenated-name/bin/rails" + assert_file "hyphenated-name/config/routes.rb", /Rails.application.routes.draw do/ + assert_file "hyphenated-name/lib/hyphenated/name/engine.rb", /module Hyphenated\n module Name\n class Engine < ::Rails::Engine\n end\n end\nend/ + assert_file "hyphenated-name/lib/hyphenated/name.rb", /require "hyphenated\/name\/engine"/ + assert_file "hyphenated-name/bin/rails", /\.\.\/\.\.\/lib\/hyphenated\/name\/engine/ + end + + def test_creating_engine_with_hyphenated_and_underscored_name_in_full_mode + run_generator [File.join(destination_root, "my_hyphenated-name"), "--full"] + assert_file "my_hyphenated-name/app/assets/javascripts/my_hyphenated/name" + assert_file "my_hyphenated-name/app/assets/stylesheets/my_hyphenated/name" + assert_file "my_hyphenated-name/app/assets/images/my_hyphenated/name" + assert_file "my_hyphenated-name/app/models" + assert_file "my_hyphenated-name/app/controllers" + assert_file "my_hyphenated-name/app/views" + assert_file "my_hyphenated-name/app/helpers" + assert_file "my_hyphenated-name/app/mailers" + assert_file "my_hyphenated-name/bin/rails" + assert_file "my_hyphenated-name/config/routes.rb", /Rails.application.routes.draw do/ + assert_file "my_hyphenated-name/lib/my_hyphenated/name/engine.rb", /module MyHyphenated\n module Name\n class Engine < ::Rails::Engine\n end\n end\nend/ + assert_file "my_hyphenated-name/lib/my_hyphenated/name.rb", /require "my_hyphenated\/name\/engine"/ + assert_file "my_hyphenated-name/bin/rails", /\.\.\/\.\.\/lib\/my_hyphenated\/name\/engine/ + end + + def test_being_quiet_while_creating_dummy_application + assert_no_match(/create\s+config\/application.rb/, run_generator) + end + + def test_create_mountable_application_with_mountable_option + run_generator [destination_root, "--mountable"] + assert_file "app/assets/javascripts/bukkits" + assert_file "app/assets/stylesheets/bukkits" + assert_file "app/assets/images/bukkits" + assert_file "config/routes.rb", /Bukkits::Engine.routes.draw do/ + assert_file "lib/bukkits/engine.rb", /isolate_namespace Bukkits/ + assert_file "test/dummy/config/routes.rb", /mount Bukkits::Engine => "\/bukkits"/ + assert_file "app/controllers/bukkits/application_controller.rb", /module Bukkits\n class ApplicationController < ActionController::Base/ + assert_file "app/jobs/bukkits/application_job.rb", /module Bukkits\n class ApplicationJob < ActiveJob::Base/ + assert_file "app/helpers/bukkits/application_helper.rb", /module Bukkits\n module ApplicationHelper/ + assert_file "app/views/layouts/bukkits/application.html.erb" do |contents| + assert_match "<title>Bukkits</title>", contents + assert_match(/stylesheet_link_tag\s+['"]bukkits\/application['"]/, contents) + assert_match(/javascript_include_tag\s+['"]bukkits\/application['"]/, contents) + end + assert_file "test/test_helper.rb" do |content| + assert_match(/ActiveRecord::Migrator\.migrations_paths.+\.\.\/test\/dummy\/db\/migrate/, content) + assert_match(/ActiveRecord::Migrator\.migrations_paths.+<<.+\.\.\/db\/migrate/, content) + assert_match(/ActionDispatch::IntegrationTest\.fixture_path = ActiveSupport::TestCase\.fixture_pat/, content) + assert_no_match(/Rails::TestUnitReporter\.executable = 'bin\/test'/, content) + end + assert_no_file 'bin/test' + end + + def test_create_mountable_application_with_mountable_option_and_hypenated_name + run_generator [File.join(destination_root, "hyphenated-name"), "--mountable"] + assert_file "hyphenated-name/app/assets/javascripts/hyphenated/name" + assert_file "hyphenated-name/app/assets/stylesheets/hyphenated/name" + assert_file "hyphenated-name/app/assets/images/hyphenated/name" + assert_file "hyphenated-name/config/routes.rb", /Hyphenated::Name::Engine.routes.draw do/ + assert_file "hyphenated-name/lib/hyphenated/name/version.rb", /module Hyphenated\n module Name\n VERSION = '0.1.0'\n end\nend/ + assert_file "hyphenated-name/lib/hyphenated/name/engine.rb", /module Hyphenated\n module Name\n class Engine < ::Rails::Engine\n isolate_namespace Hyphenated::Name\n end\n end\nend/ + assert_file "hyphenated-name/lib/hyphenated/name.rb", /require "hyphenated\/name\/engine"/ + assert_file "hyphenated-name/test/dummy/config/routes.rb", /mount Hyphenated::Name::Engine => "\/hyphenated-name"/ + assert_file "hyphenated-name/app/controllers/hyphenated/name/application_controller.rb", /module Hyphenated\n module Name\n class ApplicationController < ActionController::Base\n end\n end\nend/ + assert_file "hyphenated-name/app/jobs/hyphenated/name/application_job.rb", /module Hyphenated\n module Name\n class ApplicationJob < ActiveJob::Base/ + assert_file "hyphenated-name/app/helpers/hyphenated/name/application_helper.rb", /module Hyphenated\n module Name\n module ApplicationHelper\n end\n end\nend/ + assert_file "hyphenated-name/app/views/layouts/hyphenated/name/application.html.erb" do |contents| + assert_match "<title>Hyphenated name</title>", contents + assert_match(/stylesheet_link_tag\s+['"]hyphenated\/name\/application['"]/, contents) + assert_match(/javascript_include_tag\s+['"]hyphenated\/name\/application['"]/, contents) + end + end + + def test_create_mountable_application_with_mountable_option_and_hypenated_and_underscored_name + run_generator [File.join(destination_root, "my_hyphenated-name"), "--mountable"] + assert_file "my_hyphenated-name/app/assets/javascripts/my_hyphenated/name" + assert_file "my_hyphenated-name/app/assets/stylesheets/my_hyphenated/name" + assert_file "my_hyphenated-name/app/assets/images/my_hyphenated/name" + assert_file "my_hyphenated-name/config/routes.rb", /MyHyphenated::Name::Engine.routes.draw do/ + assert_file "my_hyphenated-name/lib/my_hyphenated/name/version.rb", /module MyHyphenated\n module Name\n VERSION = '0.1.0'\n end\nend/ + assert_file "my_hyphenated-name/lib/my_hyphenated/name/engine.rb", /module MyHyphenated\n module Name\n class Engine < ::Rails::Engine\n isolate_namespace MyHyphenated::Name\n end\n end\nend/ + assert_file "my_hyphenated-name/lib/my_hyphenated/name.rb", /require "my_hyphenated\/name\/engine"/ + assert_file "my_hyphenated-name/test/dummy/config/routes.rb", /mount MyHyphenated::Name::Engine => "\/my_hyphenated-name"/ + assert_file "my_hyphenated-name/app/controllers/my_hyphenated/name/application_controller.rb", /module MyHyphenated\n module Name\n class ApplicationController < ActionController::Base\n end\n end\nend/ + assert_file "my_hyphenated-name/app/jobs/my_hyphenated/name/application_job.rb", /module MyHyphenated\n module Name\n class ApplicationJob < ActiveJob::Base/ + assert_file "my_hyphenated-name/app/helpers/my_hyphenated/name/application_helper.rb", /module MyHyphenated\n module Name\n module ApplicationHelper\n end\n end\nend/ + assert_file "my_hyphenated-name/app/views/layouts/my_hyphenated/name/application.html.erb" do |contents| + assert_match "<title>My hyphenated name</title>", contents + assert_match(/stylesheet_link_tag\s+['"]my_hyphenated\/name\/application['"]/, contents) + assert_match(/javascript_include_tag\s+['"]my_hyphenated\/name\/application['"]/, contents) + end + end + + def test_create_mountable_application_with_mountable_option_and_multiple_hypenates_in_name + run_generator [File.join(destination_root, "deep-hyphenated-name"), "--mountable"] + assert_file "deep-hyphenated-name/app/assets/javascripts/deep/hyphenated/name" + assert_file "deep-hyphenated-name/app/assets/stylesheets/deep/hyphenated/name" + assert_file "deep-hyphenated-name/app/assets/images/deep/hyphenated/name" + assert_file "deep-hyphenated-name/config/routes.rb", /Deep::Hyphenated::Name::Engine.routes.draw do/ + assert_file "deep-hyphenated-name/lib/deep/hyphenated/name/version.rb", /module Deep\n module Hyphenated\n module Name\n VERSION = '0.1.0'\n end\n end\nend/ + assert_file "deep-hyphenated-name/lib/deep/hyphenated/name/engine.rb", /module Deep\n module Hyphenated\n module Name\n class Engine < ::Rails::Engine\n isolate_namespace Deep::Hyphenated::Name\n end\n end\n end\nend/ + assert_file "deep-hyphenated-name/lib/deep/hyphenated/name.rb", /require "deep\/hyphenated\/name\/engine"/ + assert_file "deep-hyphenated-name/test/dummy/config/routes.rb", /mount Deep::Hyphenated::Name::Engine => "\/deep-hyphenated-name"/ + assert_file "deep-hyphenated-name/app/controllers/deep/hyphenated/name/application_controller.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationController < ActionController::Base\n end\n end\n end\nend/ + assert_file "deep-hyphenated-name/app/jobs/deep/hyphenated/name/application_job.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationJob < ActiveJob::Base/ + assert_file "deep-hyphenated-name/app/helpers/deep/hyphenated/name/application_helper.rb", /module Deep\n module Hyphenated\n module Name\n module ApplicationHelper\n end\n end\n end\nend/ + assert_file "deep-hyphenated-name/app/views/layouts/deep/hyphenated/name/application.html.erb" do |contents| + assert_match "<title>Deep hyphenated name</title>", contents + assert_match(/stylesheet_link_tag\s+['"]deep\/hyphenated\/name\/application['"]/, contents) + assert_match(/javascript_include_tag\s+['"]deep\/hyphenated\/name\/application['"]/, contents) + end + end + + def test_creating_gemspec + run_generator + assert_file "bukkits.gemspec", /s.name\s+= "bukkits"/ + assert_file "bukkits.gemspec", /s.files = Dir\["\{app,config,db,lib\}\/\*\*\/\*", "MIT-LICENSE", "Rakefile", "README\.rdoc"\]/ + assert_file "bukkits.gemspec", /s.version\s+ = Bukkits::VERSION/ + end + + def test_usage_of_engine_commands + run_generator [destination_root, "--full"] + assert_file "bin/rails", /ENGINE_PATH = File.expand_path\('..\/..\/lib\/bukkits\/engine', __FILE__\)/ + assert_file "bin/rails", /ENGINE_ROOT = File.expand_path\('..\/..', __FILE__\)/ + assert_file "bin/rails", /require 'rails\/all'/ + assert_file "bin/rails", /require 'rails\/engine\/commands'/ + end + + def test_shebang + run_generator [destination_root, "--full"] + assert_file "bin/rails", /#!\/usr\/bin\/env ruby/ + end + + def test_passing_dummy_path_as_a_parameter + run_generator [destination_root, "--dummy_path", "spec/dummy"] + assert_file "spec/dummy" + assert_file "spec/dummy/config/application.rb" + assert_no_file "test/dummy" + assert_file "test/test_helper.rb" do |content| + assert_match(/require.+spec\/dummy\/config\/environment/, content) + assert_match(/ActiveRecord::Migrator\.migrations_paths.+spec\/dummy\/db\/migrate/, content) + end + end + + def test_creating_dummy_application_with_different_name + run_generator [destination_root, "--dummy_path", "spec/fake"] + assert_file "spec/fake" + assert_file "spec/fake/config/application.rb" + assert_no_file "test/dummy" + assert_file "test/test_helper.rb" do |content| + assert_match(/require.+spec\/fake\/config\/environment/, content) + assert_match(/ActiveRecord::Migrator\.migrations_paths.+spec\/fake\/db\/migrate/, content) + end + end + + def test_creating_dummy_without_tests_but_with_dummy_path + run_generator [destination_root, "--dummy_path", "spec/dummy", "--skip-test"] + assert_file "spec/dummy" + assert_file "spec/dummy/config/application.rb" + assert_no_file "test" + assert_no_file "test/test_helper.rb" + assert_file '.gitignore' do |contents| + assert_match(/spec\/dummy/, contents) + end + end + + def test_ensure_that_gitignore_can_be_generated_from_a_template_for_dummy_path + FileUtils.cd(Rails.root) + run_generator([destination_root, "--dummy_path", "spec/dummy", "--skip-test"]) + assert_file ".gitignore" do |contents| + assert_match(/spec\/dummy/, contents) + end + end + + def test_unnecessary_files_are_not_generated_in_dummy_application + run_generator + assert_no_file 'test/dummy/.gitignore' + assert_no_file 'test/dummy/db/seeds.rb' + assert_no_file 'test/dummy/Gemfile' + assert_no_file 'test/dummy/public/robots.txt' + assert_no_file 'test/dummy/README.md' + assert_no_directory 'test/dummy/lib/tasks' + assert_no_directory 'test/dummy/doc' + assert_no_directory 'test/dummy/test' + assert_no_directory 'test/dummy/vendor' + end + + def test_skipping_test_files + run_generator [destination_root, "--skip-test"] + assert_no_file "test" + assert_file '.gitignore' do |contents| + assert_no_match(/test\dummy/, contents) + end + end + + def test_skipping_gemspec + run_generator [destination_root, "--skip-gemspec"] + assert_no_file "bukkits.gemspec" + assert_file "Gemfile" do |contents| + assert_no_match('gemspec', contents) + assert_match(/gem 'rails', '~> #{Rails.version}'/, contents) + assert_match_sqlite3(contents) + assert_no_match(/# gem "jquery-rails"/, contents) + end + end + + def test_skipping_gemspec_in_full_mode + run_generator [destination_root, "--skip-gemspec", "--full"] + assert_no_file "bukkits.gemspec" + assert_file "Gemfile" do |contents| + assert_no_match('gemspec', contents) + assert_match(/gem 'rails', '~> #{Rails.version}'/, contents) + assert_match_sqlite3(contents) + end + end + + def test_creating_plugin_in_app_directory_adds_gemfile_entry + # simulate application existence + gemfile_path = "#{Rails.root}/Gemfile" + Object.const_set('APP_PATH', Rails.root) + FileUtils.touch gemfile_path + + run_generator + + assert_file gemfile_path, /gem 'bukkits', path: 'tmp\/bukkits'/ + ensure + Object.send(:remove_const, 'APP_PATH') + FileUtils.rm gemfile_path + end + + def test_skipping_gemfile_entry + # simulate application existence + gemfile_path = "#{Rails.root}/Gemfile" + Object.const_set('APP_PATH', Rails.root) + FileUtils.touch gemfile_path + + run_generator [destination_root, "--skip-gemfile-entry"] + + assert_file gemfile_path do |contents| + assert_no_match(/gem 'bukkits', path: 'tmp\/bukkits'/, contents) + end + ensure + Object.send(:remove_const, 'APP_PATH') + FileUtils.rm gemfile_path + end + + def test_generating_controller_inside_mountable_engine + run_generator [destination_root, "--mountable"] + + capture(:stdout) do + `#{destination_root}/bin/rails g controller admin/dashboard foo` + end + + assert_file "config/routes.rb" do |contents| + assert_match(/namespace :admin/, contents) + assert_no_match(/namespace :bukkit/, contents) + end + end + + def test_git_name_and_email_in_gemspec_file + name = `git config user.name`.chomp rescue "TODO: Write your name" + email = `git config user.email`.chomp rescue "TODO: Write your email address" + + run_generator + assert_file "bukkits.gemspec" do |contents| + assert_match name, contents + assert_match email, contents + end + end + + def test_git_name_in_license_file + name = `git config user.name`.chomp rescue "TODO: Write your name" + + run_generator + assert_file "MIT-LICENSE" do |contents| + assert_match name, contents + end + end + + def test_no_details_from_git_when_skip_git + name = "TODO: Write your name" + email = "TODO: Write your email address" + + run_generator [destination_root, '--skip-git'] + assert_file "MIT-LICENSE" do |contents| + assert_match name, contents + end + assert_file "bukkits.gemspec" do |contents| + assert_match name, contents + assert_match email, contents + end + end + + def test_skipping_useless_folders_generation_for_api_engines + ['--full', '--mountable'].each do |option| + run_generator [destination_root, option, '--api'] + + assert_no_directory "app/assets" + assert_no_directory "app/helpers" + assert_no_directory "app/views" + + FileUtils.rm_rf destination_root + end + end + + def test_application_controller_parent_for_mountable_api_plugins + run_generator [destination_root, '--mountable', '--api'] + + assert_file "app/controllers/bukkits/application_controller.rb" do |content| + assert_match "ApplicationController < ActionController::API", content + end + end + + def test_dummy_api_application_for_api_plugins + run_generator [destination_root, '--api'] + + assert_file "test/dummy/config/application.rb" do |content| + assert_match "config.api_only = true", content + end + end + + + def test_api_generators_configuration_for_api_engines + run_generator [destination_root, '--full', '--api'] + + assert_file "lib/bukkits/engine.rb" do |content| + assert_match "config.generators.api_only = true", content + end + end + + def test_scaffold_generator_for_mountable_api_plugins + run_generator [destination_root, '--mountable', '--api'] + + capture(:stdout) do + `#{destination_root}/bin/rails g scaffold article` + end + + assert_file "app/models/bukkits/article.rb" + assert_file "app/controllers/bukkits/articles_controller.rb" do |content| + assert_match "only: [:show, :update, :destroy]", content + end + + assert_no_directory "app/assets" + assert_no_directory "app/helpers" + assert_no_directory "app/views" + end + +protected + def action(*args, &block) + silence(:stdout){ generator.send(*args, &block) } + end + + def default_files + ::DEFAULT_PLUGIN_FILES + end + + def assert_match_sqlite3(contents) + if defined?(JRUBY_VERSION) + assert_match(/group :development do\n gem 'activerecord-jdbcsqlite3-adapter'\nend/, contents) + else + assert_match(/group :development do\n gem 'sqlite3'\nend/, contents) + end + end +end diff --git a/railties/test/generators/plugin_test_helper.rb b/railties/test/generators/plugin_test_helper.rb new file mode 100644 index 0000000000..96c1b1d31f --- /dev/null +++ b/railties/test/generators/plugin_test_helper.rb @@ -0,0 +1,24 @@ +require 'abstract_unit' +require 'tmpdir' + +module PluginTestHelper + def create_test_file(name, pass: true) + plugin_file "test/#{name}_test.rb", <<-RUBY + require 'test_helper' + + class #{name.camelize}Test < ActiveSupport::TestCase + def test_truth + puts "#{name.camelize}Test" + assert #{pass}, 'wups!' + end + end + RUBY + end + + def plugin_file(path, contents, mode: 'w') + FileUtils.mkdir_p File.dirname("#{plugin_path}/#{path}") + File.open("#{plugin_path}/#{path}", mode) do |f| + f.puts contents + end + end +end diff --git a/railties/test/generators/plugin_test_runner_test.rb b/railties/test/generators/plugin_test_runner_test.rb new file mode 100644 index 0000000000..716728819e --- /dev/null +++ b/railties/test/generators/plugin_test_runner_test.rb @@ -0,0 +1,104 @@ +require 'generators/plugin_test_helper' + +class PluginTestRunnerTest < ActiveSupport::TestCase + include PluginTestHelper + + def setup + @destination_root = Dir.mktmpdir('bukkits') + Dir.chdir(@destination_root) { `bundle exec rails plugin new bukkits --skip-bundle` } + plugin_file 'test/dummy/db/schema.rb', '' + end + + def teardown + FileUtils.rm_rf(@destination_root) + end + + def test_run_single_file + create_test_file 'foo' + create_test_file 'bar' + assert_match "1 runs, 1 assertions, 0 failures", run_test_command("test/foo_test.rb") + end + + def test_run_multiple_files + create_test_file 'foo' + create_test_file 'bar' + assert_match "2 runs, 2 assertions, 0 failures", run_test_command("test/foo_test.rb test/bar_test.rb") + end + + def test_mix_files_and_line_filters + create_test_file 'account' + plugin_file 'test/post_test.rb', <<-RUBY + require 'test_helper' + + class PostTest < ActiveSupport::TestCase + def test_post + puts 'PostTest' + assert true + end + + def test_line_filter_does_not_run_this + assert true + end + end + RUBY + + run_test_command('test/account_test.rb test/post_test.rb:4').tap do |output| + assert_match 'AccountTest', output + assert_match 'PostTest', output + assert_match '2 runs, 2 assertions', output + end + end + + def test_multiple_line_filters + create_test_file 'account' + create_test_file 'post' + + run_test_command('test/account_test.rb:4 test/post_test.rb:4').tap do |output| + assert_match 'AccountTest', output + assert_match 'PostTest', output + end + end + + def test_line_filter_without_line_runs_all_tests + create_test_file 'account' + + run_test_command('test/account_test.rb:').tap do |output| + assert_match 'AccountTest', output + end + end + + def test_output_inline_by_default + create_test_file 'post', pass: false + + output = run_test_command('test/post_test.rb') + assert_match %r{Running:\n\nPostTest\nF\n\nwups!\n\nbin/test (/private)?#{plugin_path}/test/post_test.rb:6}, output + end + + def test_only_inline_failure_output + create_test_file 'post', pass: false + + output = run_test_command('test/post_test.rb') + assert_match %r{Finished in.*\n\n1 runs, 1 assertions}, output + end + + def test_fail_fast + create_test_file 'post', pass: false + + assert_match(/Interrupt/, + capture(:stderr) { run_test_command('test/post_test.rb --fail-fast') }) + end + + def test_raise_error_when_specified_file_does_not_exist + error = capture(:stderr) { run_test_command('test/not_exists.rb') } + assert_match(%r{cannot load such file.+test/not_exists\.rb}, error) + end + + private + def plugin_path + "#{@destination_root}/bukkits" + end + + def run_test_command(arguments) + Dir.chdir(plugin_path) { `bin/test #{arguments}` } + end +end diff --git a/railties/test/generators/resource_generator_test.rb b/railties/test/generators/resource_generator_test.rb new file mode 100644 index 0000000000..581d80d60e --- /dev/null +++ b/railties/test/generators/resource_generator_test.rb @@ -0,0 +1,88 @@ +require 'generators/generators_test_helper' +require 'rails/generators/rails/resource/resource_generator' + +class ResourceGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper + arguments %w(account) + + setup :copy_routes + + def test_help_with_inherited_options + content = run_generator ["--help"] + assert_match(/ActiveRecord options:/, content) + assert_match(/TestUnit options:/, content) + end + + def test_files_from_inherited_invocation + run_generator + + %w( + app/models/account.rb + test/models/account_test.rb + test/fixtures/accounts.yml + ).each { |path| assert_file path } + + assert_migration "db/migrate/create_accounts.rb" + end + + def test_inherited_invocations_with_attributes + run_generator ["account", "name:string"] + assert_migration "db/migrate/create_accounts.rb", /t.string :name/ + end + + def test_resource_controller_with_pluralized_class_name + run_generator + assert_file "app/controllers/accounts_controller.rb", /class AccountsController < ApplicationController/ + assert_file "test/controllers/accounts_controller_test.rb", /class AccountsControllerTest < ActionController::TestCase/ + + assert_file "app/helpers/accounts_helper.rb", /module AccountsHelper/ + end + + def test_resource_controller_with_actions + run_generator ["account", "--actions", "index", "new"] + + assert_file "app/controllers/accounts_controller.rb" do |controller| + assert_instance_method :index, controller + assert_instance_method :new, controller + end + + assert_file "app/views/accounts/index.html.erb" + assert_file "app/views/accounts/new.html.erb" + end + + def test_resource_routes_are_added + run_generator + + assert_file "config/routes.rb" do |route| + assert_match(/resources :accounts$/, route) + end + end + + def test_plural_names_are_singularized + content = run_generator ["accounts".freeze] + assert_file "app/models/account.rb", /class Account < ActiveRecord::Base/ + assert_file "test/models/account_test.rb", /class AccountTest/ + assert_match(/\[WARNING\] The model name 'accounts' was recognized as a plural, using the singular 'account' instead\. Override with --force-plural or setup custom inflection rules for this noun before running the generator\./, content) + end + + def test_plural_names_can_be_forced + content = run_generator ["accounts", "--force-plural"] + assert_file "app/models/accounts.rb", /class Accounts < ActiveRecord::Base/ + assert_file "test/models/accounts_test.rb", /class AccountsTest/ + assert_no_match(/\[WARNING\]/, content) + end + + def test_mass_nouns_do_not_throw_warnings + content = run_generator ["sheep".freeze] + assert_no_match(/\[WARNING\]/, content) + end + + def test_route_is_removed_on_revoke + run_generator + run_generator ["account"], behavior: :revoke + + assert_file "config/routes.rb" do |route| + assert_no_match(/resources :accounts$/, route) + end + end +end diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb new file mode 100644 index 0000000000..b4ba101017 --- /dev/null +++ b/railties/test/generators/scaffold_controller_generator_test.rb @@ -0,0 +1,246 @@ +require 'generators/generators_test_helper' +require 'rails/generators/rails/scaffold_controller/scaffold_controller_generator' + +module Unknown + module Generators + end +end + +class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper + arguments %w(User name:string age:integer) + + def test_controller_skeleton_is_created + run_generator + + assert_file "app/controllers/users_controller.rb" do |content| + assert_match(/class UsersController < ApplicationController/, content) + + assert_instance_method :index, content do |m| + assert_match(/@users = User\.all/, m) + end + + assert_instance_method :show, content + + assert_instance_method :new, content do |m| + assert_match(/@user = User\.new/, m) + end + + assert_instance_method :edit, content + + assert_instance_method :create, content do |m| + assert_match(/@user = User\.new\(user_params\)/, m) + assert_match(/@user\.save/, m) + end + + assert_instance_method :update, content do |m| + assert_match(/@user\.update\(user_params\)/, m) + end + + assert_instance_method :destroy, content do |m| + assert_match(/@user\.destroy/, m) + assert_match(/User was successfully destroyed/, m) + end + + assert_instance_method :set_user, content do |m| + assert_match(/@user = User\.find\(params\[:id\]\)/, m) + end + + assert_match(/def user_params/, content) + assert_match(/params\.require\(:user\)\.permit\(:name, :age\)/, content) + end + end + + def test_dont_use_require_or_permit_if_there_are_no_attributes + run_generator ["User"] + + assert_file "app/controllers/users_controller.rb" do |content| + assert_match(/def user_params/, content) + assert_match(/params\.fetch\(:user, \{\}\)/, content) + end + end + + def test_controller_permit_references_attributes + run_generator ["LineItem", "product:references", "cart:belongs_to"] + + assert_file "app/controllers/line_items_controller.rb" do |content| + assert_match(/def line_item_params/, content) + assert_match(/params\.require\(:line_item\)\.permit\(:product_id, :cart_id\)/, content) + end + end + + def test_controller_permit_polymorphic_references_attributes + run_generator ["LineItem", "product:references{polymorphic}"] + + assert_file "app/controllers/line_items_controller.rb" do |content| + assert_match(/def line_item_params/, content) + assert_match(/params\.require\(:line_item\)\.permit\(:product_id, :product_type\)/, content) + end + end + + def test_helper_are_invoked_with_a_pluralized_name + run_generator + assert_file "app/helpers/users_helper.rb", /module UsersHelper/ + end + + def test_views_are_generated + run_generator + + %w(index edit new show).each do |view| + assert_file "app/views/users/#{view}.html.erb" + end + assert_no_file "app/views/layouts/users.html.erb" + end + + def test_index_page_have_notice + run_generator + + %w(index show).each do |view| + assert_file "app/views/users/#{view}.html.erb", /notice/ + end + end + + def test_functional_tests + run_generator ["User", "name:string", "age:integer", "organization:references{polymorphic}"] + + 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) + end + end + + def test_functional_tests_without_attributes + run_generator ["User"] + + 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: \{ \} \}/, content) + assert_match(/patch :update, params: \{ id: @user, user: \{ \} \}/, content) + end + end + + def test_skip_helper_if_required + run_generator ["User", "name:string", "age:integer", "--no-helper"] + assert_no_file "app/helpers/users_helper.rb" + end + + def test_skip_layout_if_required + run_generator ["User", "name:string", "age:integer", "--no-layout"] + assert_no_file "app/views/layouts/users.html.erb" + end + + def test_default_orm_is_used + run_generator ["User", "--orm=unknown"] + + assert_file "app/controllers/users_controller.rb" do |content| + assert_match(/class UsersController < ApplicationController/, content) + + assert_instance_method :index, content do |m| + assert_match(/@users = User\.all/, m) + end + end + end + + def test_customized_orm_is_used + klass = Class.new(Rails::Generators::ActiveModel) do + def self.all(klass) + "#{klass}.find(:all)" + end + end + + Unknown::Generators.const_set(:ActiveModel, klass) + run_generator ["User", "--orm=unknown"] + + assert_file "app/controllers/users_controller.rb" do |content| + assert_match(/class UsersController < ApplicationController/, content) + + assert_instance_method :index, content do |m| + assert_match(/@users = User\.find\(:all\)/, m) + assert_no_match(/@users = User\.all/, m) + end + end + ensure + Unknown::Generators.send :remove_const, :ActiveModel + end + + def test_model_name_option + run_generator ["Admin::User", "--model-name=User"] + assert_file "app/controllers/admin/users_controller.rb" do |content| + assert_instance_method :index, content do |m| + assert_match("@users = User.all", m) + end + end + end + + def test_controller_tests_pass_by_default_inside_mountable_engine + Dir.chdir(destination_root) { `bundle exec rails plugin new bukkits --mountable` } + + engine_path = File.join(destination_root, "bukkits") + + Dir.chdir(engine_path) do + quietly { `bin/rails g controller dashboard foo` } + assert_match(/2 runs, 2 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`) + end + end + + def test_controller_tests_pass_by_default_inside_full_engine + Dir.chdir(destination_root) { `bundle exec rails plugin new bukkits --full` } + + engine_path = File.join(destination_root, "bukkits") + + Dir.chdir(engine_path) do + quietly { `bin/rails g controller dashboard foo` } + assert_match(/2 runs, 2 assertions, 0 failures, 0 errors/, `bin/rails 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 new file mode 100644 index 0000000000..0c3808a9a0 --- /dev/null +++ b/railties/test/generators/scaffold_generator_test.rb @@ -0,0 +1,536 @@ +require 'generators/generators_test_helper' +require 'rails/generators/rails/scaffold/scaffold_generator' + +class ScaffoldGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper + arguments %w(product_line title:string product:belongs_to user:references) + + setup :copy_routes + + def test_scaffold_on_invoke + run_generator + + # 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_instance_method :index, content do |m| + assert_match(/@product_lines = ProductLine\.all/, m) + end + + assert_instance_method :show, content + + assert_instance_method :new, content do |m| + assert_match(/@product_line = ProductLine\.new/, m) + end + + assert_instance_method :edit, content + + assert_instance_method :create, content do |m| + assert_match(/@product_line = ProductLine\.new\(product_line_params\)/, m) + assert_match(/@product_line\.save/, m) + end + + assert_instance_method :update, content do |m| + assert_match(/@product_line\.update\(product_line_params\)/, m) + end + + assert_instance_method :destroy, content do |m| + assert_match(/@product_line\.destroy/, m) + end + + assert_instance_method :set_product_line, content do |m| + assert_match(/@product_line = ProductLine\.find\(params\[:id\]\)/, 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) + end + + # Views + assert_no_file "app/views/layouts/product_lines.html.erb" + + %w(index show).each do |view| + assert_file "app/views/product_lines/#{view}.html.erb" + end + + %w(edit new).each do |view| + assert_file "app/views/product_lines/#{view}.html.erb", /render 'form', product_line: @product_line/ + end + + assert_file "app/views/product_lines/_form.html.erb" do |test| + assert_match 'product_line', test + assert_no_match '@product_line', test + end + + # Helpers + assert_file "app/helpers/product_lines_helper.rb" + + # Assets + assert_file "app/assets/stylesheets/scaffold.css" + assert_file "app/assets/javascripts/product_lines.js" + 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"] + + assert_file "test/controllers/product_lines_controller_test.rb" do |content| + assert_match(/class ProductLinesControllerTest < ActionController::TestCase/, content) + assert_match(/test "should get index"/, content) + assert_match(/post :create, params: \{ product_line: \{ \} \}/, content) + assert_match(/patch :update, params: \{ id: @product_line, product_line: \{ \} \}/, content) + end + end + + def test_scaffold_on_revoke + run_generator + run_generator ["product_line"], behavior: :revoke + + # Model + assert_no_file "app/models/product_line.rb" + assert_no_file "test/models/product_line_test.rb" + assert_no_file "test/fixtures/product_lines.yml" + assert_no_migration "db/migrate/create_product_lines.rb" + + # Route + assert_file "config/routes.rb" do |route| + assert_no_match(/resources :product_lines$/, route) + end + + # Controller + assert_no_file "app/controllers/product_lines_controller.rb" + assert_no_file "test/controllers/product_lines_controller_test.rb" + + # Views + assert_no_file "app/views/product_lines" + assert_no_file "app/views/layouts/product_lines.html.erb" + + # Helpers + assert_no_file "app/helpers/product_lines_helper.rb" + + # Assets + assert_file "app/assets/stylesheets/scaffold.css", /:visited/ + assert_no_file "app/assets/javascripts/product_lines.js" + assert_no_file "app/assets/stylesheets/product_lines.css" + end + + def test_scaffold_with_namespace_on_invoke + run_generator [ "admin/role", "name:string", "description:string" ] + + # Model + assert_file "app/models/admin.rb", /module Admin/ + assert_file "app/models/admin/role.rb", /class Admin::Role < ActiveRecord::Base/ + assert_file "test/models/admin/role_test.rb", /class Admin::RoleTest < ActiveSupport::TestCase/ + assert_file "test/fixtures/admin/roles.yml" + assert_migration "db/migrate/create_admin_roles.rb" + + # Route + assert_file "config/routes.rb" do |route| + assert_match(/^ namespace :admin do\n resources :roles\n end$/, route) + end + + # Controller + assert_file "app/controllers/admin/roles_controller.rb" do |content| + assert_match(/class Admin::RolesController < ApplicationController/, content) + + assert_instance_method :index, content do |m| + assert_match(/@admin_roles = Admin::Role\.all/, m) + end + + assert_instance_method :show, content + + assert_instance_method :new, content do |m| + assert_match(/@admin_role = Admin::Role\.new/, m) + end + + assert_instance_method :edit, content + + assert_instance_method :create, content do |m| + assert_match(/@admin_role = Admin::Role\.new\(admin_role_params\)/, m) + assert_match(/@admin_role\.save/, m) + end + + assert_instance_method :update, content do |m| + assert_match(/@admin_role\.update\(admin_role_params\)/, m) + end + + assert_instance_method :destroy, content do |m| + assert_match(/@admin_role\.destroy/, m) + end + + assert_instance_method :set_admin_role, content do |m| + assert_match(/@admin_role = Admin::Role\.find\(params\[:id\]\)/, m) + end + end + + assert_file "test/controllers/admin/roles_controller_test.rb", + /class Admin::RolesControllerTest < ActionController::TestCase/ + + # Views + %w(index edit new show _form).each do |view| + assert_file "app/views/admin/roles/#{view}.html.erb" + end + assert_no_file "app/views/layouts/admin/roles.html.erb" + + # Helpers + assert_file "app/helpers/admin/roles_helper.rb" + + # Assets + assert_file "app/assets/stylesheets/scaffold.css", /:visited/ + assert_file "app/assets/javascripts/admin/roles.js" + assert_file "app/assets/stylesheets/admin/roles.css" + end + + def test_scaffold_with_namespace_on_revoke + run_generator [ "admin/role", "name:string", "description:string" ] + run_generator [ "admin/role" ], :behavior => :revoke + + # Model + assert_file "app/models/admin.rb" # ( should not be remove ) + assert_no_file "app/models/admin/role.rb" + assert_no_file "test/models/admin/role_test.rb" + assert_no_file "test/fixtures/admin/roles.yml" + assert_no_migration "db/migrate/create_admin_roles.rb" + + # Route + assert_file "config/routes.rb" do |route| + assert_no_match(/namespace :admin do resources :roles end$/, route) + end + + # Controller + assert_no_file "app/controllers/admin/roles_controller.rb" + assert_no_file "test/controllers/admin/roles_controller_test.rb" + + # Views + assert_no_file "app/views/admin/roles" + assert_no_file "app/views/layouts/admin/roles.html.erb" + + # Helpers + assert_no_file "app/helpers/admin/roles_helper.rb" + + # Assets + assert_file "app/assets/stylesheets/scaffold.css" + assert_no_file "app/assets/javascripts/admin/roles.js" + assert_no_file "app/assets/stylesheets/admin/roles.css" + end + + def test_scaffold_generator_on_revoke_does_not_mutilate_legacy_map_parameter + run_generator + + # Add a |map| parameter to the routes block manually + route_path = File.expand_path("config/routes.rb", destination_root) + content = File.read(route_path).gsub(/\.routes\.draw do/) do |match| + "#{match} |map|" + end + File.open(route_path, "wb") { |file| file.write(content) } + + run_generator ["product_line"], :behavior => :revoke + + assert_file "config/routes.rb", /\.routes\.draw do\s*\|map\|\s*$/ + end + + def test_scaffold_generator_on_revoke_does_not_mutilate_routes + run_generator + + route_path = File.expand_path("config/routes.rb", destination_root) + content = File.read(route_path) + + # Remove all of the comments and blank lines from the routes file + content.gsub!(/^ \#.*\n/, '') + content.gsub!(/^\n/, '') + + File.open(route_path, "wb") { |file| file.write(content) } + assert_file "config/routes.rb", /\.routes\.draw do\n resources :product_lines\nend\n\z/ + + run_generator ["product_line"], :behavior => :revoke + + assert_file "config/routes.rb", /\.routes\.draw do\nend\n\z/ + end + + def test_scaffold_generator_ignores_commented_routes + run_generator ["product"] + assert_file "config/routes.rb", /\.routes\.draw do\n resources :products\n/ + end + + def test_scaffold_generator_no_assets_with_switch_no_assets + run_generator [ "posts", "--no-assets" ] + assert_no_file "app/assets/stylesheets/scaffold.css" + assert_no_file "app/assets/javascripts/posts.js" + assert_no_file "app/assets/stylesheets/posts.css" + end + + def test_scaffold_generator_no_assets_with_switch_assets_false + run_generator [ "posts", "--assets=false" ] + assert_no_file "app/assets/stylesheets/scaffold.css" + assert_no_file "app/assets/javascripts/posts.js" + assert_no_file "app/assets/stylesheets/posts.css" + end + + def test_scaffold_generator_no_scaffold_stylesheet_with_switch_no_scaffold_stylesheet + run_generator [ "posts", "--no-scaffold-stylesheet" ] + assert_no_file "app/assets/stylesheets/scaffold.css" + assert_file "app/assets/javascripts/posts.js" + assert_file "app/assets/stylesheets/posts.css" + end + + def test_scaffold_generator_no_scaffold_stylesheet_with_switch_scaffold_stylesheet_false + run_generator [ "posts", "--scaffold-stylesheet=false" ] + assert_no_file "app/assets/stylesheets/scaffold.css" + assert_file "app/assets/javascripts/posts.js" + assert_file "app/assets/stylesheets/posts.css" + end + + def test_scaffold_generator_with_switch_resource_route_false + run_generator [ "posts", "--resource-route=false" ] + assert_file "config/routes.rb" do |route| + assert_no_match(/resources :posts$/, route) + end + end + + def test_scaffold_generator_no_helper_with_switch_no_helper + output = run_generator [ "posts", "--no-helper" ] + + assert_no_match(/error/, output) + assert_no_file "app/helpers/posts_helper.rb" + end + + def test_scaffold_generator_no_helper_with_switch_helper_false + output = run_generator [ "posts", "--helper=false" ] + + assert_no_match(/error/, output) + assert_no_file "app/helpers/posts_helper.rb" + end + + def test_scaffold_generator_no_stylesheets + run_generator [ "posts", "--no-stylesheets" ] + assert_no_file "app/assets/stylesheets/scaffold.css" + assert_file "app/assets/javascripts/posts.js" + assert_no_file "app/assets/stylesheets/posts.css" + end + + def test_scaffold_generator_no_javascripts + run_generator [ "posts", "--no-javascripts" ] + assert_file "app/assets/stylesheets/scaffold.css" + assert_no_file "app/assets/javascripts/posts.js" + assert_file "app/assets/stylesheets/posts.css" + end + + def test_scaffold_generator_outputs_error_message_on_missing_attribute_type + run_generator ["post", "title", "body:text", "author"] + + assert_migration "db/migrate/create_posts.rb" do |m| + assert_method :change, m do |up| + assert_match(/t\.string :title/, up) + assert_match(/t\.text :body/, up) + assert_match(/t\.string :author/, up) + end + end + end + + def test_scaffold_generator_belongs_to + run_generator ["account", "name", "currency:belongs_to"] + + assert_file "app/models/account.rb", /belongs_to :currency/ + + assert_migration "db/migrate/create_accounts.rb" do |m| + assert_method :change, m do |up| + assert_match(/t\.string :name/, up) + assert_match(/t\.belongs_to :currency/, up) + end + end + + assert_file "app/controllers/accounts_controller.rb" do |content| + assert_instance_method :account_params, content do |m| + assert_match(/permit\(:name, :currency_id\)/, m) + end + end + + assert_file "app/views/accounts/_form.html.erb" do |content| + assert_match(/^\W{4}<%= f\.text_field :name %>/, content) + assert_match(/^\W{4}<%= f\.text_field :currency_id %>/, content) + end + end + + def test_scaffold_generator_password_digest + run_generator ["user", "name", "password:digest"] + + assert_file "app/models/user.rb", /has_secure_password/ + + assert_migration "db/migrate/create_users.rb" do |m| + assert_method :change, m do |up| + assert_match(/t\.string :name/, up) + assert_match(/t\.string :password_digest/, up) + end + end + + assert_file "app/controllers/users_controller.rb" do |content| + assert_instance_method :user_params, content do |m| + assert_match(/permit\(:name, :password, :password_confirmation\)/, m) + end + end + + assert_file "app/views/users/_form.html.erb" do |content| + assert_match(/<%= f\.password_field :password %>/, content) + assert_match(/<%= f\.password_field :password_confirmation %>/, content) + end + + assert_file "app/views/users/index.html.erb" do |content| + assert_no_match(/password/, content) + end + + assert_file "app/views/users/show.html.erb" do |content| + assert_no_match(/password/, content) + end + + assert_file "test/controllers/users_controller_test.rb" do |content| + assert_match(/password: 'secret'/, content) + assert_match(/password_confirmation: 'secret'/, content) + end + + assert_file "test/fixtures/users.yml" do |content| + assert_match(/password_digest: <%= BCrypt::Password.create\('secret'\) %>/, content) + end + end + + def test_scaffold_tests_pass_by_default_inside_mountable_engine + Dir.chdir(destination_root) { `bundle exec rails plugin new bukkits --mountable` } + + engine_path = File.join(destination_root, "bukkits") + + Dir.chdir(engine_path) do + quietly do + `bin/rails g scaffold User name:string age:integer; + bundle exec rake db:migrate` + end + assert_match(/8 runs, 13 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`) + end + end + + def test_scaffold_tests_pass_by_default_inside_full_engine + Dir.chdir(destination_root) { `bundle exec rails plugin new bukkits --full` } + + engine_path = File.join(destination_root, "bukkits") + + Dir.chdir(engine_path) do + quietly do + `bin/rails g scaffold User name:string age:integer; + bundle exec rake db:migrate` + end + assert_match(/8 runs, 13 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`) + end + end + + def test_scaffold_tests_pass_by_default_inside_api_mountable_engine + Dir.chdir(destination_root) { `bundle exec rails plugin new bukkits --mountable --api` } + + engine_path = File.join(destination_root, "bukkits") + + Dir.chdir(engine_path) do + quietly do + `bin/rails g scaffold User name:string age:integer; + bundle exec rake db:migrate` + end + assert_match(/6 runs, 8 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`) + end + end + + def test_scaffold_tests_pass_by_default_inside_api_full_engine + Dir.chdir(destination_root) { `bundle exec rails plugin new bukkits --full --api` } + + engine_path = File.join(destination_root, "bukkits") + + Dir.chdir(engine_path) do + quietly do + `bin/rails g scaffold User name:string age:integer; + bundle exec rake db:migrate` + end + assert_match(/6 runs, 8 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`) + end + end +end diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb new file mode 100644 index 0000000000..acb78ec888 --- /dev/null +++ b/railties/test/generators/shared_generator_tests.rb @@ -0,0 +1,159 @@ +# +# Tests, setup, and teardown common to the application and plugin generator suites. +# +module SharedGeneratorTests + def setup + Rails.application = TestApp::Application + super + Rails::Generators::AppGenerator.instance_variable_set('@desc', nil) + + 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::Generators::AppGenerator.instance_variable_set('@desc', nil) + Rails.application = TestApp::Application.instance + end + + def test_skeleton_is_created + run_generator + + default_files.each { |path| assert_file path } + end + + def assert_generates_with_bundler(options = {}) + generator([destination_root], options) + + command_check = -> command do + @install_called ||= 0 + + case command + when 'install' + @install_called += 1 + assert_equal 1, @install_called, "install expected to be called once, but was called #{@install_called} times" + when 'exec spring binstub --all' + # Called when running tests with spring, let through unscathed. + end + end + + generator.stub :bundle_command, command_check do + quietly { generator.invoke_all } + end + end + + def test_generation_runs_bundle_install + assert_generates_with_bundler + end + + def test_plugin_new_generate_pretend + run_generator ["testapp", "--pretend"] + default_files.each{ |path| assert_no_file File.join("testapp",path) } + end + + def test_invalid_database_option_raises_an_error + content = capture(:stderr){ run_generator([destination_root, "-d", "unknown"]) } + assert_match(/Invalid value for \-\-database option/, content) + end + + def test_test_files_are_skipped_if_required + run_generator [destination_root, "--skip-test"] + assert_no_file "test" + end + + def test_name_collision_raises_an_error + reserved_words = %w[application destroy plugin runner test] + reserved_words.each do |reserved| + content = capture(:stderr){ run_generator [File.join(destination_root, reserved)] } + assert_match(/Invalid \w+ name #{reserved}. Please give a name which does not match one of the reserved rails words: application, destroy, plugin, runner, test\n/, content) + end + end + + def test_name_raises_an_error_if_name_already_used_constant + %w{ String Hash Class Module Set Symbol }.each do |ruby_class| + content = capture(:stderr){ run_generator [File.join(destination_root, ruby_class)] } + assert_match(/Invalid \w+ name #{ruby_class}, constant #{ruby_class} is already in use. Please choose another \w+ name.\n/, content) + end + end + + def test_shebang_is_added_to_rails_file + run_generator [destination_root, "--ruby", "foo/bar/baz", "--full"] + assert_file "bin/rails", /#!foo\/bar\/baz/ + end + + def test_shebang_when_is_the_same_as_default_use_env + run_generator [destination_root, "--ruby", Thor::Util.ruby_command, "--full"] + assert_file "bin/rails", /#!\/usr\/bin\/env/ + end + + def test_template_raises_an_error_with_invalid_path + quietly do + content = capture(:stderr){ run_generator([destination_root, "-m", "non/existent/path"]) } + + assert_match(/The template \[.*\] could not be loaded/, content) + assert_match(/non\/existent\/path/, content) + end + end + + def test_template_is_executed_when_supplied_an_https_path + path = "https://gist.github.com/josevalim/103208/raw/" + template = %{ say "It works!" } + template.instance_eval "def read; self; end" # Make the string respond to read + + check_open = -> *args do + assert_equal [ path, 'Accept' => 'application/x-thor-template' ], args + template + end + + generator([destination_root], template: path).stub(:open, check_open, template) do + quietly { assert_match(/It works!/, capture(:stdout) { generator.invoke_all }) } + end + end + + def test_dev_option + assert_generates_with_bundler dev: true + rails_path = File.expand_path('../../..', Rails.root) + assert_file 'Gemfile', /^gem\s+["']rails["'],\s+path:\s+["']#{Regexp.escape(rails_path)}["']$/ + end + + def test_edge_option + assert_generates_with_bundler edge: true + assert_file 'Gemfile', %r{^gem\s+["']rails["'],\s+github:\s+["']#{Regexp.escape("rails/rails")}["']$} + end + + def test_skip_gemfile + assert_not_called(generator([destination_root], skip_gemfile: true), :bundle_command) do + quietly { generator.invoke_all } + assert_no_file 'Gemfile' + end + end + + def test_skip_bundle + assert_not_called(generator([destination_root], skip_bundle: true), :bundle_command) do + quietly { generator.invoke_all } + # skip_bundle is only about running bundle install, ensure the Gemfile is still + # generated. + assert_file 'Gemfile' + end + end + + def test_skip_git + run_generator [destination_root, '--skip-git', '--full'] + assert_no_file('.gitignore') + assert_file('app/mailers/.keep') + end + + def test_skip_keeps + run_generator [destination_root, '--skip-keeps', '--full'] + + assert_file '.gitignore' do |content| + assert_no_match(/\.keep/, content) + end + + assert_no_file('app/mailers/.keep') + end +end diff --git a/railties/test/generators/task_generator_test.rb b/railties/test/generators/task_generator_test.rb new file mode 100644 index 0000000000..d5bd44b9db --- /dev/null +++ b/railties/test/generators/task_generator_test.rb @@ -0,0 +1,24 @@ +require 'generators/generators_test_helper' +require 'rails/generators/rails/task/task_generator' + +class TaskGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper + arguments %w(feeds foo bar) + + def test_task_is_created + run_generator + assert_file "lib/tasks/feeds.rake" do |content| + assert_match(/namespace :feeds/, content) + assert_match(/task foo:/, content) + assert_match(/task bar:/, content) + end + end + + def test_task_on_revoke + task_path = 'lib/tasks/feeds.rake' + run_generator + assert_file task_path + run_generator ['feeds'], behavior: :revoke + assert_no_file task_path + end +end diff --git a/railties/test/generators/test_runner_in_engine_test.rb b/railties/test/generators/test_runner_in_engine_test.rb new file mode 100644 index 0000000000..641c5d0835 --- /dev/null +++ b/railties/test/generators/test_runner_in_engine_test.rb @@ -0,0 +1,31 @@ +require 'generators/plugin_test_helper' + +class TestRunnerInEngineTest < ActiveSupport::TestCase + include PluginTestHelper + + def setup + @destination_root = Dir.mktmpdir('bukkits') + Dir.chdir(@destination_root) { `bundle exec rails plugin new bukkits --full --skip-bundle` } + plugin_file 'test/dummy/db/schema.rb', '' + end + + def teardown + FileUtils.rm_rf(@destination_root) + end + + def test_rerun_snippet_is_relative_path + create_test_file 'post', pass: false + + output = run_test_command('test/post_test.rb') + assert_match %r{Running:\n\nPostTest\nF\n\nwups!\n\nbin/rails test test/post_test.rb:6}, output + end + + private + def plugin_path + "#{@destination_root}/bukkits" + end + + def run_test_command(arguments) + Dir.chdir(plugin_path) { `bin/rails test #{arguments}` } + end +end diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb new file mode 100644 index 0000000000..291415858c --- /dev/null +++ b/railties/test/generators_test.rb @@ -0,0 +1,246 @@ +require 'generators/generators_test_helper' +require 'rails/generators/rails/model/model_generator' +require 'rails/generators/test_unit/model/model_generator' + +class GeneratorsTest < Rails::Generators::TestCase + include GeneratorsTestHelper + + def setup + @path = File.expand_path("lib", Rails.root) + $LOAD_PATH.unshift(@path) + end + + def teardown + $LOAD_PATH.delete(@path) + end + + def test_simple_invoke + assert File.exist?(File.join(@path, 'generators', 'model_generator.rb')) + assert_called_with(TestUnit::Generators::ModelGenerator, :start, [["Account"], {}]) do + Rails::Generators.invoke("test_unit:model", ["Account"]) + end + end + + def test_invoke_when_generator_is_not_found + name = :unknown + output = capture(:stdout){ Rails::Generators.invoke name } + assert_match "Could not find generator '#{name}'", output + assert_match "`rails generate --help`", output + end + + def test_generator_suggestions + name = :migrationz + output = capture(:stdout){ Rails::Generators.invoke name } + assert_match "Maybe you meant 'migration'", output + end + + def test_generator_multiple_suggestions + name = :tas + output = capture(:stdout){ Rails::Generators.invoke name } + assert_match "Maybe you meant 'task', 'job' or", output + end + + def test_help_when_a_generator_with_required_arguments_is_invoked_without_arguments + output = capture(:stdout){ Rails::Generators.invoke :model, [] } + assert_match(/Description:/, output) + end + + def test_should_give_higher_preference_to_rails_generators + assert File.exist?(File.join(@path, 'generators', 'model_generator.rb')) + assert_called_with(Rails::Generators::ModelGenerator, :start, [["Account"], {}]) do + warnings = capture(:stderr){ Rails::Generators.invoke :model, ["Account"] } + assert warnings.empty? + end + end + + def test_invoke_with_default_values + assert_called_with(Rails::Generators::ModelGenerator, :start, [["Account"], {}]) do + Rails::Generators.invoke :model, ["Account"] + end + end + + def test_invoke_with_config_values + assert_called_with(Rails::Generators::ModelGenerator, :start, [["Account"], behavior: :skip]) do + Rails::Generators.invoke :model, ["Account"], behavior: :skip + end + end + + def test_find_by_namespace + klass = Rails::Generators.find_by_namespace("rails:model") + assert klass + assert_equal "rails:model", klass.namespace + end + + def test_find_by_namespace_with_base + klass = Rails::Generators.find_by_namespace(:model, :rails) + assert klass + assert_equal "rails:model", klass.namespace + end + + def test_find_by_namespace_with_context + klass = Rails::Generators.find_by_namespace(:test_unit, nil, :model) + assert klass + assert_equal "test_unit:model", klass.namespace + end + + def test_find_by_namespace_with_generator_on_root + klass = Rails::Generators.find_by_namespace(:fixjour) + assert klass + assert_equal "fixjour", klass.namespace + end + + def test_find_by_namespace_in_subfolder + klass = Rails::Generators.find_by_namespace(:fixjour, :active_record) + assert klass + assert_equal "active_record:fixjour", klass.namespace + end + + def test_find_by_namespace_with_duplicated_name + klass = Rails::Generators.find_by_namespace(:foobar) + assert klass + assert_equal "foobar:foobar", klass.namespace + end + + def test_find_by_namespace_without_base_or_context_looks_into_rails_namespace + assert Rails::Generators.find_by_namespace(:model) + end + + def test_invoke_with_nested_namespaces + model_generator = Minitest::Mock.new + model_generator.expect(:start, nil, [["Account"], {}]) + assert_called_with(Rails::Generators, :find_by_namespace, ['namespace', 'my:awesome'], returns: model_generator) do + Rails::Generators.invoke 'my:awesome:namespace', ["Account"] + end + model_generator.verify + end + + def test_rails_generators_help_with_builtin_information + output = capture(:stdout){ Rails::Generators.help } + assert_match(/Rails:/, output) + assert_match(/^ model$/, output) + assert_match(/^ scaffold_controller$/, output) + assert_no_match(/^ app$/, output) + end + + def test_rails_generators_help_does_not_include_app_nor_plugin_new + output = capture(:stdout){ Rails::Generators.help } + assert_no_match(/app/, output) + assert_no_match(/[^:]plugin/, output) + end + + def test_rails_generators_with_others_information + output = capture(:stdout){ Rails::Generators.help } + assert_match(/Fixjour:/, output) + assert_match(/^ fixjour$/, output) + end + + def test_rails_generators_does_not_show_active_record_hooks + output = capture(:stdout){ Rails::Generators.help } + assert_match(/ActiveRecord:/, output) + assert_match(/^ active_record:fixjour$/, output) + end + + def test_default_banner_should_show_generator_namespace + klass = Rails::Generators.find_by_namespace(:foobar) + assert_match(/^rails generate foobar:foobar/, klass.banner) + end + + def test_default_banner_should_not_show_rails_generator_namespace + klass = Rails::Generators.find_by_namespace(:model) + assert_match(/^rails generate model/, klass.banner) + end + + def test_no_color_sets_proper_shell + Rails::Generators.no_color! + assert_equal Thor::Shell::Basic, Thor::Base.shell + ensure + Thor::Base.shell = Thor::Shell::Color + end + + def test_fallbacks_for_generators_on_find_by_namespace + Rails::Generators.fallbacks[:remarkable] = :test_unit + klass = Rails::Generators.find_by_namespace(:plugin, :remarkable) + assert klass + assert_equal "test_unit:plugin", klass.namespace + ensure + Rails::Generators.fallbacks.delete(:remarkable) + end + + def test_fallbacks_for_generators_on_find_by_namespace_with_context + Rails::Generators.fallbacks[:remarkable] = :test_unit + klass = Rails::Generators.find_by_namespace(:remarkable, :rails, :plugin) + assert klass + assert_equal "test_unit:plugin", klass.namespace + ensure + Rails::Generators.fallbacks.delete(:remarkable) + end + + def test_fallbacks_for_generators_on_invoke + Rails::Generators.fallbacks[:shoulda] = :test_unit + assert_called_with(TestUnit::Generators::ModelGenerator, :start, [["Account"], {}]) do + Rails::Generators.invoke "shoulda:model", ["Account"] + end + ensure + Rails::Generators.fallbacks.delete(:shoulda) + end + + def test_nested_fallbacks_for_generators + Rails::Generators.fallbacks[:shoulda] = :test_unit + Rails::Generators.fallbacks[:super_shoulda] = :shoulda + assert_called_with(TestUnit::Generators::ModelGenerator, :start, [["Account"], {}]) do + Rails::Generators.invoke "super_shoulda:model", ["Account"] + end + ensure + Rails::Generators.fallbacks.delete(:shoulda) + Rails::Generators.fallbacks.delete(:super_shoulda) + end + + def test_developer_options_are_overwritten_by_user_options + Rails::Generators.options[:with_options] = { generate: false } + + self.class.class_eval(<<-end_eval, __FILE__, __LINE__ + 1) + class WithOptionsGenerator < Rails::Generators::Base + class_option :generate, :default => true + end + end_eval + + assert_equal false, WithOptionsGenerator.class_options[:generate].default + ensure + Rails::Generators.subclasses.delete(WithOptionsGenerator) + end + + def test_rails_root_templates + template = File.join(Rails.root, "lib", "templates", "active_record", "model", "model.rb") + + # Create template + mkdir_p(File.dirname(template)) + File.open(template, 'w'){ |f| f.write "empty" } + + capture(:stdout) do + Rails::Generators.invoke :model, ["user"], destination_root: destination_root + end + + assert_file "app/models/user.rb" do |content| + assert_equal "empty", content + end + ensure + rm_rf File.dirname(template) + end + + def test_source_paths_for_not_namespaced_generators + mspec = Rails::Generators.find_by_namespace :fixjour + assert mspec.source_paths.include?(File.join(Rails.root, "lib", "templates", "fixjour")) + end + + def test_usage_with_embedded_ruby + require File.expand_path("fixtures/lib/generators/usage_template/usage_template_generator", File.dirname(__FILE__)) + output = capture(:stdout) { Rails::Generators.invoke :usage_template, ['--help'] } + assert_match(/:: 2 ::/, output) + end + + def test_hide_namespace + assert !Rails::Generators.hidden_namespaces.include?("special:namespace") + Rails::Generators.hide_namespace("special:namespace") + assert Rails::Generators.hidden_namespaces.include?("special:namespace") + end +end diff --git a/railties/test/initializable_test.rb b/railties/test/initializable_test.rb new file mode 100644 index 0000000000..ed9573453b --- /dev/null +++ b/railties/test/initializable_test.rb @@ -0,0 +1,230 @@ +require 'abstract_unit' +require 'rails/initializable' + +module InitializableTests + + class Foo + include Rails::Initializable + attr_accessor :foo, :bar + + initializer :start do + @foo ||= 0 + @foo += 1 + end + end + + class Bar < Foo + initializer :bar do + @bar ||= 0 + @bar += 1 + end + end + + class Parent + include Rails::Initializable + + initializer :one do + $arr << 1 + end + + initializer :two do + $arr << 2 + end + end + + class Child < Parent + include Rails::Initializable + + initializer :three, before: :one do + $arr << 3 + end + + initializer :four, after: :one, before: :two do + $arr << 4 + end + end + + class Parent + initializer :five, before: :one do + $arr << 5 + end + end + + class Instance + include Rails::Initializable + + initializer :one, group: :assets do + $arr << 1 + end + + initializer :two do + $arr << 2 + end + + initializer :three, group: :all do + $arr << 3 + end + + initializer :four do + $arr << 4 + end + end + + class WithArgs + include Rails::Initializable + + initializer :foo do |arg| + $with_arg = arg + end + end + + class OverriddenInitializer + class MoreInitializers + include Rails::Initializable + + initializer :startup, before: :last do + $arr << 3 + end + + initializer :terminate, after: :first, before: :startup do + $arr << two + end + + def two + 2 + end + end + + include Rails::Initializable + + initializer :first do + $arr << 1 + end + + initializer :last do + $arr << 4 + end + + def self.initializers + super + MoreInitializers.new.initializers + end + end + + module Interdependent + class PluginA + include Rails::Initializable + + initializer "plugin_a.startup" do + $arr << 1 + end + + initializer "plugin_a.terminate" do + $arr << 4 + end + end + + class PluginB + include Rails::Initializable + + initializer "plugin_b.startup", after: "plugin_a.startup" do + $arr << 2 + end + + initializer "plugin_b.terminate", before: "plugin_a.terminate" do + $arr << 3 + end + end + + class Application + include Rails::Initializable + def self.initializers + PluginB.initializers + PluginA.initializers + end + end + end + + class Basic < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + test "initializers run" do + foo = Foo.new + foo.run_initializers + assert_equal 1, foo.foo + end + + test "initializers are inherited" do + bar = Bar.new + bar.run_initializers + assert_equal [1, 1], [bar.foo, bar.bar] + end + + test "initializers only get run once" do + foo = Foo.new + foo.run_initializers + foo.run_initializers + assert_equal 1, foo.foo + end + + test "creating initializer without a block raises an error" do + assert_raise(ArgumentError) do + Class.new do + include Rails::Initializable + + initializer :foo + end + end + end + end + + class BeforeAfter < ActiveSupport::TestCase + test "running on parent" do + $arr = [] + Parent.new.run_initializers + assert_equal [5, 1, 2], $arr + end + + test "running on child" do + $arr = [] + Child.new.run_initializers + assert_equal [5, 3, 1, 4, 2], $arr + end + + test "handles dependencies introduced before all initializers are loaded" do + $arr = [] + Interdependent::Application.new.run_initializers + assert_equal [1, 2, 3, 4], $arr + end + end + + class InstanceTest < ActiveSupport::TestCase + test "running locals" do + $arr = [] + instance = Instance.new + instance.run_initializers + assert_equal [2, 3, 4], $arr + end + + test "running locals with groups" do + $arr = [] + instance = Instance.new + instance.run_initializers(:assets) + assert_equal [1, 3], $arr + end + end + + class WithArgsTest < ActiveSupport::TestCase + test "running initializers with args" do + $with_arg = nil + WithArgs.new.run_initializers(:default, 'foo') + assert_equal 'foo', $with_arg + end + end + + class OverriddenInitializerTest < ActiveSupport::TestCase + test "merges in the initializers from the parent in the right order" do + $arr = [] + OverriddenInitializer.new.run_initializers + assert_equal [1, 2, 3, 4], $arr + end + end +end diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb new file mode 100644 index 0000000000..df3c2ca66d --- /dev/null +++ b/railties/test/isolation/abstract_unit.rb @@ -0,0 +1,339 @@ +# Note: +# It is important to keep this file as light as possible +# the goal for tests that require this is to test booting up +# rails from an empty state, so anything added here could +# hide potential failures +# +# It is also good to know what is the bare minimum to get +# Rails booted up. +require 'fileutils' + +require 'bundler/setup' unless defined?(Bundler) +require 'active_support' +require 'active_support/testing/autorun' +require 'active_support/testing/stream' +require 'active_support/test_case' + +RAILS_FRAMEWORK_ROOT = File.expand_path("#{File.dirname(__FILE__)}/../../..") + +# These files do not require any others and are needed +# to run the tests +require "active_support/testing/isolation" +require "active_support/core_ext/kernel/reporting" +require 'tmpdir' + +module TestHelpers + module Paths + def app_template_path + File.join Dir.tmpdir, 'app_template' + end + + def tmp_path(*args) + @tmp_path ||= File.realpath(Dir.mktmpdir) + File.join(@tmp_path, *args) + end + + def app_path(*args) + tmp_path(*%w[app] + args) + end + + def framework_path + RAILS_FRAMEWORK_ROOT + end + + def rails_root + app_path + end + end + + module Rack + def app(env = "production") + old_env = ENV["RAILS_ENV"] + @app ||= begin + ENV["RAILS_ENV"] = env + + # FIXME: shush Sass warning spam, not relevant to testing Railties + Kernel.silence_warnings do + require "#{app_path}/config/environment" + end + + Rails.application + end + ensure + ENV["RAILS_ENV"] = old_env + end + + def extract_body(response) + "".tap do |body| + response[2].each {|chunk| body << chunk } + end + end + + def get(path) + @app.call(::Rack::MockRequest.env_for(path)) + end + + def assert_welcome(resp) + assert_equal 200, resp[0] + assert_match 'text/html', resp[1]["Content-Type"] + assert_match 'charset=utf-8', resp[1]["Content-Type"] + assert extract_body(resp).match(/Welcome aboard/) + end + + def assert_success(resp) + assert_equal 202, resp[0] + end + + def assert_missing(resp) + assert_equal 404, resp[0] + end + + def assert_header(key, value, resp) + assert_equal value, resp[1][key.to_s] + end + + def assert_body(expected, resp) + assert_equal expected, extract_body(resp) + end + end + + module Generation + # Build an application by invoking the generator and going through the whole stack. + def build_app(options = {}) + @prev_rails_env = ENV['RAILS_ENV'] + ENV['RAILS_ENV'] = "development" + ENV['SECRET_KEY_BASE'] ||= SecureRandom.hex(16) + + FileUtils.rm_rf(app_path) + FileUtils.cp_r(app_template_path, app_path) + + # Delete the initializers unless requested + unless options[:initializers] + Dir["#{app_path}/config/initializers/*.rb"].each do |initializer| + File.delete(initializer) + end + end + + gemfile_path = "#{app_path}/Gemfile" + if options[:gemfile].blank? && File.exist?(gemfile_path) + File.delete gemfile_path + end + + routes = File.read("#{app_path}/config/routes.rb") + if routes =~ /(\n\s*end\s*)\Z/ + File.open("#{app_path}/config/routes.rb", 'w') do |f| + f.puts $` + "\nmatch ':controller(/:action(/:id))(.:format)', via: :all\n" + $1 + end + end + + File.open("#{app_path}/config/database.yml", "w") do |f| + f.puts <<-YAML + default: &default + adapter: sqlite3 + pool: 5 + timeout: 5000 + development: + <<: *default + database: db/development.sqlite3 + test: + <<: *default + database: db/test.sqlite3 + production: + <<: *default + database: db/production.sqlite3 + YAML + end + + add_to_config <<-RUBY + config.eager_load = false + config.session_store :cookie_store, key: "_myapp_session" + config.active_support.deprecation = :log + config.active_support.test_order = :random + config.action_controller.allow_forgery_protection = false + config.log_level = :info + RUBY + end + + def teardown_app + ENV['RAILS_ENV'] = @prev_rails_env if @prev_rails_env + end + + # Make a very basic app, without creating the whole directory structure. + # This is faster and simpler than the method above. + def make_basic_app + require "rails" + require "action_controller/railtie" + require "action_view/railtie" + require 'action_dispatch/middleware/flash' + + @app = Class.new(Rails::Application) + @app.config.eager_load = false + @app.secrets.secret_key_base = "3b7cd727ee24e8444053437c36cc66c4" + @app.config.session_store :cookie_store, key: "_myapp_session" + @app.config.active_support.deprecation = :log + @app.config.active_support.test_order = :random + @app.config.log_level = :info + + yield @app if block_given? + @app.initialize! + + @app.routes.draw do + get "/" => "omg#index" + end + + require 'rack/test' + extend ::Rack::Test::Methods + end + + def simple_controller + controller :foo, <<-RUBY + class FooController < ApplicationController + def index + render text: "foo" + end + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get ':controller(/:action)' + end + RUBY + end + + class Bukkit + attr_reader :path + + def initialize(path) + @path = path + end + + def write(file, string) + path = "#{@path}/#{file}" + FileUtils.mkdir_p(File.dirname(path)) + File.open(path, "w") {|f| f.puts string } + end + + def delete(file) + File.delete("#{@path}/#{file}") + end + end + + def engine(name) + dir = "#{app_path}/random/#{name}" + FileUtils.mkdir_p(dir) + + app = File.readlines("#{app_path}/config/application.rb") + app.insert(2, "$:.unshift(\"#{dir}/lib\")") + app.insert(3, "require #{name.inspect}") + + File.open("#{app_path}/config/application.rb", 'r+') do |f| + f.puts app + end + + Bukkit.new(dir).tap do |bukkit| + yield bukkit if block_given? + end + end + + def script(script) + Dir.chdir(app_path) do + `#{Gem.ruby} #{app_path}/bin/rails #{script}` + end + end + + def add_to_top_of_config(str) + environment = File.read("#{app_path}/config/application.rb") + if environment =~ /(Rails::Application\s*)/ + File.open("#{app_path}/config/application.rb", 'w') do |f| + f.puts $` + $1 + "\n#{str}\n" + $' + end + end + end + + def add_to_config(str) + environment = File.read("#{app_path}/config/application.rb") + if environment =~ /(\n\s*end\s*end\s*)\Z/ + File.open("#{app_path}/config/application.rb", 'w') do |f| + f.puts $` + "\n#{str}\n" + $1 + end + end + end + + def add_to_env_config(env, str) + environment = File.read("#{app_path}/config/environments/#{env}.rb") + if environment =~ /(\n\s*end\s*)\Z/ + File.open("#{app_path}/config/environments/#{env}.rb", 'w') do |f| + f.puts $` + "\n#{str}\n" + $1 + end + end + end + + def remove_from_config(str) + file = "#{app_path}/config/application.rb" + contents = File.read(file) + contents.sub!(/#{str}/, "") + File.open(file, "w+") { |f| f.puts contents } + end + + def app_file(path, contents, mode = 'w') + FileUtils.mkdir_p File.dirname("#{app_path}/#{path}") + File.open("#{app_path}/#{path}", mode) do |f| + f.puts contents + end + end + + def remove_file(path) + FileUtils.rm_rf "#{app_path}/#{path}" + end + + def controller(name, contents) + app_file("app/controllers/#{name}_controller.rb", contents) + end + + def use_frameworks(arr) + to_remove = [:actionmailer, :activerecord] - arr + + if to_remove.include?(:activerecord) + remove_from_config 'config.active_record.*' + end + + $:.reject! {|path| path =~ %r'/(#{to_remove.join('|')})/' } + end + + def boot_rails + # FIXME: shush Sass warning spam, not relevant to testing Railties + Kernel.silence_warnings do + require File.expand_path('../../../../load_paths', __FILE__) + end + end + end +end + +class ActiveSupport::TestCase + include TestHelpers::Paths + include TestHelpers::Rack + include TestHelpers::Generation + include ActiveSupport::Testing::Stream + + self.test_order = :sorted + +end + +# Create a scope and build a fixture rails app +Module.new do + extend TestHelpers::Paths + + # Build a rails app + FileUtils.rm_rf(app_template_path) + FileUtils.mkdir(app_template_path) + + environment = File.expand_path('../../../../load_paths', __FILE__) + require_environment = "-r #{environment}" + + `#{Gem.ruby} #{require_environment} #{RAILS_FRAMEWORK_ROOT}/railties/exe/rails new #{app_template_path} --skip-gemfile --no-rc` + File.open("#{app_template_path}/config/boot.rb", 'w') do |f| + f.puts "require '#{environment}'" + f.puts "require 'rails/all'" + end +end unless defined?(RAILS_ISOLATED_ENGINE) diff --git a/railties/test/path_generation_test.rb b/railties/test/path_generation_test.rb new file mode 100644 index 0000000000..a16adc72a6 --- /dev/null +++ b/railties/test/path_generation_test.rb @@ -0,0 +1,83 @@ +require 'abstract_unit' +require 'active_support/core_ext/object/with_options' +require 'active_support/core_ext/object/json' + +class PathGenerationTest < ActiveSupport::TestCase + attr_reader :app + + class TestSet < ActionDispatch::Routing::RouteSet + def initialize(block) + @block = block + super() + end + + class Request < DelegateClass(ActionDispatch::Request) + def initialize(target, url_helpers, block) + super(target) + @url_helpers = url_helpers + @block = block + end + + def controller_class + url_helpers = @url_helpers + block = @block + Class.new(ActionController::Base) { + include url_helpers + define_method(:process) { |name| block.call(self) } + def to_a; [200, {}, []]; end + } + end + end + + def make_request(env) + Request.new super, self.url_helpers, @block + end + end + + def send_request(uri_or_host, method, path, script_name = nil) + host = uri_or_host.host unless path + path ||= uri_or_host.path + + params = {'PATH_INFO' => path, + 'REQUEST_METHOD' => method, + 'HTTP_HOST' => host } + + params['SCRIPT_NAME'] = script_name if script_name + + status, headers, body = app.call(params) + new_body = [] + body.each { |part| new_body << part } + body.close if body.respond_to? :close + [status, headers, new_body] + end + + def test_original_script_name + original_logger = Rails.logger + Rails.logger = Logger.new nil + + app = Class.new(Rails::Application) { + attr_accessor :controller + def initialize + super + app = self + @routes = TestSet.new ->(c) { app.controller = c } + secrets.secret_key_base = "foo" + secrets.secret_token = "foo" + end + def app; routes; end + } + + @app = app + app.routes.draw { resource :blogs } + + url = URI("http://example.org/blogs") + + send_request(url, 'GET', nil, '/FOO') + assert_equal '/FOO/blogs', app.instance.controller.blogs_path + + send_request(url, 'GET', nil) + assert_equal '/blogs', app.instance.controller.blogs_path + ensure + Rails.logger = original_logger + end +end diff --git a/railties/test/paths_test.rb b/railties/test/paths_test.rb new file mode 100644 index 0000000000..96b54c7264 --- /dev/null +++ b/railties/test/paths_test.rb @@ -0,0 +1,276 @@ +require 'abstract_unit' +require 'rails/paths' +require 'minitest/mock' + +class PathsTest < ActiveSupport::TestCase + def setup + @root = Rails::Paths::Root.new("/foo/bar") + end + + test "the paths object is initialized with the root path" do + root = Rails::Paths::Root.new("/fiz/baz") + assert_equal "/fiz/baz", root.path + end + + test "the paths object can be initialized with nil" do + assert_nothing_raised do + Rails::Paths::Root.new(nil) + end + end + + test "a paths object initialized with nil can be updated" do + root = Rails::Paths::Root.new(nil) + root.add "app" + root.path = "/root" + assert_equal ["app"], root["app"].to_ary + assert_equal ["/root/app"], root["app"].to_a + end + + test "creating a root level path" do + @root.add "app" + assert_equal ["/foo/bar/app"], @root["app"].to_a + end + + test "creating a root level path with options" do + @root.add "app", with: "/foo/bar" + assert_equal ["/foo/bar"], @root["app"].to_a + end + + test "raises exception if root path never set" do + root = Rails::Paths::Root.new(nil) + root.add "app" + assert_raises RuntimeError do + root["app"].to_a + end + end + + test "creating a child level path" do + @root.add "app" + @root.add "app/models" + assert_equal ["/foo/bar/app/models"], @root["app/models"].to_a + end + + test "creating a child level path with option" do + @root.add "app" + @root.add "app/models", with: "/foo/bar/baz" + assert_equal ["/foo/bar/baz"], @root["app/models"].to_a + end + + test "child level paths are relative from the root" do + @root.add "app" + @root.add "app/models", with: "baz" + assert_equal ["/foo/bar/baz"], @root["app/models"].to_a + end + + test "absolute current path" do + @root.add "config" + @root.add "config/locales" + + assert_equal "/foo/bar/config/locales", @root["config/locales"].absolute_current + end + + test "adding multiple physical paths as an array" do + @root.add "app", with: ["/app", "/app2"] + assert_equal ["/app", "/app2"], @root["app"].to_a + end + + test "adding multiple physical paths using #push" do + @root.add "app" + @root["app"].push "app2" + assert_equal ["/foo/bar/app", "/foo/bar/app2"], @root["app"].to_a + end + + test "adding multiple physical paths using <<" do + @root.add "app" + @root["app"] << "app2" + assert_equal ["/foo/bar/app", "/foo/bar/app2"], @root["app"].to_a + end + + test "adding multiple physical paths using concat" do + @root.add "app" + @root["app"].concat ["app2", "/app3"] + assert_equal ["/foo/bar/app", "/foo/bar/app2", "/app3"], @root["app"].to_a + end + + test "adding multiple physical paths using #unshift" do + @root.add "app" + @root["app"].unshift "app2" + assert_equal ["/foo/bar/app2", "/foo/bar/app"], @root["app"].to_a + end + + test "it is possible to add a path that should be autoloaded only once" do + File.stub(:exist?, true) do + @root.add "app", with: "/app" + @root["app"].autoload_once! + assert @root["app"].autoload_once? + assert @root.autoload_once.include?(@root["app"].expanded.first) + end + end + + test "it is possible to remove a path that should be autoloaded only once" do + @root["app"] = "/app" + @root["app"].autoload_once! + assert @root["app"].autoload_once? + + @root["app"].skip_autoload_once! + assert !@root["app"].autoload_once? + assert !@root.autoload_once.include?(@root["app"].expanded.first) + end + + test "it is possible to add a path without assignment and specify it should be loaded only once" do + File.stub(:exist?, true) do + @root.add "app", with: "/app", autoload_once: true + assert @root["app"].autoload_once? + assert @root.autoload_once.include?("/app") + end + end + + test "it is possible to add multiple paths without assignment and specify it should be loaded only once" do + File.stub(:exist?, true) do + @root.add "app", with: ["/app", "/app2"], autoload_once: true + assert @root["app"].autoload_once? + assert @root.autoload_once.include?("/app") + assert @root.autoload_once.include?("/app2") + end + end + + test "making a path autoload_once more than once only includes it once in @root.load_once" do + File.stub(:exist?, true) do + @root["app"] = "/app" + @root["app"].autoload_once! + @root["app"].autoload_once! + assert_equal 1, @root.autoload_once.select {|p| p == @root["app"].expanded.first }.size + end + end + + test "paths added to a load_once path should be added to the autoload_once collection" do + File.stub(:exist?, true) do + @root["app"] = "/app" + @root["app"].autoload_once! + @root["app"] << "/app2" + assert_equal 2, @root.autoload_once.size + end + end + + test "it is possible to mark a path as eager loaded" do + File.stub(:exist?, true) do + @root["app"] = "/app" + @root["app"].eager_load! + assert @root["app"].eager_load? + assert @root.eager_load.include?(@root["app"].to_a.first) + end + end + + test "it is possible to skip a path from eager loading" do + @root["app"] = "/app" + @root["app"].eager_load! + assert @root["app"].eager_load? + + @root["app"].skip_eager_load! + assert !@root["app"].eager_load? + assert !@root.eager_load.include?(@root["app"].to_a.first) + end + + test "it is possible to add a path without assignment and mark it as eager" do + File.stub(:exist?, true) do + @root.add "app", with: "/app", eager_load: true + assert @root["app"].eager_load? + assert @root.eager_load.include?("/app") + end + end + + test "it is possible to add multiple paths without assignment and mark them as eager" do + File.stub(:exist?, true) do + @root.add "app", with: ["/app", "/app2"], eager_load: true + assert @root["app"].eager_load? + assert @root.eager_load.include?("/app") + assert @root.eager_load.include?("/app2") + end + end + + test "it is possible to create a path without assignment and mark it both as eager and load once" do + File.stub(:exist?, true) do + @root.add "app", with: "/app", eager_load: true, autoload_once: true + assert @root["app"].eager_load? + assert @root["app"].autoload_once? + assert @root.eager_load.include?("/app") + assert @root.autoload_once.include?("/app") + end + end + + test "making a path eager more than once only includes it once in @root.eager_paths" do + File.stub(:exist?, true) do + @root["app"] = "/app" + @root["app"].eager_load! + @root["app"].eager_load! + assert_equal 1, @root.eager_load.select {|p| p == @root["app"].expanded.first }.size + end + end + + test "paths added to an eager_load path should be added to the eager_load collection" do + File.stub(:exist?, true) do + @root["app"] = "/app" + @root["app"].eager_load! + @root["app"] << "/app2" + assert_equal 2, @root.eager_load.size + end + end + + test "it should be possible to add a path's default glob" do + @root["app"] = "/app" + @root["app"].glob = "*.rb" + assert_equal "*.rb", @root["app"].glob + end + + test "it should be possible to get extensions by glob" do + @root["app"] = "/app" + @root["app"].glob = "*.{rb,yml}" + assert_equal ["rb", "yml"], @root["app"].extensions + end + + test "it should be possible to override a path's default glob without assignment" do + @root.add "app", with: "/app", glob: "*.rb" + assert_equal "*.rb", @root["app"].glob + end + + test "it should be possible to replace a path and persist the original paths glob" do + @root.add "app", glob: "*.rb" + @root["app"] = "app2" + assert_equal ["/foo/bar/app2"], @root["app"].to_a + assert_equal "*.rb", @root["app"].glob + end + + test "a path can be added to the load path" do + File.stub(:exist?, true) do + @root["app"] = "app" + @root["app"].load_path! + @root["app/models"] = "app/models" + assert_equal ["/foo/bar/app"], @root.load_paths + end + end + + test "a path can be added to the load path on creation" do + File.stub(:exist?, true) do + @root.add "app", with: "/app", load_path: true + assert @root["app"].load_path? + assert_equal ["/app"], @root.load_paths + end + end + + test "a path can be marked as autoload path" do + File.stub(:exist?, true) do + @root["app"] = "app" + @root["app"].autoload! + @root["app/models"] = "app/models" + assert_equal ["/foo/bar/app"], @root.autoload_paths + end + end + + test "a path can be marked as autoload on creation" do + File.stub(:exist?, true) do + @root.add "app", with: "/app", autoload: true + assert @root["app"].autoload? + assert_equal ["/app"], @root.autoload_paths + end + end +end diff --git a/railties/test/rack_logger_test.rb b/railties/test/rack_logger_test.rb new file mode 100644 index 0000000000..fcc79b57fb --- /dev/null +++ b/railties/test/rack_logger_test.rb @@ -0,0 +1,75 @@ +require 'abstract_unit' +require 'active_support/testing/autorun' +require 'active_support/test_case' +require 'rails/rack/logger' +require 'logger' + +module Rails + module Rack + class LoggerTest < ActiveSupport::TestCase + class TestLogger < Rails::Rack::Logger + NULL = ::Logger.new File::NULL + + attr_reader :logger + + def initialize(logger = NULL, taggers = nil, &block) + super(->(_) { block.call; [200, {}, []] }, taggers) + @logger = logger + end + + def development?; false; end + end + + class Subscriber < Struct.new(:starts, :finishes) + def initialize(starts = [], finishes = []) + super + end + + def start(name, id, payload) + starts << [name, id, payload] + end + + def finish(name, id, payload) + finishes << [name, id, payload] + end + end + + attr_reader :subscriber, :notifier + + def setup + @subscriber = Subscriber.new + @notifier = ActiveSupport::Notifications.notifier + @subscription = notifier.subscribe 'request.action_dispatch', subscriber + end + + def teardown + notifier.unsubscribe @subscription + end + + def test_notification + logger = TestLogger.new { } + + assert_difference('subscriber.starts.length') do + assert_difference('subscriber.finishes.length') do + logger.call('REQUEST_METHOD' => 'GET').last.close + end + end + end + + def test_notification_on_raise + logger = TestLogger.new do + # using an exception class that is not a StandardError subclass on purpose + raise NotImplementedError + end + + assert_difference('subscriber.starts.length') do + assert_difference('subscriber.finishes.length') do + assert_raises(NotImplementedError) do + logger.call 'REQUEST_METHOD' => 'GET' + end + end + end + end + end + end +end diff --git a/railties/test/rails_info_controller_test.rb b/railties/test/rails_info_controller_test.rb new file mode 100644 index 0000000000..c51503c2b7 --- /dev/null +++ b/railties/test/rails_info_controller_test.rb @@ -0,0 +1,81 @@ +require 'abstract_unit' + +module ActionController + class Base + include ActionController::Testing + end +end + +class InfoControllerTest < ActionController::TestCase + tests Rails::InfoController + + def setup + Rails.application.routes.draw do + get '/rails/info/properties' => "rails/info#properties" + get '/rails/info/routes' => "rails/info#routes" + end + @routes = Rails.application.routes + + Rails::InfoController.include(@routes.url_helpers) + + @request.env["REMOTE_ADDR"] = "127.0.0.1" + end + + test "info controller does not allow remote requests" do + @request.env["REMOTE_ADDR"] = "example.org" + get :properties + assert_response :forbidden + end + + test "info controller renders an error message when request was forbidden" do + @request.env["REMOTE_ADDR"] = "example.org" + get :properties + assert_select 'p' + end + + test "info controller allows requests when all requests are considered local" do + get :properties + assert_response :success + end + + test "info controller allows local requests" do + get :properties + assert_response :success + end + + test "info controller renders a table with properties" do + get :properties + assert_select 'table' + end + + test "info controller renders with routes" do + get :routes + assert_response :success + end + + test "info controller returns exact matches" do + exact_count = -> { JSON(response.body)['exact'].size } + + get :routes, params: { path: 'rails/info/route' } + assert exact_count.call == 0, 'should not match incomplete routes' + + get :routes, params: { path: 'rails/info/routes' } + assert exact_count.call == 1, 'should match complete routes' + + get :routes, params: { path: 'rails/info/routes.html' } + assert exact_count.call == 1, 'should match complete routes with optional parts' + end + + test "info controller returns fuzzy matches" do + fuzzy_count = -> { JSON(response.body)['fuzzy'].size } + + get :routes, params: { path: 'rails/info' } + assert fuzzy_count.call == 2, 'should match incomplete routes' + + get :routes, params: { path: 'rails/info/routes' } + assert fuzzy_count.call == 1, 'should match complete routes' + + get :routes, params: { path: 'rails/info/routes.html' } + assert fuzzy_count.call == 0, 'should match optional parts of route literally' + end +end diff --git a/railties/test/rails_info_test.rb b/railties/test/rails_info_test.rb new file mode 100644 index 0000000000..92e4af25b5 --- /dev/null +++ b/railties/test/rails_info_test.rb @@ -0,0 +1,71 @@ +require 'abstract_unit' + +unless defined?(Rails) && defined?(Rails::Info) + module Rails + class Info; end + end +end + +require "active_support/core_ext/kernel/reporting" + +class InfoTest < ActiveSupport::TestCase + def setup + Rails.send :remove_const, :Info + silence_warnings { load 'rails/info.rb' } + end + + def test_property_with_block_swallows_exceptions_and_ignores_property + assert_nothing_raised do + Rails::Info.module_eval do + property('Bogus') {raise} + end + end + assert !property_defined?('Bogus') + end + + def test_property_with_string + Rails::Info.module_eval do + property 'Hello', 'World' + end + assert_property 'Hello', 'World' + end + + def test_property_with_block + Rails::Info.module_eval do + property('Goodbye') {'World'} + end + assert_property 'Goodbye', 'World' + end + + def test_rails_version + assert_property 'Rails version', + File.read(File.realpath('../../../RAILS_VERSION', __FILE__)).chomp + end + + def test_html_includes_middleware + Rails::Info.module_eval do + property 'Middleware', ['Rack::Lock', 'Rack::Static'] + end + + html = Rails::Info.to_html + assert html.include?('<tr><td class="name">Middleware</td>') + properties.value_for('Middleware').each do |value| + assert html.include?("<li>#{CGI.escapeHTML(value)}</li>") + end + end + + protected + def properties + Rails::Info.properties + end + + def property_defined?(property_name) + properties.names.include? property_name + end + + def assert_property(property_name, value) + raise "Property #{property_name.inspect} not defined" unless + property_defined? property_name + assert_equal value, properties.value_for(property_name) + end +end diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb new file mode 100644 index 0000000000..24386de82a --- /dev/null +++ b/railties/test/railties/engine_test.rb @@ -0,0 +1,1361 @@ +require "isolation/abstract_unit" +require "stringio" +require "rack/test" + +module RailtiesTest + class EngineTest < ActiveSupport::TestCase + + include ActiveSupport::Testing::Isolation + include Rack::Test::Methods + + def setup + build_app + + @plugin = engine "bukkits" do |plugin| + plugin.write "lib/bukkits.rb", <<-RUBY + module Bukkits + class Engine < ::Rails::Engine + railtie_name "bukkits" + end + end + RUBY + plugin.write "lib/another.rb", "class Another; end" + end + end + + def teardown + teardown_app + end + + def boot_rails + super + require "#{app_path}/config/environment" + end + + test "serving sprocket's assets" do + @plugin.write "app/assets/javascripts/engine.js.erb", "<%= :alert %>();" + add_to_env_config "development", "config.assets.digest = false" + + boot_rails + require 'rack/test' + extend Rack::Test::Methods + + get "/assets/engine.js" + assert_match "alert()", last_response.body + end + + test "rake environment can be called in the engine" do + boot_rails + + @plugin.write "Rakefile", <<-RUBY + APP_RAKEFILE = '#{app_path}/Rakefile' + load 'rails/tasks/engine.rake' + task :foo => :environment do + puts "Task ran" + end + RUBY + + Dir.chdir(@plugin.path) do + output = `bundle exec rake foo` + assert_match "Task ran", output + end + end + + test "copying migrations" do + @plugin.write "db/migrate/1_create_users.rb", <<-RUBY + class CreateUsers < ActiveRecord::Migration + end + RUBY + + @plugin.write "db/migrate/2_add_last_name_to_users.rb", <<-RUBY + class AddLastNameToUsers < ActiveRecord::Migration + end + RUBY + + @plugin.write "db/migrate/3_create_sessions.rb", <<-RUBY + class CreateSessions < ActiveRecord::Migration + end + RUBY + + app_file "db/migrate/1_create_sessions.rb", <<-RUBY + class CreateSessions < ActiveRecord::Migration + def up + end + end + RUBY + + add_to_config "ActiveRecord::Base.timestamped_migrations = false" + + boot_rails + + Dir.chdir(app_path) do + output = `bundle exec rake bukkits:install:migrations` + + assert File.exist?("#{app_path}/db/migrate/2_create_users.bukkits.rb") + assert File.exist?("#{app_path}/db/migrate/3_add_last_name_to_users.bukkits.rb") + assert_match(/Copied migration 2_create_users.bukkits.rb from bukkits/, output) + assert_match(/Copied migration 3_add_last_name_to_users.bukkits.rb from bukkits/, output) + assert_match(/NOTE: Migration 3_create_sessions.rb from bukkits has been skipped/, output) + assert_equal 3, Dir["#{app_path}/db/migrate/*.rb"].length + + output = `bundle exec rake railties:install:migrations`.split("\n") + + assert_no_match(/2_create_users/, output.join("\n")) + + bukkits_migration_order = output.index(output.detect{|o| /NOTE: Migration 3_create_sessions.rb from bukkits has been skipped/ =~ o }) + assert_not_nil bukkits_migration_order, "Expected migration to be skipped" + + migrations_count = Dir["#{app_path}/db/migrate/*.rb"].length + `bundle exec rake railties:install:migrations` + + assert_equal migrations_count, Dir["#{app_path}/db/migrate/*.rb"].length + end + end + + test 'respects the order of railties when installing migrations' do + @blog = engine "blog" do |plugin| + plugin.write "lib/blog.rb", <<-RUBY + module Blog + class Engine < ::Rails::Engine + end + end + RUBY + end + + @plugin.write "db/migrate/1_create_users.rb", <<-RUBY + class CreateUsers < ActiveRecord::Migration + end + RUBY + + @blog.write "db/migrate/2_create_blogs.rb", <<-RUBY + class CreateBlogs < ActiveRecord::Migration + end + RUBY + + add_to_config("config.railties_order = [Bukkits::Engine, Blog::Engine, :all, :main_app]") + + boot_rails + + Dir.chdir(app_path) do + output = `bundle exec rake railties:install:migrations`.split("\n") + + assert_match(/Copied migration \d+_create_users.bukkits.rb from bukkits/, output.first) + assert_match(/Copied migration \d+_create_blogs.blog_engine.rb from blog_engine/, output.last) + end + end + + test "dont reverse default railties order" do + @api = engine "api" do |plugin| + plugin.write "lib/api.rb", <<-RUBY + module Api + class Engine < ::Rails::Engine; end + end + RUBY + end + + # added last but here is loaded before api engine + @core = engine "core" do |plugin| + plugin.write "lib/core.rb", <<-RUBY + module Core + class Engine < ::Rails::Engine; end + end + RUBY + end + + @core.write "db/migrate/1_create_users.rb", <<-RUBY + class CreateUsers < ActiveRecord::Migration; end + RUBY + + @api.write "db/migrate/2_create_keys.rb", <<-RUBY + class CreateKeys < ActiveRecord::Migration; end + RUBY + + boot_rails + + Dir.chdir(app_path) do + output = `bundle exec rake railties:install:migrations`.split("\n") + + assert_match(/Copied migration \d+_create_users.core_engine.rb from core_engine/, output.first) + assert_match(/Copied migration \d+_create_keys.api_engine.rb from api_engine/, output.last) + end + end + + test "mountable engine should copy migrations within engine_path" do + @plugin.write "lib/bukkits.rb", <<-RUBY + module Bukkits + class Engine < ::Rails::Engine + isolate_namespace Bukkits + end + end + RUBY + + @plugin.write "db/migrate/0_add_first_name_to_users.rb", <<-RUBY + class AddFirstNameToUsers < ActiveRecord::Migration + end + RUBY + + @plugin.write "Rakefile", <<-RUBY + APP_RAKEFILE = '#{app_path}/Rakefile' + load 'rails/tasks/engine.rake' + RUBY + + add_to_config "ActiveRecord::Base.timestamped_migrations = false" + + boot_rails + + Dir.chdir(@plugin.path) do + output = `bundle exec rake app:bukkits:install:migrations` + assert File.exist?("#{app_path}/db/migrate/0_add_first_name_to_users.bukkits.rb") + assert_match(/Copied migration 0_add_first_name_to_users.bukkits.rb from bukkits/, output) + assert_equal 1, Dir["#{app_path}/db/migrate/*.rb"].length + end + end + + test "no rake task without migrations" do + boot_rails + require 'rake' + require 'rdoc/task' + require 'rake/testtask' + Rails.application.load_tasks + assert !Rake::Task.task_defined?('bukkits:install:migrations') + end + + test "puts its lib directory on load path" do + boot_rails + require "another" + assert_equal "Another", Another.name + end + + test "puts its models directory on autoload path" do + @plugin.write "app/models/my_bukkit.rb", "class MyBukkit ; end" + boot_rails + assert_nothing_raised { MyBukkit } + end + + test "puts its controllers directory on autoload path" do + @plugin.write "app/controllers/bukkit_controller.rb", "class BukkitController ; end" + boot_rails + assert_nothing_raised { BukkitController } + end + + test "adds its views to view paths" do + @plugin.write "app/controllers/bukkit_controller.rb", <<-RUBY + class BukkitController < ActionController::Base + def index + end + end + RUBY + + @plugin.write "app/views/bukkit/index.html.erb", "Hello bukkits" + + boot_rails + + require "action_controller" + require "rack/mock" + response = BukkitController.action(:index).call(Rack::MockRequest.env_for("/")) + assert_equal "Hello bukkits\n", response[2].body + end + + test "adds its views to view paths with lower priority than app ones" do + @plugin.write "app/controllers/bukkit_controller.rb", <<-RUBY + class BukkitController < ActionController::Base + def index + end + end + RUBY + + @plugin.write "app/views/bukkit/index.html.erb", "Hello bukkits" + app_file "app/views/bukkit/index.html.erb", "Hi bukkits" + + boot_rails + + require "action_controller" + require "rack/mock" + response = BukkitController.action(:index).call(Rack::MockRequest.env_for("/")) + assert_equal "Hi bukkits\n", response[2].body + end + + test "adds helpers to controller views" do + @plugin.write "app/controllers/bukkit_controller.rb", <<-RUBY + class BukkitController < ActionController::Base + def index + end + end + RUBY + + @plugin.write "app/helpers/bukkit_helper.rb", <<-RUBY + module BukkitHelper + def bukkits + "bukkits" + end + end + RUBY + + @plugin.write "app/views/bukkit/index.html.erb", "Hello <%= bukkits %>" + + boot_rails + + require "rack/mock" + response = BukkitController.action(:index).call(Rack::MockRequest.env_for("/")) + assert_equal "Hello bukkits\n", response[2].body + end + + test "autoload any path under app" do + @plugin.write "app/anything/foo.rb", <<-RUBY + module Foo; end + RUBY + boot_rails + assert Foo + end + + test "routes are added to router" do + @plugin.write "config/routes.rb", <<-RUBY + class Sprokkit + def self.call(env) + [200, {'Content-Type' => 'text/html'}, ["I am a Sprokkit"]] + end + end + + Rails.application.routes.draw do + get "/sprokkit", :to => Sprokkit + end + RUBY + + boot_rails + require 'rack/test' + extend Rack::Test::Methods + + get "/sprokkit" + assert_equal "I am a Sprokkit", last_response.body + end + + test "routes in engines have lower priority than application ones" do + controller "foo", <<-RUBY + class FooController < ActionController::Base + def index + render :text => "foo" + end + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get 'foo', :to => 'foo#index' + end + RUBY + + @plugin.write "app/controllers/bar_controller.rb", <<-RUBY + class BarController < ActionController::Base + def index + render :text => "bar" + end + end + RUBY + + @plugin.write "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get 'foo', to: 'bar#index' + get 'bar', to: 'bar#index' + end + RUBY + + boot_rails + require 'rack/test' + extend Rack::Test::Methods + + get '/foo' + assert_equal 'foo', last_response.body + + get '/bar' + assert_equal 'bar', last_response.body + end + + test "rake tasks lib tasks are loaded" do + $executed = false + @plugin.write "lib/tasks/foo.rake", <<-RUBY + task :foo do + $executed = true + end + RUBY + + boot_rails + require 'rake' + require 'rdoc/task' + require 'rake/testtask' + Rails.application.load_tasks + Rake::Task[:foo].invoke + assert $executed + end + + test "i18n files have lower priority than application ones" do + add_to_config <<-RUBY + config.i18n.load_path << "#{app_path}/app/locales/en.yml" + RUBY + + app_file 'app/locales/en.yml', <<-YAML +en: + bar: "1" +YAML + + app_file 'config/locales/en.yml', <<-YAML +en: + foo: "2" + bar: "2" +YAML + + @plugin.write 'config/locales/en.yml', <<-YAML +en: + foo: "3" +YAML + + boot_rails + + expected_locales = %W( + #{RAILS_FRAMEWORK_ROOT}/activesupport/lib/active_support/locale/en.yml + #{RAILS_FRAMEWORK_ROOT}/activemodel/lib/active_model/locale/en.yml + #{RAILS_FRAMEWORK_ROOT}/activerecord/lib/active_record/locale/en.yml + #{RAILS_FRAMEWORK_ROOT}/actionview/lib/action_view/locale/en.yml + #{@plugin.path}/config/locales/en.yml + #{app_path}/config/locales/en.yml + #{app_path}/app/locales/en.yml + ).map { |path| File.expand_path(path) } + + actual_locales = I18n.load_path.map { |path| + File.expand_path(path) + } & expected_locales # remove locales external to Rails + + assert_equal expected_locales, actual_locales + + assert_equal "2", I18n.t(:foo) + assert_equal "1", I18n.t(:bar) + end + + test "namespaced controllers with namespaced routes" do + @plugin.write "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + namespace :admin do + namespace :foo do + get "bar", to: "bar#index" + end + end + end + RUBY + + @plugin.write "app/controllers/admin/foo/bar_controller.rb", <<-RUBY + class Admin::Foo::BarController < ApplicationController + def index + render text: "Rendered from namespace" + end + end + RUBY + + boot_rails + require 'rack/test' + extend Rack::Test::Methods + + get "/admin/foo/bar" + assert_equal 200, last_response.status + assert_equal "Rendered from namespace", last_response.body + end + + test "initializers" do + $plugin_initializer = false + @plugin.write "config/initializers/foo.rb", <<-RUBY + $plugin_initializer = true + RUBY + + boot_rails + assert $plugin_initializer + end + + test "middleware referenced in configuration" do + @plugin.write "lib/bukkits.rb", <<-RUBY + class Bukkits + def initialize(app) + @app = app + end + + def call(env) + @app.call(env) + end + end + RUBY + + add_to_config "config.middleware.use \"Bukkits\"" + boot_rails + end + + test "initializers are executed after application configuration initializers" do + @plugin.write "lib/bukkits.rb", <<-RUBY + module Bukkits + class Engine < ::Rails::Engine + initializer "dummy_initializer" do + end + end + end + RUBY + + boot_rails + + initializers = Rails.application.initializers.tsort + dummy_index = initializers.index { |i| i.name == "dummy_initializer" } + config_index = initializers.rindex { |i| i.name == :load_config_initializers } + stack_index = initializers.index { |i| i.name == :build_middleware_stack } + + assert config_index < dummy_index + assert dummy_index < stack_index + end + + class Upcaser + def initialize(app) + @app = app + end + + def call(env) + response = @app.call(env) + response[2].each(&:upcase!) + response + end + end + + test "engine is a rack app and can have its own middleware stack" do + add_to_config("config.action_dispatch.show_exceptions = false") + + @plugin.write "lib/bukkits.rb", <<-RUBY + module Bukkits + class Engine < ::Rails::Engine + endpoint lambda { |env| [200, {'Content-Type' => 'text/html'}, ['Hello World']] } + config.middleware.use ::RailtiesTest::EngineTest::Upcaser + end + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + mount(Bukkits::Engine => "/bukkits") + end + RUBY + + boot_rails + + get("/bukkits") + assert_equal "HELLO WORLD", last_response.body + end + + test "pass the value of the segment" do + controller "foo", <<-RUBY + class FooController < ActionController::Base + def index + render text: params[:username] + end + end + RUBY + + @plugin.write "config/routes.rb", <<-RUBY + Bukkits::Engine.routes.draw do + root to: "foo#index" + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + mount(Bukkits::Engine => "/:username") + end + RUBY + + boot_rails + + get("/arunagw") + assert_equal "arunagw", last_response.body + + end + + test "it provides routes as default endpoint" do + @plugin.write "lib/bukkits.rb", <<-RUBY + module Bukkits + class Engine < ::Rails::Engine + end + end + RUBY + + @plugin.write "config/routes.rb", <<-RUBY + Bukkits::Engine.routes.draw do + get "/foo" => lambda { |env| [200, {'Content-Type' => 'text/html'}, ['foo']] } + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + mount(Bukkits::Engine => "/bukkits") + end + RUBY + + boot_rails + + get("/bukkits/foo") + assert_equal "foo", last_response.body + end + + test "it loads its environments file" do + @plugin.write "lib/bukkits.rb", <<-RUBY + module Bukkits + class Engine < ::Rails::Engine + config.paths["config/environments"].push "config/environments/additional.rb" + end + end + RUBY + + @plugin.write "config/environments/development.rb", <<-RUBY + Bukkits::Engine.configure do + config.environment_loaded = true + end + RUBY + + @plugin.write "config/environments/additional.rb", <<-RUBY + Bukkits::Engine.configure do + config.additional_environment_loaded = true + end + RUBY + + boot_rails + + assert Bukkits::Engine.config.environment_loaded + assert Bukkits::Engine.config.additional_environment_loaded + end + + test "it passes router in env" do + @plugin.write "lib/bukkits.rb", <<-RUBY + module Bukkits + class Engine < ::Rails::Engine + endpoint lambda { |env| [200, {'Content-Type' => 'text/html'}, ['hello']] } + end + end + RUBY + + require "rack/file" + boot_rails + + env = Rack::MockRequest.env_for("/") + Bukkits::Engine.call(env) + assert_equal Bukkits::Engine.routes, env['action_dispatch.routes'] + + env = Rack::MockRequest.env_for("/") + Rails.application.call(env) + assert_equal Rails.application.routes, env['action_dispatch.routes'] + end + + test "isolated engine should include only its own routes and helpers" do + @plugin.write "lib/bukkits.rb", <<-RUBY + module Bukkits + class Engine < ::Rails::Engine + isolate_namespace Bukkits + end + end + RUBY + + @plugin.write "app/models/bukkits/post.rb", <<-RUBY + module Bukkits + class Post + include ActiveModel::Model + + def to_param + "1" + end + + def persisted? + true + end + end + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get "/bar" => "bar#index", as: "bar" + mount Bukkits::Engine => "/bukkits", as: "bukkits" + end + RUBY + + @plugin.write "config/routes.rb", <<-RUBY + Bukkits::Engine.routes.draw do + get "/foo" => "foo#index", as: "foo" + get "/foo/show" => "foo#show" + get "/from_app" => "foo#from_app" + get "/routes_helpers_in_view" => "foo#routes_helpers_in_view" + get "/polymorphic_path_without_namespace" => "foo#polymorphic_path_without_namespace" + resources :posts + end + RUBY + + app_file "app/helpers/some_helper.rb", <<-RUBY + module SomeHelper + def something + "Something... Something... Something..." + end + end + RUBY + + @plugin.write "app/helpers/engine_helper.rb", <<-RUBY + module EngineHelper + def help_the_engine + "Helped." + end + end + RUBY + + @plugin.write "app/controllers/bukkits/foo_controller.rb", <<-RUBY + class Bukkits::FooController < ActionController::Base + def index + render inline: "<%= help_the_engine %>" + end + + def show + render text: foo_path + end + + def from_app + render inline: "<%= (self.respond_to?(:bar_path) || self.respond_to?(:something)) %>" + end + + def routes_helpers_in_view + render inline: "<%= foo_path %>, <%= main_app.bar_path %>" + end + + def polymorphic_path_without_namespace + render text: polymorphic_path(Post.new) + end + end + RUBY + + @plugin.write "app/mailers/bukkits/my_mailer.rb", <<-RUBY + module Bukkits + class MyMailer < ActionMailer::Base + end + end + RUBY + + add_to_config("config.action_dispatch.show_exceptions = false") + + boot_rails + + assert_equal "bukkits_", Bukkits.table_name_prefix + assert_equal "bukkits", Bukkits::Engine.engine_name + assert_equal Bukkits.railtie_namespace, Bukkits::Engine + assert ::Bukkits::MyMailer.method_defined?(:foo_url) + assert !::Bukkits::MyMailer.method_defined?(:bar_url) + + get("/bukkits/from_app") + assert_equal "false", last_response.body + + get("/bukkits/foo/show") + assert_equal "/bukkits/foo", last_response.body + + get("/bukkits/foo") + assert_equal "Helped.", last_response.body + + get("/bukkits/routes_helpers_in_view") + assert_equal "/bukkits/foo, /bar", last_response.body + + get("/bukkits/polymorphic_path_without_namespace") + assert_equal "/bukkits/posts/1", last_response.body + end + + test "isolated engine should avoid namespace in names if that's possible" do + @plugin.write "lib/bukkits.rb", <<-RUBY + module Bukkits + class Engine < ::Rails::Engine + isolate_namespace Bukkits + end + end + RUBY + + @plugin.write "app/models/bukkits/post.rb", <<-RUBY + module Bukkits + class Post + include ActiveModel::Model + attr_accessor :title + + def to_param + "1" + end + + def persisted? + false + end + end + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + mount Bukkits::Engine => "/bukkits", as: "bukkits" + end + RUBY + + @plugin.write "config/routes.rb", <<-RUBY + Bukkits::Engine.routes.draw do + resources :posts + end + RUBY + + @plugin.write "app/controllers/bukkits/posts_controller.rb", <<-RUBY + class Bukkits::PostsController < ActionController::Base + def new + end + end + RUBY + + @plugin.write "app/views/bukkits/posts/new.html.erb", <<-ERB + <%= form_for(Bukkits::Post.new) do |f| %> + <%= f.text_field :title %> + <% end %> + ERB + + add_to_config("config.action_dispatch.show_exceptions = false") + + boot_rails + + get("/bukkits/posts/new") + assert_match(/name="post\[title\]"/, last_response.body) + end + + test "isolated engine should set correct route module prefix for nested namespace" do + @plugin.write "lib/bukkits.rb", <<-RUBY + module Bukkits + module Awesome + class Engine < ::Rails::Engine + isolate_namespace Bukkits::Awesome + end + end + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + mount Bukkits::Awesome::Engine => "/bukkits", :as => "bukkits" + end + RUBY + + @plugin.write "config/routes.rb", <<-RUBY + Bukkits::Awesome::Engine.routes.draw do + get "/foo" => "foo#index" + end + RUBY + + @plugin.write "app/controllers/bukkits/awesome/foo_controller.rb", <<-RUBY + class Bukkits::Awesome::FooController < ActionController::Base + def index + render :text => "ok" + end + end + RUBY + + add_to_config("config.action_dispatch.show_exceptions = false") + + boot_rails + + get("/bukkits/foo") + assert_equal "ok", last_response.body + end + + test "loading seed data" do + @plugin.write "db/seeds.rb", <<-RUBY + Bukkits::Engine.config.bukkits_seeds_loaded = true + RUBY + + app_file "db/seeds.rb", <<-RUBY + Rails.application.config.app_seeds_loaded = true + RUBY + + boot_rails + + Rails.application.load_seed + assert Rails.application.config.app_seeds_loaded + assert_raise(NoMethodError) { Bukkits::Engine.config.bukkits_seeds_loaded } + + Bukkits::Engine.load_seed + assert Bukkits::Engine.config.bukkits_seeds_loaded + end + + test "skips nonexistent seed data" do + FileUtils.rm "#{app_path}/db/seeds.rb" + boot_rails + assert_nil Rails.application.load_seed + end + + test "using namespace more than once on one module should not overwrite railtie_namespace method" do + @plugin.write "lib/bukkits.rb", <<-RUBY + module AppTemplate + class Engine < ::Rails::Engine + isolate_namespace(AppTemplate) + end + end + RUBY + + add_to_config "isolate_namespace AppTemplate" + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do end + RUBY + + boot_rails + + assert_equal AppTemplate.railtie_namespace, AppTemplate::Engine + end + + test "properly reload routes" do + # when routes are inside application class definition + # they should not be reloaded when engine's routes + # file has changed + add_to_config <<-RUBY + routes do + mount lambda{|env| [200, {}, ["foo"]]} => "/foo" + mount Bukkits::Engine => "/bukkits" + end + RUBY + + FileUtils.rm(File.join(app_path, "config/routes.rb")) + + @plugin.write "config/routes.rb", <<-RUBY + Bukkits::Engine.routes.draw do + mount lambda{|env| [200, {}, ["bar"]]} => "/bar" + end + RUBY + + @plugin.write "lib/bukkits.rb", <<-RUBY + module Bukkits + class Engine < ::Rails::Engine + isolate_namespace(Bukkits) + end + end + RUBY + + boot_rails + + get("/foo") + assert_equal "foo", last_response.body + + get("/bukkits/bar") + assert_equal "bar", last_response.body + end + + test "setting generators for engine and overriding app generator's" do + @plugin.write "lib/bukkits.rb", <<-RUBY + module Bukkits + class Engine < ::Rails::Engine + config.generators do |g| + g.orm :data_mapper + g.template_engine :haml + g.test_framework :rspec + end + + config.app_generators do |g| + g.orm :mongoid + g.template_engine :liquid + g.test_framework :shoulda + end + end + end + RUBY + + add_to_config <<-RUBY + config.generators do |g| + g.test_framework :test_unit + end + RUBY + + boot_rails + + app_generators = Rails.application.config.generators.options[:rails] + assert_equal :mongoid , app_generators[:orm] + assert_equal :liquid , app_generators[:template_engine] + assert_equal :test_unit, app_generators[:test_framework] + + generators = Bukkits::Engine.config.generators.options[:rails] + assert_equal :data_mapper, generators[:orm] + assert_equal :haml , generators[:template_engine] + assert_equal :rspec , generators[:test_framework] + end + + test "engine should get default generators with ability to overwrite them" do + @plugin.write "lib/bukkits.rb", <<-RUBY + module Bukkits + class Engine < ::Rails::Engine + config.generators.test_framework :rspec + end + end + RUBY + + boot_rails + + generators = Bukkits::Engine.config.generators.options[:rails] + assert_equal :active_record, generators[:orm] + assert_equal :rspec , generators[:test_framework] + + app_generators = Rails.application.config.generators.options[:rails] + assert_equal :test_unit , app_generators[:test_framework] + end + + test "do not create table_name_prefix method if it already exists" do + @plugin.write "lib/bukkits.rb", <<-RUBY + module Bukkits + def self.table_name_prefix + "foo" + end + + class Engine < ::Rails::Engine + isolate_namespace(Bukkits) + end + end + RUBY + + boot_rails + + assert_equal "foo", Bukkits.table_name_prefix + end + + test "fetching engine by path" do + @plugin.write "lib/bukkits.rb", <<-RUBY + module Bukkits + class Engine < ::Rails::Engine + end + end + RUBY + + boot_rails + + assert_equal Bukkits::Engine.instance, Rails::Engine.find(@plugin.path) + + # check expanding paths + engine_dir = @plugin.path.chomp("/").split("/").last + engine_path = File.join(@plugin.path, '..', engine_dir) + assert_equal Bukkits::Engine.instance, Rails::Engine.find(engine_path) + end + + test "gather isolated engine's helpers in Engine#helpers" do + @plugin.write "lib/bukkits.rb", <<-RUBY + module Bukkits + class Engine < ::Rails::Engine + isolate_namespace Bukkits + end + end + RUBY + + app_file "app/helpers/some_helper.rb", <<-RUBY + module SomeHelper + def foo + 'foo' + end + end + RUBY + + @plugin.write "app/helpers/bukkits/engine_helper.rb", <<-RUBY + module Bukkits + module EngineHelper + def bar + 'bar' + end + end + end + RUBY + + @plugin.write "app/helpers/engine_helper.rb", <<-RUBY + module EngineHelper + def baz + 'baz' + end + end + RUBY + + add_to_config("config.action_dispatch.show_exceptions = false") + + boot_rails + + assert_equal [:bar, :baz], Bukkits::Engine.helpers.public_instance_methods.sort + end + + test "setting priority for engines with config.railties_order" do + @blog = engine "blog" do |plugin| + plugin.write "lib/blog.rb", <<-RUBY + module Blog + class Engine < ::Rails::Engine + end + end + RUBY + end + + @plugin.write "lib/bukkits.rb", <<-RUBY + module Bukkits + class Engine < ::Rails::Engine + isolate_namespace Bukkits + end + end + RUBY + + controller "main", <<-RUBY + class MainController < ActionController::Base + def foo + render inline: '<%= render :partial => "shared/foo" %>' + end + + def bar + render inline: '<%= render :partial => "shared/bar" %>' + end + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get "/foo" => "main#foo" + get "/bar" => "main#bar" + end + RUBY + + @plugin.write "app/views/shared/_foo.html.erb", <<-RUBY + Bukkit's foo partial + RUBY + + app_file "app/views/shared/_foo.html.erb", <<-RUBY + App's foo partial + RUBY + + @blog.write "app/views/shared/_bar.html.erb", <<-RUBY + Blog's bar partial + RUBY + + app_file "app/views/shared/_bar.html.erb", <<-RUBY + App's bar partial + RUBY + + @plugin.write "app/assets/javascripts/foo.js", <<-RUBY + // Bukkit's foo js + RUBY + + app_file "app/assets/javascripts/foo.js", <<-RUBY + // App's foo js + RUBY + + @blog.write "app/assets/javascripts/bar.js", <<-RUBY + // Blog's bar js + RUBY + + app_file "app/assets/javascripts/bar.js", <<-RUBY + // App's bar js + RUBY + + add_to_config("config.railties_order = [:all, :main_app, Blog::Engine]") + add_to_env_config "development", "config.assets.digest = false" + + boot_rails + + get("/foo") + assert_equal "Bukkit's foo partial", last_response.body.strip + + get("/bar") + assert_equal "App's bar partial", last_response.body.strip + + get("/assets/foo.js") + assert_equal "// Bukkit's foo js", last_response.body.strip + + get("/assets/bar.js") + assert_equal "// App's bar js", last_response.body.strip + + # ensure that railties are not added twice + railties = Rails.application.send(:ordered_railties).map(&:class) + assert_equal railties, railties.uniq + end + + test "railties_order adds :all with lowest priority if not given" do + @plugin.write "lib/bukkits.rb", <<-RUBY + module Bukkits + class Engine < ::Rails::Engine + end + end + RUBY + + controller "main", <<-RUBY + class MainController < ActionController::Base + def foo + render inline: '<%= render :partial => "shared/foo" %>' + end + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get "/foo" => "main#foo" + end + RUBY + + @plugin.write "app/views/shared/_foo.html.erb", <<-RUBY + Bukkit's foo partial + RUBY + + app_file "app/views/shared/_foo.html.erb", <<-RUBY + App's foo partial + RUBY + + add_to_config("config.railties_order = [Bukkits::Engine]") + + boot_rails + + get("/foo") + assert_equal "Bukkit's foo partial", last_response.body.strip + end + + test "engine can be properly mounted at root" do + add_to_config("config.action_dispatch.show_exceptions = false") + add_to_config("config.public_file_server.enabled = false") + + @plugin.write "lib/bukkits.rb", <<-RUBY + module Bukkits + class Engine < ::Rails::Engine + isolate_namespace ::Bukkits + end + end + RUBY + + @plugin.write "config/routes.rb", <<-RUBY + Bukkits::Engine.routes.draw do + root "foo#index" + end + RUBY + + @plugin.write "app/controllers/bukkits/foo_controller.rb", <<-RUBY + module Bukkits + class FooController < ActionController::Base + def index + text = <<-TEXT + script_name: \#{request.script_name} + fullpath: \#{request.fullpath} + path: \#{request.path} + TEXT + render text: text + end + end + end + RUBY + + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + mount Bukkits::Engine => "/" + end + RUBY + + boot_rails + + expected = <<-TEXT + script_name: + fullpath: / + path: / + TEXT + + get("/") + assert_equal expected.split("\n").map(&:strip), + last_response.body.split("\n").map(&:strip) + end + + test "paths are properly generated when application is mounted at sub-path" do + @plugin.write "lib/bukkits.rb", <<-RUBY + module Bukkits + class Engine < ::Rails::Engine + isolate_namespace Bukkits + end + end + RUBY + + app_file "app/controllers/bar_controller.rb", <<-RUBY + class BarController < ApplicationController + def index + render text: bukkits.bukkit_path + end + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get '/bar' => 'bar#index', :as => 'bar' + mount Bukkits::Engine => "/bukkits", :as => "bukkits" + end + RUBY + + @plugin.write "config/routes.rb", <<-RUBY + Bukkits::Engine.routes.draw do + get '/bukkit' => 'bukkit#index' + end + RUBY + + + @plugin.write "app/controllers/bukkits/bukkit_controller.rb", <<-RUBY + class Bukkits::BukkitController < ActionController::Base + def index + render text: main_app.bar_path + end + end + RUBY + + boot_rails + + get("/bukkits/bukkit", {}, {'SCRIPT_NAME' => '/foo'}) + assert_equal '/foo/bar', last_response.body + + get("/bar", {}, {'SCRIPT_NAME' => '/foo'}) + assert_equal '/foo/bukkits/bukkit', last_response.body + end + + test "paths are properly generated when application is mounted at sub-path and relative_url_root is set" do + add_to_config "config.relative_url_root = '/foo'" + + @plugin.write "lib/bukkits.rb", <<-RUBY + module Bukkits + class Engine < ::Rails::Engine + isolate_namespace Bukkits + end + end + RUBY + + app_file "app/controllers/bar_controller.rb", <<-RUBY + class BarController < ApplicationController + def index + render text: bukkits.bukkit_path + end + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get '/bar' => 'bar#index', :as => 'bar' + mount Bukkits::Engine => "/bukkits", :as => "bukkits" + end + RUBY + + @plugin.write "config/routes.rb", <<-RUBY + Bukkits::Engine.routes.draw do + get '/bukkit' => 'bukkit#index' + end + RUBY + + @plugin.write "app/controllers/bukkits/bukkit_controller.rb", <<-RUBY + class Bukkits::BukkitController < ActionController::Base + def index + render text: main_app.bar_path + end + end + RUBY + + boot_rails + + get("/bukkits/bukkit", {}, {'SCRIPT_NAME' => '/foo'}) + assert_equal '/foo/bar', last_response.body + + get("/bar", {}, {'SCRIPT_NAME' => '/foo'}) + assert_equal '/foo/bukkits/bukkit', last_response.body + end + + private + def app + Rails.application + end + end +end diff --git a/railties/test/railties/generators_test.rb b/railties/test/railties/generators_test.rb new file mode 100644 index 0000000000..5f4171d44b --- /dev/null +++ b/railties/test/railties/generators_test.rb @@ -0,0 +1,134 @@ +RAILS_ISOLATED_ENGINE = true +require "isolation/abstract_unit" + +require 'generators/generators_test_helper' +require "rails/generators/test_case" + +module RailtiesTests + class GeneratorTest < Rails::Generators::TestCase + include ActiveSupport::Testing::Isolation + + def destination_root + tmp_path 'foo_bar' + end + + def tmp_path(*args) + @tmp_path ||= File.realpath(Dir.mktmpdir) + File.join(@tmp_path, *args) + end + + def engine_path + tmp_path('foo_bar') + end + + def bundled_rails(cmd) + `bundle exec rails #{cmd}` + end + + def rails(cmd) + environment = File.expand_path('../../../../load_paths', __FILE__) + if File.exist?("#{environment}.rb") + require_environment = "-r #{environment}" + end + `#{Gem.ruby} #{require_environment} #{RAILS_FRAMEWORK_ROOT}/railties/exe/rails #{cmd}` + end + + def build_engine(is_mountable=false) + FileUtils.rm_rf(engine_path) + FileUtils.mkdir_p(engine_path) + + mountable = is_mountable ? "--mountable" : "" + + rails("plugin new #{engine_path} --full #{mountable}") + + Dir.chdir(engine_path) do + File.open("Gemfile", "w") do |f| + f.write <<-GEMFILE.gsub(/^ {12}/, '') + source "https://rubygems.org" + + gem 'rails', path: '#{RAILS_FRAMEWORK_ROOT}' + gem 'sqlite3' + GEMFILE + end + end + end + + def build_mountable_engine + build_engine(true) + end + + def test_controllers_are_correctly_namespaced_when_engine_is_mountable + build_mountable_engine + Dir.chdir(engine_path) do + bundled_rails("g controller topics") + assert_file "app/controllers/foo_bar/topics_controller.rb", /module FooBar\n class TopicsController/ + assert_no_file "app/controllers/topics_controller.rb" + end + end + + def test_models_are_correctly_namespaced_when_engine_is_mountable + build_mountable_engine + Dir.chdir(engine_path) do + bundled_rails("g model topic") + assert_file "app/models/foo_bar/topic.rb", /module FooBar\n class Topic/ + assert_no_file "app/models/topic.rb" + end + end + + def test_table_name_prefix_is_correctly_namespaced_when_engine_is_mountable + build_mountable_engine + Dir.chdir(engine_path) do + bundled_rails("g model namespaced/topic") + assert_file "app/models/foo_bar/namespaced.rb", /module FooBar\n module Namespaced/ do |content| + assert_class_method :table_name_prefix, content do |method_content| + assert_match(/'foo_bar_namespaced_'/, method_content) + end + end + end + end + + def test_helpers_are_correctly_namespaced_when_engine_is_mountable + build_mountable_engine + Dir.chdir(engine_path) do + bundled_rails("g helper topics") + assert_file "app/helpers/foo_bar/topics_helper.rb", /module FooBar\n module TopicsHelper/ + assert_no_file "app/helpers/topics_helper.rb" + end + end + + def test_controllers_are_not_namespaced_when_engine_is_not_mountable + build_engine + Dir.chdir(engine_path) do + bundled_rails("g controller topics") + assert_file "app/controllers/topics_controller.rb", /class TopicsController/ + assert_no_file "app/controllers/foo_bar/topics_controller.rb" + end + end + + def test_models_are_not_namespaced_when_engine_is_not_mountable + build_engine + Dir.chdir(engine_path) do + bundled_rails("g model topic") + assert_file "app/models/topic.rb", /class Topic/ + assert_no_file "app/models/foo_bar/topic.rb" + end + end + + def test_helpers_are_not_namespaced_when_engine_is_not_mountable + build_engine + Dir.chdir(engine_path) do + bundled_rails("g helper topics") + assert_file "app/helpers/topics_helper.rb", /module TopicsHelper/ + assert_no_file "app/helpers/foo_bar/topics_helper.rb" + end + end + + def test_assert_file_with_special_characters + path = "#{app_path}/tmp" + file_name = "#{path}/v0.1.4~alpha+nightly" + FileUtils.mkdir_p path + FileUtils.touch file_name + assert_file file_name + end + end +end diff --git a/railties/test/railties/mounted_engine_test.rb b/railties/test/railties/mounted_engine_test.rb new file mode 100644 index 0000000000..fb2071c7c3 --- /dev/null +++ b/railties/test/railties/mounted_engine_test.rb @@ -0,0 +1,274 @@ +require 'isolation/abstract_unit' + +module ApplicationTests + class ApplicationRoutingTest < ActiveSupport::TestCase + require 'rack/test' + include Rack::Test::Methods + include ActiveSupport::Testing::Isolation + + def setup + build_app + + add_to_config("config.action_dispatch.show_exceptions = false") + + @simple_plugin = engine "weblog" + @plugin = engine "blog" + @metrics_plugin = engine "metrics" + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + mount Weblog::Engine, :at => '/', :as => 'weblog' + resources :posts + get "/engine_route" => "application_generating#engine_route" + get "/engine_route_in_view" => "application_generating#engine_route_in_view" + get "/weblog_engine_route" => "application_generating#weblog_engine_route" + get "/weblog_engine_route_in_view" => "application_generating#weblog_engine_route_in_view" + get "/url_for_engine_route" => "application_generating#url_for_engine_route" + get "/polymorphic_route" => "application_generating#polymorphic_route" + get "/application_polymorphic_path" => "application_generating#application_polymorphic_path" + scope "/:user", :user => "anonymous" do + mount Blog::Engine => "/blog" + end + mount Metrics::Engine => "/metrics" + root :to => 'main#index' + end + RUBY + + + @simple_plugin.write "lib/weblog.rb", <<-RUBY + module Weblog + class Engine < ::Rails::Engine + end + end + RUBY + + @simple_plugin.write "config/routes.rb", <<-RUBY + Weblog::Engine.routes.draw do + get '/weblog' => "weblogs#index", as: 'weblogs' + end + RUBY + + @simple_plugin.write "app/controllers/weblogs_controller.rb", <<-RUBY + class WeblogsController < ActionController::Base + def index + render text: request.url + end + end + RUBY + + @metrics_plugin.write "lib/metrics.rb", <<-RUBY + module Metrics + class Engine < ::Rails::Engine + isolate_namespace(Metrics) + end + end + RUBY + + @metrics_plugin.write "config/routes.rb", <<-RUBY + Metrics::Engine.routes.draw do + get '/generate_blog_route', to: 'generating#generate_blog_route' + get '/generate_blog_route_in_view', to: 'generating#generate_blog_route_in_view' + end + RUBY + + @metrics_plugin.write "app/controllers/metrics/generating_controller.rb", <<-RUBY + module Metrics + class GeneratingController < ActionController::Base + def generate_blog_route + render text: blog.post_path(1) + end + + def generate_blog_route_in_view + render inline: "<%= blog.post_path(1) -%>" + end + end + end + RUBY + + @plugin.write "app/models/blog/post.rb", <<-RUBY + module Blog + class Post + include ActiveModel::Model + + def id + 44 + end + + def persisted? + true + end + end + end + RUBY + + @plugin.write "lib/blog.rb", <<-RUBY + module Blog + class Engine < ::Rails::Engine + isolate_namespace(Blog) + end + end + RUBY + + @plugin.write "config/routes.rb", <<-RUBY + Blog::Engine.routes.draw do + resources :posts + get '/generate_application_route', to: 'posts#generate_application_route' + get '/application_route_in_view', to: 'posts#application_route_in_view' + get '/engine_polymorphic_path', to: 'posts#engine_polymorphic_path' + get '/engine_asset_path', to: 'posts#engine_asset_path' + end + RUBY + + @plugin.write "app/controllers/blog/posts_controller.rb", <<-RUBY + module Blog + class PostsController < ActionController::Base + def index + render text: blog.post_path(1) + end + + def generate_application_route + path = main_app.url_for(controller: "/main", + action: "index", + only_path: true) + render text: path + end + + def application_route_in_view + render inline: "<%= main_app.root_path %>" + end + + def engine_polymorphic_path + render text: polymorphic_path(Post.new) + end + + def engine_asset_path + render inline: "<%= asset_path 'images/foo.png' %>" + end + end + end + RUBY + + app_file "app/controllers/application_generating_controller.rb", <<-RUBY + class ApplicationGeneratingController < ActionController::Base + def engine_route + render text: blog.posts_path + end + + def engine_route_in_view + render inline: "<%= blog.posts_path %>" + end + + def weblog_engine_route + render text: weblog.weblogs_path + end + + def weblog_engine_route_in_view + render inline: "<%= weblog.weblogs_path %>" + end + + def url_for_engine_route + render text: blog.url_for(controller: "blog/posts", action: "index", user: "john", only_path: true) + end + + def polymorphic_route + render text: polymorphic_url([blog, Blog::Post.new]) + end + + def application_polymorphic_path + render text: polymorphic_path(Blog::Post.new) + end + end + RUBY + + boot_rails + end + + def teardown + teardown_app + end + + def app + @app ||= begin + require "#{app_path}/config/environment" + Rails.application + end + end + + test "routes generation in engine and application" do + # test generating engine's route from engine + get "/john/blog/posts" + assert_equal "/john/blog/posts/1", last_response.body + + # test generating engine's route from engine with default_url_options + get "/john/blog/posts", {}, 'SCRIPT_NAME' => "/foo" + assert_equal "/foo/john/blog/posts/1", last_response.body + + # test generating engine's route from application + get "/engine_route" + assert_equal "/anonymous/blog/posts", last_response.body + + get "/engine_route_in_view" + assert_equal "/anonymous/blog/posts", last_response.body + + get "/url_for_engine_route" + assert_equal "/john/blog/posts", last_response.body + + # test generating engine's route from application with default_url_options + get "/engine_route", {}, 'SCRIPT_NAME' => "/foo" + assert_equal "/foo/anonymous/blog/posts", last_response.body + + get "/url_for_engine_route", {}, 'SCRIPT_NAME' => "/foo" + assert_equal "/foo/john/blog/posts", last_response.body + + # test generating application's route from engine + get "/someone/blog/generate_application_route" + assert_equal "/", last_response.body + + get "/somone/blog/application_route_in_view" + assert_equal "/", last_response.body + + # test generating engine's route from other engine + get "/metrics/generate_blog_route" + assert_equal '/anonymous/blog/posts/1', last_response.body + + get "/metrics/generate_blog_route_in_view" + assert_equal '/anonymous/blog/posts/1', last_response.body + + # test generating engine's route from other engine with default_url_options + get "/metrics/generate_blog_route", {}, 'SCRIPT_NAME' => '/foo' + assert_equal '/foo/anonymous/blog/posts/1', last_response.body + + get "/metrics/generate_blog_route_in_view", {}, 'SCRIPT_NAME' => '/foo' + assert_equal '/foo/anonymous/blog/posts/1', last_response.body + + + # test generating application's route from engine with default_url_options + get "/someone/blog/generate_application_route", {}, 'SCRIPT_NAME' => '/foo' + assert_equal "/foo/", last_response.body + + # test polymorphic routes + get "/polymorphic_route" + assert_equal "http://example.org/anonymous/blog/posts/44", last_response.body + + # test that correct path is generated for the same polymorphic_path call in an engine + get "/somone/blog/engine_polymorphic_path" + assert_equal "/somone/blog/posts/44", last_response.body + + # and in an application + get "/application_polymorphic_path" + assert_equal "/posts/44", last_response.body + + # test that asset path will not get script_name when generated in the engine + get "/someone/blog/engine_asset_path" + assert_equal "/images/foo.png", last_response.body + end + + test "route path for controller action when engine is mounted at root" do + get "/weblog_engine_route" + assert_equal "/weblog", last_response.body + + get "/weblog_engine_route_in_view" + assert_equal "/weblog", last_response.body + end + end +end diff --git a/railties/test/railties/railtie_test.rb b/railties/test/railties/railtie_test.rb new file mode 100644 index 0000000000..5042d628cf --- /dev/null +++ b/railties/test/railties/railtie_test.rb @@ -0,0 +1,208 @@ +require "isolation/abstract_unit" + +module RailtiesTest + class RailtieTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + FileUtils.rm_rf("#{app_path}/config/environments") + require "rails/all" + end + + def teardown + teardown_app + end + + def app + @app ||= Rails.application + end + + test "cannot instantiate a Railtie object" do + assert_raise(RuntimeError) { Rails::Railtie.new } + end + + test "Railtie provides railtie_name" do + begin + class ::FooBarBaz < Rails::Railtie ; end + assert_equal "foo_bar_baz", FooBarBaz.railtie_name + ensure + Object.send(:remove_const, :"FooBarBaz") + end + end + + test "railtie_name can be set manually" do + class Foo < Rails::Railtie + railtie_name "bar" + end + assert_equal "bar", Foo.railtie_name + end + + test "config is available to railtie" do + class Foo < Rails::Railtie ; end + assert_nil Foo.config.action_controller.foo + end + + test "config name is available for the railtie" do + class Foo < Rails::Railtie + config.foo = ActiveSupport::OrderedOptions.new + config.foo.greetings = "hello" + end + assert_equal "hello", Foo.config.foo.greetings + end + + test "railtie configurations are available in the application" do + class Foo < Rails::Railtie + config.foo = ActiveSupport::OrderedOptions.new + config.foo.greetings = "hello" + end + require "#{app_path}/config/application" + assert_equal "hello", Rails.application.config.foo.greetings + end + + test "railtie can add to_prepare callbacks" do + $to_prepare = false + class Foo < Rails::Railtie ; config.to_prepare { $to_prepare = true } ; end + assert !$to_prepare + require "#{app_path}/config/environment" + require "rack/test" + extend Rack::Test::Methods + get "/" + assert $to_prepare + end + + test "railtie have access to application in before_configuration callbacks" do + $before_configuration = false + class Foo < Rails::Railtie ; config.before_configuration { $before_configuration = Rails.root.to_path } ; end + assert_not $before_configuration + require "#{app_path}/config/environment" + assert_equal app_path, $before_configuration + end + + test "railtie can add after_initialize callbacks" do + $after_initialize = false + class Foo < Rails::Railtie ; config.after_initialize { $after_initialize = true } ; end + assert !$after_initialize + require "#{app_path}/config/environment" + assert $after_initialize + end + + test "rake_tasks block is executed when MyApp.load_tasks is called" do + $ran_block = false + + class MyTie < Rails::Railtie + rake_tasks do + $ran_block = true + end + end + + require "#{app_path}/config/environment" + + assert !$ran_block + require 'rake' + require 'rake/testtask' + require 'rdoc/task' + + Rails.application.load_tasks + assert $ran_block + end + + test "rake_tasks block defined in superclass of railtie is also executed" do + $ran_block = [] + + class Rails::Railtie + rake_tasks do + $ran_block << railtie_name + end + end + + class MyTie < Rails::Railtie + railtie_name "my_tie" + end + + require "#{app_path}/config/environment" + + assert_equal [], $ran_block + require 'rake' + require 'rake/testtask' + require 'rdoc/task' + + Rails.application.load_tasks + assert $ran_block.include?("my_tie") + end + + test "generators block is executed when MyApp.load_generators is called" do + $ran_block = false + + class MyTie < Rails::Railtie + generators do + $ran_block = true + end + end + + require "#{app_path}/config/environment" + + assert !$ran_block + Rails.application.load_generators + assert $ran_block + end + + test "console block is executed when MyApp.load_console is called" do + $ran_block = false + + class MyTie < Rails::Railtie + console do + $ran_block = true + end + end + + require "#{app_path}/config/environment" + + assert !$ran_block + Rails.application.load_console + assert $ran_block + end + + test "runner block is executed when MyApp.load_runner is called" do + $ran_block = false + + class MyTie < Rails::Railtie + runner do + $ran_block = true + end + end + + require "#{app_path}/config/environment" + + assert !$ran_block + Rails.application.load_runner + assert $ran_block + end + + test "railtie can add initializers" do + $ran_block = false + + class MyTie < Rails::Railtie + initializer :something_nice do + $ran_block = true + end + end + + assert !$ran_block + require "#{app_path}/config/environment" + assert $ran_block + end + + test "we can change our environment if we want to" do + begin + original_env = Rails.env + Rails.env = 'foo' + assert_equal('foo', Rails.env) + ensure + Rails.env = original_env + assert_equal(original_env, Rails.env) + end + end + end +end diff --git a/railties/test/test_unit/reporter_test.rb b/railties/test/test_unit/reporter_test.rb new file mode 100644 index 0000000000..e517d8dd0b --- /dev/null +++ b/railties/test/test_unit/reporter_test.rb @@ -0,0 +1,147 @@ +require 'abstract_unit' +require 'rails/test_unit/reporter' + +class TestUnitReporterTest < ActiveSupport::TestCase + class ExampleTest < Minitest::Test + def woot; end + end + + setup do + @output = StringIO.new + @reporter = Rails::TestUnitReporter.new @output, output_inline: true + end + + test "prints rerun snippet to run a single failed test" do + @reporter.record(failed_test) + @reporter.report + + assert_match %r{^bin/rails test .*test/test_unit/reporter_test.rb:\d+$}, @output.string + assert_rerun_snippet_count 1 + end + + test "prints rerun snippet for every failed test" do + @reporter.record(failed_test) + @reporter.record(failed_test) + @reporter.record(failed_test) + @reporter.report + + assert_rerun_snippet_count 3 + end + + test "does not print snippet for successful and skipped tests" do + @reporter.record(passing_test) + @reporter.record(skipped_test) + @reporter.report + assert_no_match 'Failed tests:', @output.string + assert_rerun_snippet_count 0 + end + + test "prints rerun snippet for skipped tests if run in verbose mode" do + verbose = Rails::TestUnitReporter.new @output, verbose: true + verbose.record(skipped_test) + verbose.report + + assert_rerun_snippet_count 1 + end + + test "allows to customize the executable in the rerun snippet" do + original_executable = Rails::TestUnitReporter.executable + begin + Rails::TestUnitReporter.executable = "bin/test" + @reporter.record(failed_test) + @reporter.report + + assert_match %r{^bin/test .*test/test_unit/reporter_test.rb:\d+$}, @output.string + ensure + Rails::TestUnitReporter.executable = original_executable + end + end + + test "outputs failures inline" do + @reporter.record(failed_test) + @reporter.report + + assert_match %r{\A\n\nboo\n\nbin/rails test .*test/test_unit/reporter_test.rb:\d+\n\n\z}, @output.string + end + + test "outputs errors inline" do + @reporter.record(errored_test) + @reporter.report + + assert_match %r{\A\n\nArgumentError: wups\n No backtrace\n\nbin/rails test .*test/test_unit/reporter_test.rb:6\n\n\z}, @output.string + end + + test "outputs skipped tests inline if verbose" do + verbose = Rails::TestUnitReporter.new @output, verbose: true, output_inline: true + verbose.record(skipped_test) + verbose.report + + assert_match %r{\A\n\nskipchurches, misstemples\n\nbin/rails test .*test/test_unit/reporter_test.rb:\d+\n\n\z}, @output.string + end + + test "does not output rerun snippets after run" do + @reporter.record(failed_test) + @reporter.report + + assert_no_match 'Failed tests:', @output.string + end + + test "fail fast interrupts run on failure" do + fail_fast = Rails::TestUnitReporter.new @output, fail_fast: true + interrupt_raised = false + + # Minitest passes through Interrupt, catch it manually. + begin + fail_fast.record(failed_test) + rescue Interrupt + interrupt_raised = true + ensure + assert interrupt_raised, 'Expected Interrupt to be raised.' + end + end + + test "fail fast does not interrupt run errors or skips" do + fail_fast = Rails::TestUnitReporter.new @output, fail_fast: true + + fail_fast.record(errored_test) + assert_no_match 'Failed tests:', @output.string + + fail_fast.record(skipped_test) + assert_no_match 'Failed tests:', @output.string + end + + private + def assert_rerun_snippet_count(snippet_count) + assert_equal snippet_count, @output.string.scan(%r{^bin/rails test }).size + end + + def failed_test + ft = ExampleTest.new(:woot) + ft.failures << begin + raise Minitest::Assertion, "boo" + rescue Minitest::Assertion => e + e + end + ft + end + + def errored_test + et = ExampleTest.new(:woot) + et.failures << Minitest::UnexpectedError.new(ArgumentError.new("wups")) + et + end + + def passing_test + ExampleTest.new(:woot) + end + + def skipped_test + st = ExampleTest.new(:woot) + st.failures << begin + raise Minitest::Skip, "skipchurches, misstemples" + rescue Minitest::Assertion => e + e + end + st + end +end diff --git a/railties/test/version_test.rb b/railties/test/version_test.rb new file mode 100644 index 0000000000..f270d8f0c9 --- /dev/null +++ b/railties/test/version_test.rb @@ -0,0 +1,12 @@ +require 'abstract_unit' + +class VersionTest < ActiveSupport::TestCase + def test_rails_version_returns_a_string + assert Rails.version.is_a? String + end + + def test_rails_gem_version_returns_a_correct_gem_version_object + assert Rails.gem_version.is_a? Gem::Version + assert_equal Rails.version, Rails.gem_version.to_s + end +end |