mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Removing Metal from Rails 3.
If you have existing Metals, you have a few options: * if your metal behaves like a middleware, add it to the middleware stack via config.middleware.use. You can use methods on the middleware stack to control exactly where it should go * if it behaves like a Rack endpoint, you can link to it in the router. This will result in more optimal routing time, and allows you to remove code in your endpoint that matches specific URLs in favor of the more powerful handling in the router itself. For the future, you can use ActionController::Metal to get a very fast controller with the ability to opt-in to specific controller features without paying the penalty of the full controller stack. Since Rails 3 is closer to Rack, the Metal abstraction is no longer needed.
This commit is contained in:
parent
e6b0ea3f8a
commit
ed34652d1a
20 changed files with 9 additions and 319 deletions
|
@ -43,7 +43,6 @@ module ActionDispatch
|
|||
|
||||
autoload_under 'middleware' do
|
||||
autoload :Callbacks
|
||||
autoload :Cascade
|
||||
autoload :Cookies
|
||||
autoload :Flash
|
||||
autoload :Head
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
module ActionDispatch
|
||||
class Cascade
|
||||
def self.new(*apps)
|
||||
apps = apps.flatten
|
||||
|
||||
case apps.length
|
||||
when 0
|
||||
raise ArgumentError, "app is required"
|
||||
when 1
|
||||
apps.first
|
||||
else
|
||||
super(apps)
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(apps)
|
||||
@apps = apps
|
||||
end
|
||||
|
||||
def call(env)
|
||||
result = nil
|
||||
@apps.each do |app|
|
||||
result = app.call(env)
|
||||
break unless result[1]["X-Cascade"] == "pass"
|
||||
end
|
||||
result
|
||||
end
|
||||
end
|
||||
end
|
|
@ -63,8 +63,6 @@ h4. Rails General Configuration
|
|||
|
||||
* +config.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then used to log information from Action Controller. Set to nil to disable logging.
|
||||
|
||||
* +config.metals+ accepts an array used as the metals to load. If this is set to nil, all metals will be loaded in alphabetical order. If this is set to [], no metals will be loaded. Otherwise metals will be loaded in the order specified
|
||||
|
||||
* +config.plugin_loader+ overrides the class that handles loading each plugin. Defaults to +Rails::Plugin::Loader+.
|
||||
|
||||
* +config.plugin_locators+ overrides the class that handle finding the desired plugins that you‘d like to load for your application. By default it is the +Rails::Plugin::FileSystemLocator+.
|
||||
|
|
|
@ -668,7 +668,6 @@ This file requires _rails/railtie.rb_ which defines +Rails::Railtie+.
|
|||
* add_routing_namespaces
|
||||
* add_locales
|
||||
* add_view_paths
|
||||
* add_metals
|
||||
* add_generator_templates
|
||||
* load_application_initializers
|
||||
* load_application_classes
|
||||
|
@ -726,7 +725,6 @@ This file is used to set up the +Rails::Paths+ module which is used to set up he
|
|||
paths.app.helpers "app/helpers", :eager_load => true
|
||||
paths.app.models "app/models", :eager_load => true
|
||||
paths.app.mailers "app/mailers", :eager_load => true
|
||||
paths.app.metals "app/metal", :eager_load => true
|
||||
paths.app.views "app/views", :eager_load => true
|
||||
paths.lib "lib", :load_path => true
|
||||
paths.lib.tasks "lib/tasks", :glob => "**/*.rake"
|
||||
|
@ -3154,7 +3152,6 @@ This method is defined like this:
|
|||
middleware.use('ActionDispatch::Cookies')
|
||||
middleware.use(lambda { ActionController::Base.session_store }, lambda { ActionController::Base.session_options })
|
||||
middleware.use('ActionDispatch::Flash', :if => lambda { ActionController::Base.session_store })
|
||||
middleware.use(lambda { Rails::Rack::Metal.new(Rails.application.config.paths.app.metals.to_a, Rails.application.config.metals) })
|
||||
middleware.use('ActionDispatch::ParamsParser')
|
||||
middleware.use('::Rack::MethodOverride')
|
||||
middleware.use('::ActionDispatch::Head')
|
||||
|
@ -3288,7 +3285,7 @@ Finally, a +Rails::Application::Configuration+ object will be returned. On this
|
|||
<ruby>
|
||||
attr_accessor :after_initialize_blocks, :cache_classes, :colorize_logging,
|
||||
:consider_all_requests_local, :dependency_loading,
|
||||
:load_once_paths, :logger, :metals, :plugins,
|
||||
:load_once_paths, :logger, :plugins,
|
||||
:preload_frameworks, :reload_plugins, :serve_static_assets,
|
||||
:time_zone, :whiny_nils
|
||||
|
||||
|
@ -3574,7 +3571,6 @@ The +super+ method it references comes from +Rails::Engine::Configuration+ which
|
|||
paths.app.controllers "app/controllers", :eager_load => true
|
||||
paths.app.helpers "app/helpers", :eager_load => true
|
||||
paths.app.models "app/models", :eager_load => true
|
||||
paths.app.metals "app/metal"
|
||||
paths.app.views "app/views"
|
||||
paths.lib "lib", :load_path => true
|
||||
paths.lib.tasks "lib/tasks", :glob => "**/*.rake"
|
||||
|
|
|
@ -222,57 +222,6 @@ use MyOwnStackFromStratch
|
|||
run ActionController::Dispatcher.new
|
||||
</ruby>
|
||||
|
||||
h3. Rails Metal Applications
|
||||
|
||||
Rails Metal applications are minimal Rack applications specially designed for integrating with a typical Rails application. As Rails Metal Applications skip all of the Action Controller stack, serving a request has no overhead from the Rails framework itself. This is especially useful for infrequent cases where the performance of the full stack Rails framework is an issue.
|
||||
|
||||
Ryan Bates' "Railscast on Rails Metal":http://railscasts.com/episodes/150-rails-metal provides a nice walkthrough generating and using Rails Metal.
|
||||
|
||||
h4. Generating a Metal Application
|
||||
|
||||
Rails provides a generator called +metal+ for creating a new Metal application:
|
||||
|
||||
<shell>
|
||||
$ rails generate metal poller
|
||||
</shell>
|
||||
|
||||
This generates +poller.rb+ in the +app/metal+ directory:
|
||||
|
||||
<ruby>
|
||||
# Allow the metal piece to run in isolation
|
||||
require(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails)
|
||||
|
||||
class Poller
|
||||
def self.call(env)
|
||||
if env["PATH_INFO"] =~ /^\/poller/
|
||||
[200, {"Content-Type" => "text/html"}, ["Hello, World!"]]
|
||||
else
|
||||
[404, {"Content-Type" => "text/html", "X-Cascade" => "pass"}, ["Not Found"]]
|
||||
end
|
||||
end
|
||||
end
|
||||
</ruby>
|
||||
|
||||
Metal applications within +app/metal+ folders in plugins will also be discovered and added to the list.
|
||||
|
||||
Metal applications are an optimization. You should make sure to "understand the related performance implications":http://weblog.rubyonrails.org/2008/12/20/performance-of-rails-metal before using it.
|
||||
|
||||
WARNING: To continue the Metal chain execution, return an +X-Cascade+ HTTP header with a value of +pass+.
|
||||
|
||||
h4. Execution Order
|
||||
|
||||
All Metal Applications are executed in alphabetical order of their filenames, so +aaa.rb+ will come before +bbb.rb+ in the metal chain.
|
||||
|
||||
You can override the default ordering in your environment. Simply add a line like the following to +config/application.rb+
|
||||
|
||||
It is, however, possible to override the default ordering in your environment. Simply add a line like the following to +config/environment.rb+
|
||||
|
||||
<ruby>
|
||||
config.metals = ["Bbb", "Aaa"]
|
||||
</ruby>
|
||||
|
||||
Each string in the array should be the name of your metal class. If you do this then be warned that any metal applications not listed will not be loaded.
|
||||
|
||||
h3. Resources
|
||||
|
||||
h4. Learning Rack
|
||||
|
|
|
@ -27,7 +27,7 @@ module Rails
|
|||
# Besides providing the same configuration as Rails::Engine and Rails::Railtie,
|
||||
# the application object has several specific configurations, for example
|
||||
# "allow_concurrency", "cache_classes", "consider_all_requests_local", "filter_parameters",
|
||||
# "logger", "metals", "reload_engines", "reload_plugins" and so forth.
|
||||
# "logger", "reload_engines", "reload_plugins" and so forth.
|
||||
#
|
||||
# Check Rails::Application::Configuration to see them all.
|
||||
#
|
||||
|
@ -36,17 +36,15 @@ module Rails
|
|||
# The application object is also responsible for holding the routes and reloading routes
|
||||
# whenever the files change in development.
|
||||
#
|
||||
# == Middlewares and metals
|
||||
# == Middlewares
|
||||
#
|
||||
# The Application is also responsible for building the middleware stack and setting up
|
||||
# both application and engines metals.
|
||||
# The Application is also responsible for building the middleware stack.
|
||||
#
|
||||
class Application < Engine
|
||||
autoload :Bootstrap, 'rails/application/bootstrap'
|
||||
autoload :Configurable, 'rails/application/configurable'
|
||||
autoload :Configuration, 'rails/application/configuration'
|
||||
autoload :Finisher, 'rails/application/finisher'
|
||||
autoload :MetalLoader, 'rails/application/metal_loader'
|
||||
autoload :Railties, 'rails/application/railties'
|
||||
autoload :RoutesReloader, 'rails/application/routes_reloader'
|
||||
|
||||
|
@ -83,7 +81,7 @@ module Rails
|
|||
end
|
||||
end
|
||||
|
||||
delegate :middleware, :metal_loader, :to => :config
|
||||
delegate :middleware, :to => :config
|
||||
|
||||
def require_environment!
|
||||
environment = paths.config.environment.to_a.first
|
||||
|
|
|
@ -9,7 +9,7 @@ module Rails
|
|||
|
||||
attr_accessor :allow_concurrency, :cache_classes, :cache_store,
|
||||
:encoding, :consider_all_requests_local, :dependency_loading,
|
||||
:filter_parameters, :log_level, :logger, :metals,
|
||||
:filter_parameters, :log_level, :logger,
|
||||
:plugins, :preload_frameworks, :reload_engines, :reload_plugins,
|
||||
:secret_token, :serve_static_assets, :session_options,
|
||||
:time_zone, :whiny_nils
|
||||
|
@ -45,10 +45,6 @@ module Rails
|
|||
@middleware ||= app_middleware.merge_into(default_middleware_stack)
|
||||
end
|
||||
|
||||
def metal_loader
|
||||
@metal_loader ||= Rails::Application::MetalLoader.new
|
||||
end
|
||||
|
||||
def paths
|
||||
@paths ||= begin
|
||||
paths = super
|
||||
|
@ -157,7 +153,6 @@ module Rails
|
|||
middleware.use('::ActionDispatch::ParamsParser')
|
||||
middleware.use('::Rack::MethodOverride')
|
||||
middleware.use('::ActionDispatch::Head')
|
||||
middleware.use(lambda { metal_loader.build_middleware(metals) }, :if => lambda { metal_loader.metals.any? })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
require 'action_dispatch'
|
||||
|
||||
module Rails
|
||||
class Application
|
||||
class MetalLoader
|
||||
attr_reader :paths, :metals
|
||||
|
||||
def initialize
|
||||
@paths, @metals = [], []
|
||||
end
|
||||
|
||||
def build_middleware(list=nil)
|
||||
load_metals!(list)
|
||||
self
|
||||
end
|
||||
|
||||
def new(app)
|
||||
ActionDispatch::Cascade.new(@metals, app)
|
||||
end
|
||||
|
||||
def name
|
||||
ActionDispatch::Cascade.name
|
||||
end
|
||||
alias :to_s :name
|
||||
|
||||
protected
|
||||
|
||||
def load_metals!(list)
|
||||
metals = []
|
||||
list = Array(list || :all).map(&:to_sym)
|
||||
|
||||
paths.each do |path|
|
||||
matcher = /\A#{Regexp.escape(path)}\/(.*)\.rb\Z/
|
||||
Dir.glob("#{path}/**/*.rb").sort.each do |metal_path|
|
||||
metal = metal_path.sub(matcher, '\1').to_sym
|
||||
next unless list.include?(metal) || list.include?(:all)
|
||||
require_dependency metal.to_s
|
||||
metals << metal
|
||||
end
|
||||
end
|
||||
|
||||
metals = metals.sort_by do |m|
|
||||
[list.index(m) || list.index(:all), m.to_s]
|
||||
end
|
||||
|
||||
@metals = metals.map { |m| m.to_s.camelize.constantize }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -25,7 +25,7 @@ module Rails
|
|||
# end
|
||||
#
|
||||
# Then ensure that this file is loaded at the top of your config/application.rb (or in
|
||||
# your Gemfile) and it will automatically load models, controllers, helpers and metals
|
||||
# your Gemfile) and it will automatically load models, controllers and helpers
|
||||
# inside app, load routes at "config/routes.rb", load locales at "config/locales/*",
|
||||
# load tasks at "lib/tasks/*".
|
||||
#
|
||||
|
@ -73,7 +73,6 @@ module Rails
|
|||
# paths.app.controllers = "app/controllers"
|
||||
# paths.app.helpers = "app/helpers"
|
||||
# paths.app.models = "app/models"
|
||||
# paths.app.metals = "app/metal"
|
||||
# paths.app.views = "app/views"
|
||||
# paths.lib = "lib"
|
||||
# paths.lib.tasks = "lib/tasks"
|
||||
|
@ -202,10 +201,6 @@ module Rails
|
|||
end
|
||||
end
|
||||
|
||||
initializer :add_metals do |app|
|
||||
app.metal_loader.paths.unshift(*paths.app.metals.to_a)
|
||||
end
|
||||
|
||||
initializer :load_config_initializers do
|
||||
paths.config.initializers.to_a.sort.each do |initializer|
|
||||
load(initializer)
|
||||
|
|
|
@ -19,7 +19,6 @@ module Rails
|
|||
paths.app.helpers "app/helpers", :eager_load => true
|
||||
paths.app.models "app/models", :eager_load => true
|
||||
paths.app.mailers "app/mailers", :eager_load => true
|
||||
paths.app.metals "app/metal", :eager_load => true
|
||||
paths.app.views "app/views"
|
||||
paths.lib "lib", :load_path => true
|
||||
paths.lib.tasks "lib/tasks", :glob => "**/*.rake"
|
||||
|
|
|
@ -288,7 +288,7 @@ module Rails
|
|||
end
|
||||
|
||||
# Removes the namespaces and get the generator name. For example,
|
||||
# Rails::Generators::MetalGenerator will return "metal" as generator name.
|
||||
# Rails::Generators::ModelGenerator will return "model" as generator name.
|
||||
#
|
||||
def self.generator_name
|
||||
@generator_name ||= begin
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
Description:
|
||||
Cast some metal!
|
||||
|
||||
Examples:
|
||||
`rails generate metal poller`
|
||||
|
||||
This will create:
|
||||
Metal: app/metal/poller.rb
|
|
@ -1,11 +0,0 @@
|
|||
module Rails
|
||||
module Generators
|
||||
class MetalGenerator < NamedBase
|
||||
check_class_collision
|
||||
|
||||
def create_metal_file
|
||||
template "metal.rb", "app/metal/#{file_name}.rb"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,12 +0,0 @@
|
|||
# Allow the metal piece to run in isolation
|
||||
require File.expand_path('../../../config/environment', __FILE__) unless defined?(Rails)
|
||||
|
||||
class <%= class_name %>
|
||||
def self.call(env)
|
||||
if env["PATH_INFO"] =~ /^\/<%= file_name %>/
|
||||
[200, {"Content-Type" => "text/html"}, ["Hello, World!"]]
|
||||
else
|
||||
[404, {"Content-Type" => "text/html", "X-Cascade" => "pass"}, ["Not Found"]]
|
||||
end
|
||||
end
|
||||
end
|
|
@ -37,7 +37,7 @@ namespace :rails do
|
|||
project_templates = "#{Rails.root}/lib/templates"
|
||||
|
||||
default_templates = { "erb" => %w{controller mailer scaffold},
|
||||
"rails" => %w{controller helper metal scaffold_controller stylesheets} }
|
||||
"rails" => %w{controller helper scaffold_controller stylesheets} }
|
||||
|
||||
default_templates.each do |type, names|
|
||||
local_template_type_dir = File.join(project_templates, type)
|
||||
|
|
|
@ -1,86 +0,0 @@
|
|||
require 'isolation/abstract_unit'
|
||||
|
||||
module ApplicationTests
|
||||
class MetalTest < Test::Unit::TestCase
|
||||
include ActiveSupport::Testing::Isolation
|
||||
|
||||
def setup
|
||||
build_app
|
||||
boot_rails
|
||||
|
||||
require 'rack/test'
|
||||
extend Rack::Test::Methods
|
||||
end
|
||||
|
||||
def app
|
||||
@app ||= begin
|
||||
require "#{app_path}/config/environment"
|
||||
Rails.application
|
||||
end
|
||||
end
|
||||
|
||||
test "single metal endpoint" do
|
||||
app_file 'app/metal/foo_metal.rb', <<-RUBY
|
||||
class FooMetal
|
||||
def self.call(env)
|
||||
[200, { "Content-Type" => "text/html"}, ["FooMetal"]]
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
|
||||
get "/not/slash"
|
||||
assert_equal 200, last_response.status
|
||||
assert_equal "FooMetal", last_response.body
|
||||
end
|
||||
|
||||
test "multiple metal endpoints" do
|
||||
app_file 'app/metal/metal_a.rb', <<-RUBY
|
||||
class MetalA
|
||||
def self.call(env)
|
||||
[404, { "Content-Type" => "text/html", "X-Cascade" => "pass" }, ["Metal A"]]
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
|
||||
app_file 'app/metal/metal_b.rb', <<-RUBY
|
||||
class MetalB
|
||||
def self.call(env)
|
||||
[200, { "Content-Type" => "text/html"}, ["Metal B"]]
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
|
||||
get "/not/slash"
|
||||
assert_equal 200, last_response.status
|
||||
assert_equal "Metal B", last_response.body
|
||||
end
|
||||
|
||||
test "pass through to application" do
|
||||
app_file 'app/metal/foo_metal.rb', <<-RUBY
|
||||
class FooMetal
|
||||
def self.call(env)
|
||||
[404, { "Content-Type" => "text/html", "X-Cascade" => "pass" }, ["Not Found"]]
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
|
||||
controller :foo, <<-RUBY
|
||||
class FooController < ActionController::Base
|
||||
def index
|
||||
render :text => "foo"
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
|
||||
app_file 'config/routes.rb', <<-RUBY
|
||||
AppTemplate::Application.routes.draw do |map|
|
||||
match ':controller(/:action)'
|
||||
end
|
||||
RUBY
|
||||
|
||||
get "/foo"
|
||||
assert_equal 200, last_response.status
|
||||
assert_equal "foo", last_response.body
|
||||
end
|
||||
end
|
||||
end
|
|
@ -82,12 +82,6 @@ module ApplicationTests
|
|||
assert_equal "Rack::Config", middleware.first
|
||||
end
|
||||
|
||||
test "shows cascade if any metal exists" do
|
||||
app_file "app/metal/foo.rb", "class Foo; end"
|
||||
boot!
|
||||
assert middleware.include?("ActionDispatch::Cascade")
|
||||
end
|
||||
|
||||
# x_sendfile_header middleware
|
||||
test "config.action_dispatch.x_sendfile_header defaults to ''" do
|
||||
make_basic_app
|
||||
|
|
|
@ -38,7 +38,6 @@ module ApplicationTests
|
|||
|
||||
test "booting up Rails yields a valid paths object" do
|
||||
assert_path @paths.app.models, "app", "models"
|
||||
assert_path @paths.app.metals, "app", "metal"
|
||||
assert_path @paths.app.helpers, "app", "helpers"
|
||||
assert_path @paths.app.views, "app", "views"
|
||||
assert_path @paths.lib, "lib"
|
||||
|
@ -73,7 +72,6 @@ module ApplicationTests
|
|||
assert_in_load_path "vendor"
|
||||
|
||||
assert_not_in_load_path "app", "views"
|
||||
assert_not_in_load_path "app", "metal"
|
||||
assert_not_in_load_path "config"
|
||||
assert_not_in_load_path "config", "locales"
|
||||
assert_not_in_load_path "config", "environments"
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
require 'generators/generators_test_helper'
|
||||
require 'rails/generators/rails/metal/metal_generator'
|
||||
|
||||
class MetalGeneratorTest < Rails::Generators::TestCase
|
||||
include GeneratorsTestHelper
|
||||
arguments %w(foo)
|
||||
|
||||
def test_metal_skeleton_is_created
|
||||
run_generator
|
||||
assert_file "app/metal/foo.rb", /class Foo/
|
||||
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
|
||||
end
|
|
@ -241,24 +241,6 @@ YAML
|
|||
assert_equal "1", I18n.t(:bar)
|
||||
end
|
||||
|
||||
def test_plugin_metals_added_to_middleware_stack
|
||||
@plugin.write 'app/metal/foo_metal.rb', <<-RUBY
|
||||
class FooMetal
|
||||
def self.call(env)
|
||||
[200, { "Content-Type" => "text/html"}, ["FooMetal"]]
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
|
||||
boot_rails
|
||||
require 'rack/test'
|
||||
extend Rack::Test::Methods
|
||||
|
||||
get "/not/slash"
|
||||
assert_equal 200, last_response.status
|
||||
assert_equal "FooMetal", last_response.body
|
||||
end
|
||||
|
||||
def test_namespaced_controllers_with_namespaced_routes
|
||||
@plugin.write "config/routes.rb", <<-RUBY
|
||||
Rails.application.routes.draw do
|
||||
|
|
Loading…
Reference in a new issue