1
0
Fork 0
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:
wycats 2010-05-29 20:07:47 +02:00
parent 564ab4776c
commit 45e60283e7
20 changed files with 9 additions and 319 deletions

View file

@ -43,7 +43,6 @@ module ActionDispatch
autoload_under 'middleware' do
autoload :Callbacks
autoload :Cascade
autoload :Cookies
autoload :Flash
autoload :Head

View file

@ -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

View file

@ -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 youd like to load for your application. By default it is the +Rails::Plugin::FileSystemLocator+.

View file

@ -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"

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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"

View file

@ -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

View file

@ -1,8 +0,0 @@
Description:
Cast some metal!
Examples:
`rails generate metal poller`
This will create:
Metal: app/metal/poller.rb

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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

View file

@ -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