Skip to content

Commit

Permalink
Introduce Rails Metal
Browse files Browse the repository at this point in the history
  # app/metal/poller.rb
  class Poller < Rails::Rack::Metal
    def call(env)
      if env["PATH_INFO"] =~ /^\/poller/
        [200, {"Content-Type" => "application/json"}, Message.recent.to_json]
      else
        super
      end
    end
  end

* There is a generator to help you get started
    `script/generate metal poller`

* Also, metal bits can be ran standalone with rackup
    `rackup app/metal/poller.rb`
  • Loading branch information
josh committed Dec 16, 2008
1 parent c4023cb commit 8c3a543
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 0 deletions.
10 changes: 10 additions & 0 deletions railties/lib/initializer.rb
Expand Up @@ -155,6 +155,8 @@ def process
initialize_framework_settings
initialize_framework_views

initialize_metal

add_support_load_paths

load_gems
Expand Down Expand Up @@ -533,6 +535,12 @@ def initialize_i18n
end
end

def initialize_metal
Dir["#{configuration.root_path}/app/metal/*.rb"].each do |file|
configuration.middleware.use(File.basename(file, '.rb').camelize)
end
end

# Initializes framework-specific settings for each of the loaded frameworks
# (Configuration#frameworks). The available settings map to the accessors
# on each of the corresponding Base classes.
Expand Down Expand Up @@ -915,6 +923,7 @@ def default_load_paths
# Followed by the standard includes.
paths.concat %w(
app
app/metal
app/models
app/controllers
app/helpers
Expand All @@ -933,6 +942,7 @@ def default_load_once_paths

def default_eager_load_paths
%w(
app/metal
app/models
app/controllers
app/helpers
Expand Down
1 change: 1 addition & 0 deletions railties/lib/rails/rack.rb
Expand Up @@ -2,6 +2,7 @@ module Rails
module Rack
autoload :Debugger, "rails/rack/debugger"
autoload :Logger, "rails/rack/logger"
autoload :Metal, "rails/rack/metal"
autoload :Static, "rails/rack/static"
end
end
21 changes: 21 additions & 0 deletions railties/lib/rails/rack/metal.rb
@@ -0,0 +1,21 @@
module Rails
module Rack
class Metal
NotFound = lambda { |env|
[404, {"Content-Type" => "text/html"}, "Not Found"]
}

def self.call(env)
new(NotFound).call(env)
end

def initialize(app)
@app = app
end

def call(env)
@app.call(env)
end
end
end
end
@@ -0,0 +1,8 @@
Description:
Cast some metal!

Examples:
`./script/generate metal poller`

This will create:
Metal: app/metal/poller.rb
@@ -0,0 +1,8 @@
class MetalGenerator < Rails::Generator::NamedBase
def manifest
record do |m|
m.directory 'app/metal'
m.template 'metal.rb', File.join('app/metal', "#{file_name}.rb")
end
end
end
@@ -0,0 +1,12 @@
# Allow the metal piece to run in isolation
require(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails)

class <%= class_name %> < Rails::Rack::Metal
def call(env)
if env["PATH_INFO"] =~ /^\/<%= file_name %>/
[200, {"Content-Type" => "text/html"}, "Hello, World!"]
else
super
end
end
end

63 comments on commit 8c3a543

@anildigital
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kewl.. can somebody explain me, what is the metal about?

@topfunky
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great idea!

While the paint is still wet, how about calling it something that matches the descriptive names of “controllers” “models” and “views”?

I suggest “middleware”.

@drnic
Copy link
Contributor

@drnic drnic commented on 8c3a543 Dec 16, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No tests for this patch? :)

@mtodd
Copy link
Contributor

@mtodd mtodd commented on 8c3a543 Dec 16, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to be a recreation of the already simple idea of middleware… but I like it.

Also agree with calling it Middleware, though “Metal” is kinda badass. :)

@judofyr
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can’t kill the metal.
The metal will live on.

@dhh
Copy link
Member

@dhh dhh commented on 8c3a543 Dec 16, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Middleware is a more generic notion for anything that’s used for preprocessing of a request. Metal is intended to be end-points for things that are simple and needs to be really fast. So think of Metal < Middleware.

@amerine
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metal sounds much better then “middleware”. But as far as having a “built-in” place to run “middeware code”…. I’m down with that.

@norbert
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, really? Sigh.

@nakajima
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this a lot. However, I am with drnic. I’d also like to see some “metals” test helpers added to the framework, since this doesn’t seem to fall under the category of model, functional or integration (well, maybe integration).

@josh
Copy link
Contributor Author

@josh josh commented on 8c3a543 Dec 16, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@norbert just curious what you don’t dig?

@nakajima integration tests will work great since they test the entire call stack.

@tristandunn
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be great for a project of mine if you can still use the session with it. Can you and if so is anything extra needed?

@tjogin
Copy link

@tjogin tjogin commented on 8c3a543 Dec 16, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What “regular” parts of Rails are bypassed in order to make this so much faster?

@wycats
Copy link
Member

@wycats wycats commented on 8c3a543 Dec 16, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty nice! It’s great to see Rails embracing Rack and Rack middleware.

@drnic
Copy link
Contributor

@drnic drnic commented on 8c3a543 Dec 16, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nakajima yeah I think I’d like to see a test/metal/my_metal_test.rb + some helpers. very sweet.

@josh glad it can still work via integration/cucumber etc

this is the merb-pastie integration that I always wanted; so nice.

@tjogin
Copy link

@tjogin tjogin commented on 8c3a543 Dec 16, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What “regular” parts of Rails are bypassed in order to make this so much faster?

@samgranieri
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tjogin, josh replaced the old cgi engine with rack. Rails is just one big rack application now. I think this is pretty cool for intercepting requests that you dont want to be processed by actioncontroller. I’m going to use this for heartbeat monitoring with HAProxy. One caveat while developing: unlike regular controllers you need to restart mongrel/thin when you change any metal.

I think this should be renamed to spikes.

@tjogin
Copy link

@tjogin tjogin commented on 8c3a543 Dec 16, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@samgranieri: So the reason “metal” is so much faster is simply because it doesn’t load actioncontroller? That’s it?

@drnic
Copy link
Contributor

@drnic drnic commented on 8c3a543 Dec 16, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 for “middleware”

@drnic
Copy link
Contributor

@drnic drnic commented on 8c3a543 Dec 16, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tjogin it would bypass the normal routing system too

@tjogin
Copy link

@tjogin tjogin commented on 8c3a543 Dec 16, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@drnic: Anything else?

@defunkt
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blacker than the blackest black, times infinity.

@drnic
Copy link
Contributor

@drnic drnic commented on 8c3a543 Dec 16, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tjogin – http://soylentfoo.jnewland.com/articles/2008/12/16/rails-metal-a-micro-framework-with-the-power-of-rails-m has a speed summary, btw

@samgranieri
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tjogin, that’s not entirely accurate.

When the rails app starts up, it ( loads | autoloads | ? ) the framework , including actioncontroller.

The metal is so much faster because it intercepts the Rack call. In the example, if the route containts poller, send a json message, if not, then pass the request to action controller via super

@josh
Copy link
Contributor Author

@josh josh commented on 8c3a543 Dec 16, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wycats Glad you like it. I hope this means we can start sharing Rack compatible code between Rails and Merb.

@tjogin it bypasses everything. whatever you code in your metal bit talks directly to the server (mongrel/thin/etc).

Still a bit of confusion about metal == middleware? The answer is no. Metal bits are designed to be endpoints.

I think this commit did not get noticed: http://github.com/rails/rails/commit/06ed8e451198b2296d8b2752741e259b4f995081
I added `config.middleware.use Rack::Cache` to the initializer as a hook for any plugins that want to connect in as middleware.

Yes, I know its really all the same thing but metal does sound really cool :)

@drnic I think integration tests are a good fit here. Thats my opinion though. If you think some helpers are inorder, go for it. (Actually, if I had my way, everything would be an integration test ;)

@tjogin
Copy link

@tjogin tjogin commented on 8c3a543 Dec 16, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@josh: So, since metal bypasses “everything”, does that mean that “metal” code can’t use some of the stuff the rest of a Rails app uses? Like.. ActiveSupport, or anything like that? Maybe I’m curious for too much information too soon. Blogs are likely delve deeper into this.

@samgranieri
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tjogin, yeah, ActiveSupport works. http://gist.github.com/36861

@drnic
Copy link
Contributor

@drnic drnic commented on 8c3a543 Dec 16, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From Ezra’s (sarcastic) tweet (http://twitter.com/ezmobius/statuses/1061662672) “rack middleware you cannot use outside of rails” – what would be the mechanism for a writing a plugin that contained a rack call hook, that could be used in different frameworks (rails/merb/other-rack-super-framework)? Given the rails-specific superclass, then the call behaviour would need to be in a module, and included into either a Rails::Rack::Metal subclass via the rails plugin’s init.rb or hooked into each specific Merb/other-rack-superframework in a specific way? Or can this be generic so its uber simple to write sharable rack plugins that are framework neutral?

@josh
Copy link
Contributor Author

@josh josh commented on 8c3a543 Dec 16, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@drnic

  1. init.rb

config.middleware.use Rack::MyFanaticMiddleware

This is the same api as the rack builder syntax. Its totally compatible. Use any rack middleware with Rails now (on edge ;)

again metal != middleware

@drnic
Copy link
Contributor

@drnic drnic commented on 8c3a543 Dec 16, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/me found answer to his question in @josh’s comment above

@wycats
Copy link
Member

@wycats wycats commented on 8c3a543 Dec 16, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

drnic I’m assuming that Rails::Rack::Metal will become more featured over time. At the moment there doesn’t seem to be a big win in using it over regular middleware (but again, I expect that might change).

@samgranieri
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here’s a novel use for metal: You can use http://gist.github.com/36865 to rickroll every non page cached url. Use at your own risk!

@wycats
Copy link
Member

@wycats wycats commented on 8c3a543 Dec 16, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lifo don’t you use config.middleware.use to set up the Metal classes?

@carllerche
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 for naming it middleware.

In the Rack code itself, Rack::File is obviously an endpoint, yet is called middleware. It’s called middleware because it is in the middle of the request → application chain, not because it is an end point or not.

Naming this metal instead of middleware is just going to muddy the waters for newer developers not familiar with rack.

@josh
Copy link
Contributor Author

@josh josh commented on 8c3a543 Dec 16, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wycats yes, under the hood the metal is middleware. we inject it into the middleware stack and thats how it intercepts the request. The focus is on the endpoint implementation. Metal bits should be application endpoints, not filters.

@ezmobius
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

middlewares do not have to be filters. they can absolutely be endpoints and that is the point. Making a new concept just makes learning rack harder no?

Same concept i blogged ages ago here: brainspl.at/articles/2008/02/16/so-merb-core-is-built-on-rack-you-say-why-should-i-care

I love that you guys are adding this stuff. But I implore you to not embrace and extend but rather just embrace rack and make people learn it. This is just middleware in sheep’s clothing afaict.

@josh
Copy link
Contributor Author

@josh josh commented on 8c3a543 Dec 17, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ezmobius The original idea was to have a single endpoint class, and force people use Rack::URLMap. But we didn’t like the idea of forcing people to use that for routing. So we moved the routing logic into the class (which is causing all this confusing). It may be clearer if it looked something like this.

class Poller
def self.call(env)
[200, ….]
end
end

class PollerRouterMiddleware
def initialize(app)
@app = app
end

def call(env) if env[…] == /something/ Poller.call(env) else @app.call(env) end end

end

However, we decided to merge the two for a nicer API. Okay, since Rack::Metal secretly takes an app instance under the hood, is not an endpoint but middleware. But thats not how we want to think of it.

I know still confusing.

@Roman2K
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 for “middleware”, or some other name that’s more descriptive than “metal”.

@wycats
Copy link
Member

@wycats wycats commented on 8c3a543 Dec 17, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lifo is there an important reason for people to think of it differently? might keeping the (tiny) implementation exposed help people understand what’s going on?

@josh
Copy link
Contributor Author

@josh josh commented on 8c3a543 Dec 17, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wycats possibly, maybe that would clear up this mess :)

(btw, I’m not lifo)

@wycats
Copy link
Member

@wycats wycats commented on 8c3a543 Dec 17, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@josh oh snap. /me holds head

My typing fingers have betrayed me!

@josh
Copy link
Contributor Author

@josh josh commented on 8c3a543 Dec 17, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, all good comments. If someone wants to propose a change, maybe we should take this to the ML or something. I’m getting lost :)

@macournoyer
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 for naming it Awesomeware

@peterc
Copy link
Contributor

@peterc peterc commented on 8c3a543 Dec 17, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think metal is an awesome name and reasonably descriptive. That said, middleware is probably more descriptive but doesn’t it clash nominatively with the generic concept of middleware in Rack?

@wycats
Copy link
Member

@wycats wycats commented on 8c3a543 Dec 17, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@peterc is is middleware in Rack

@actsasflinn
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 for middleware

@lgomez
Copy link

@lgomez lgomez commented on 8c3a543 Dec 17, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pardon my ignorance but this means responses to requests intercepted by metal have to be completely generated manually or at least not with actionpack niceties, correct? Or is it just actioncontroller the one that is not loaded?

Where can I learn more about rack, metal and related subjects?

@mattetti
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Igomez http://rack.rubyforge.org/ and http://book.merbist.com/getting-started/request-path

Rails Edge now uses Rack like merb/sinatra/waves/ramaze/mack so the process is the same.
The Rack middleware receives a rack env and returns a simple tuple containing status, headers, body.
The middleware has access to your models.

@lifo
Copy link
Member

@lifo lifo commented on 8c3a543 Dec 17, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lgomez : Have a look at two posts I had made recently
- http://m.onkey.org/2008/11/17/ruby-on-rack-1
- http://m.onkey.org/2008/11/18/ruby-on-rack-2-rack-builder

Think of Metal as a minimal Rack application.

HTH.

@mattetti
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lifo great posts!

@josh
Copy link
Contributor Author

@josh josh commented on 8c3a543 Dec 17, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this will help. Metals are “application-specific” mini apps. Not really designed to be shared. However, you want want to create a plugin, engine, etc you can wrap it in a boarder middleware wrapper that would work on any Rack platform.

@softprops
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

neat!

@datra
Copy link

@datra datra commented on 8c3a543 Dec 17, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think looks pretty awesome. Excited about the future! I must admit that it took me a while to get the idea, though.

Metal is a cool name but to me it makes no sence. I don’t really like middleware either. I would prefer a plural noun like models, views, helpers, and controllers. I don’t know, maybe spikes. :)

@technoweenie
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Horns \m/

@henrik
Copy link
Contributor

@henrik henrik commented on 8c3a543 Dec 17, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

app/wares? ;)

@josh
Copy link
Contributor Author

@josh josh commented on 8c3a543 Dec 17, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tweaked the API:

http://github.com/rails/rails/commit/61a41154f7d50099da371e0d2f22fd25ab9113c2

Metals are now pure endpoints. Not fake middleware, nor any other type of middleware. They are applications, they do not accept a app in their initializer. Did I mention they were not middleware?

@stefanoc
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+∞ for metal, but I like “awesomeware” too :-)
I find it really hard to believe that ppl don’t get the difference between “metal” and “middleware”. I don’t think it’s confusing at all. I mean, just look at the code, it’s all there!

@josh: just keep pulling great stuff like this

@lifo
Copy link
Member

@lifo lifo commented on 8c3a543 Dec 17, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mattetti Thanks!

@creationmachine
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will really help me out in some stuff I am working on. Thanks!

As far as naming it “Metal”, um, there are 1 billion gems with different names. I think people new to Rails will understand the subject matter after it’s mentioned once or twice. But, maybe it should be named “Derailer” or “Sidetrack”. :D

Thanks again for all your work!

@balachandranl
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi,

Is there any possibilities to do rack and metal in rails 2.2 version ?

Please any one help me ASAP.

Regards,
Bala

@jnicklas
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bala,

you probably chose the single worst place of all to ask that question. May I suggest the rails-talk mailing list or Stack Overflow as alternatives.

/Jonas

Please sign in to comment.