Permalink
Browse files

Make generated Metal bits a pure rack endpoint application (not middl…

…eware)

Instead of calling super to pass the request on, return a 404.
The modified app looks like this:

  # app/metal/poller.rb
  class Poller
    def self.call(env)
      if env["PATH_INFO"] =~ /^\/poller/
        [200, {"Content-Type" => "text/html"}, "Hello, World!"]
      else
        [404, {"Content-Type" => "text/html"}, "Not Found"]
      end
    end
  end

But you aren't locked in to just Rails:

  # app/metal/api.rb
  require 'sinatra'
  Sinatra::Application.default_options.merge!(:run => false, :env => :production)
  Api = Sinatra.application unless defined? Api

  get '/interesting/new/ideas' do
    'Hello Sinatra!'
  end
  • Loading branch information...
josh committed Dec 17, 2008
1 parent 97a178b commit 61a41154f7d50099da371e0d2f22fd25ab9113c2
@@ -536,9 +536,7 @@ def initialize_i18n
end
def initialize_metal
Dir["#{configuration.root_path}/app/metal/*.rb"].each do |file|
configuration.middleware.use(File.basename(file, '.rb').camelize)
end
configuration.middleware.use Rails::Rack::Metal
end
# Initializes framework-specific settings for each of the loaded frameworks
@@ -1,21 +1,17 @@
module Rails
module Rack
class Metal
def self.new(app)
apps = Dir["#{Rails.root}/app/metal/*.rb"].map do |file|
File.basename(file, '.rb').camelize.constantize
end
apps << app
::Rack::Cascade.new(apps)
end
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
@@ -1,12 +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)
class <%= class_name %>
def self.call(env)
if env["PATH_INFO"] =~ /^\/<%= file_name %>/
[200, {"Content-Type" => "text/html"}, "Hello, World!"]
[200, {"Content-Type" => "text/html"}, ["Hello, World!"]]
else
super
[404, {"Content-Type" => "text/html"}, ["Not Found"]]
end
end
end

23 comments on commit 61a4115

@trevorturk

This comment has been minimized.

Show comment
Hide comment
@trevorturk

trevorturk Dec 17, 2008

Contributor

Just curious why the generator has been changed to return an array instead of a string now…?

BTW – that you can use Sinatra this easily is 100% awesome. Thank you.

Contributor

trevorturk replied Dec 17, 2008

Just curious why the generator has been changed to return an array instead of a string now…?

BTW – that you can use Sinatra this easily is 100% awesome. Thank you.

@josh

This comment has been minimized.

Show comment
Hide comment
@josh

josh Dec 17, 2008

Member

@trevorturk Returning strings is being removed from the Rack spec since Ruby 1.9 doesn’t have String#each. Long story: http://groups.google.com/group/rack-devel/browse_thread/thread/e6132c3509438df

Member

josh replied Dec 17, 2008

@trevorturk Returning strings is being removed from the Rack spec since Ruby 1.9 doesn’t have String#each. Long story: http://groups.google.com/group/rack-devel/browse_thread/thread/e6132c3509438df

@bryanl

This comment has been minimized.

Show comment
Hide comment
@bryanl

bryanl Dec 17, 2008

Contributor

I like this much better. Great work Josh!

Contributor

bryanl replied Dec 17, 2008

I like this much better. Great work Josh!

@jnewland

This comment has been minimized.

Show comment
Hide comment
@jnewland

jnewland Dec 17, 2008

Contributor

@josh Awesome, it’s easier to understand the distinction between Metal and Middleware now. Updated my blog post to match.

Contributor

jnewland replied Dec 17, 2008

@josh Awesome, it’s easier to understand the distinction between Metal and Middleware now. Updated my blog post to match.

@tjogin

This comment has been minimized.

Show comment
Hide comment
@tjogin

tjogin Dec 17, 2008

The distinction between metal and middleware became clearer, but IMHO the useage turned towards the ugly side.

Here’s how I’d like it to look:

# app/metal/poller.rb
  class Poller
    def self.call(env)
      if env["PATH_INFO"] =~ /^\/poller/
        [200, {"Content-Type" => "text/html"}, "Hello, World!"]
      end
    end
  end

tjogin replied Dec 17, 2008

The distinction between metal and middleware became clearer, but IMHO the useage turned towards the ugly side.

Here’s how I’d like it to look:

# app/metal/poller.rb
  class Poller
    def self.call(env)
      if env["PATH_INFO"] =~ /^\/poller/
        [200, {"Content-Type" => "text/html"}, "Hello, World!"]
      end
    end
  end
@josh

This comment has been minimized.

Show comment
Hide comment
@josh

josh Dec 17, 2008

Member

@tjogin The initial Rails::Metal class hid this stuff from you. However the initial confusion made me decide to expose it. Nothing magic anymore, thats just a class.

Member

josh replied Dec 17, 2008

@tjogin The initial Rails::Metal class hid this stuff from you. However the initial confusion made me decide to expose it. Nothing magic anymore, thats just a class.

@heycarsten

This comment has been minimized.

Show comment
Hide comment
@heycarsten

heycarsten Dec 17, 2008

This is awesome.

heycarsten replied Dec 17, 2008

This is awesome.

@tjogin

This comment has been minimized.

Show comment
Hide comment
@tjogin

tjogin Dec 17, 2008

@josh: Personally, I liked it hidden. :P

tjogin replied Dec 17, 2008

@josh: Personally, I liked it hidden. :P

@matthewrudy

This comment has been minimized.

Show comment
Hide comment
@matthewrudy

matthewrudy Dec 17, 2008

Contributor

yeah.
I also liked the way it was.

I think that the default 404 is going to cause the most confusion,
It makes me think “I dont want to return a 404!”,
and I’d probably delete it.

Saying that, a magical “super” is probably equally as brittle.

Anyway,
its good stuff.

Contributor

matthewrudy replied Dec 17, 2008

yeah.
I also liked the way it was.

I think that the default 404 is going to cause the most confusion,
It makes me think “I dont want to return a 404!”,
and I’d probably delete it.

Saying that, a magical “super” is probably equally as brittle.

Anyway,
its good stuff.

@foca

This comment has been minimized.

Show comment
Hide comment
@foca

foca Dec 18, 2008

Contributor

What about defining a constant Metal::PassThru = [404, {"Content-Type" => "text/html"}, ["Not Found"]]

And then in the code you can just return Metal::PassThru and voila, you keep the logic plain and simple, and the “magic” 404 becomes a name which clearly states your intentions.

Contributor

foca replied Dec 18, 2008

What about defining a constant Metal::PassThru = [404, {"Content-Type" => "text/html"}, ["Not Found"]]

And then in the code you can just return Metal::PassThru and voila, you keep the logic plain and simple, and the “magic” 404 becomes a name which clearly states your intentions.

@foca

This comment has been minimized.

Show comment
Hide comment
@foca

foca Dec 18, 2008

Contributor

or maybe it’s clearer if PassThru is an exception and the code that adds each metal class on the call chain rescues PassThru and does a next on the iterator? (I didn’t look at the implementation, so maybe this won’t work)

But you get the idea.

Contributor

foca replied Dec 18, 2008

or maybe it’s clearer if PassThru is an exception and the code that adds each metal class on the call chain rescues PassThru and does a next on the iterator? (I didn’t look at the implementation, so maybe this won’t work)

But you get the idea.

@splattael

This comment has been minimized.

Show comment
Hide comment
@splattael

splattael Dec 18, 2008

Contributor

@foca
NotFound? http://github.com/rails/rails/tree/61a41154f7d50099da371e0d2f22fd25ab9113c2/railties/lib/rails/rack/metal.rb#L12

Contributor

splattael replied Dec 18, 2008

@foca
NotFound? http://github.com/rails/rails/tree/61a41154f7d50099da371e0d2f22fd25ab9113c2/railties/lib/rails/rack/metal.rb#L12

@bumi

This comment has been minimized.

Show comment
Hide comment
@bumi

bumi Dec 18, 2008

@foca +1

bumi replied Dec 18, 2008

@foca +1

@pkoch

This comment has been minimized.

Show comment
Hide comment
@pkoch

pkoch Dec 21, 2008

what if I really want to 404?

pkoch replied Dec 21, 2008

what if I really want to 404?

@skade

This comment has been minimized.

Show comment
Hide comment
@skade

skade Jan 18, 2009

One question: the Rack specification says that Rack-Applications are not allowed to be Classes. AFAIK, it doesn’t enforce this, but wouldn’t it be good to stick to the Spec?

I’m okay with using Cascade and 404.

skade replied Jan 18, 2009

One question: the Rack specification says that Rack-Applications are not allowed to be Classes. AFAIK, it doesn’t enforce this, but wouldn’t it be good to stick to the Spec?

I’m okay with using Cascade and 404.

@josh

This comment has been minimized.

Show comment
Hide comment
@josh

josh Jan 19, 2009

Member

@skade A valid rack application is anything that responds to “call”, there are no type requirements. Even if you wanted to restrict this to objects, in Ruby classes are objects ;)

Member

josh replied Jan 19, 2009

@skade A valid rack application is anything that responds to “call”, there are no type requirements. Even if you wanted to restrict this to objects, in Ruby classes are objects ;)

@pager

This comment has been minimized.

Show comment
Hide comment
@pager

pager Jan 19, 2009

Contributor

@josh http://rack.rubyforge.org/doc/files/SPEC.html
“A Rack application is an Ruby object (not a class) that responds to call”

Contributor

pager replied Jan 19, 2009

@josh http://rack.rubyforge.org/doc/files/SPEC.html
“A Rack application is an Ruby object (not a class) that responds to call”

@skade

This comment has been minimized.

Show comment
Hide comment
@skade

skade Jan 19, 2009

@josh: yes, ruby classes are objects. Thanks for the lesson… But they are special objects with subtle differences. There is a distinction, although classes behave like objects in almost every case. For example: classes are (usually) globally unique, objects (usually) not.
And that might just be the reason why the spec explicitly forbids classes.

skade replied Jan 19, 2009

@josh: yes, ruby classes are objects. Thanks for the lesson… But they are special objects with subtle differences. There is a distinction, although classes behave like objects in almost every case. For example: classes are (usually) globally unique, objects (usually) not.
And that might just be the reason why the spec explicitly forbids classes.

@kairichard

This comment has been minimized.

Show comment
Hide comment
@kairichard

kairichard Jan 19, 2009

woot, this is gonna be huge +1

kairichard replied Jan 19, 2009

woot, this is gonna be huge +1

@cypher

This comment has been minimized.

Show comment
Hide comment
@cypher

cypher Jan 20, 2009

@skade, pager: “object, not a class” means that Rack won’t try to instantiate anything for you. As Josh correctly points out, all it wants is an object that has a “call” method, and classes are objects too.

cypher replied Jan 20, 2009

@skade, pager: “object, not a class” means that Rack won’t try to instantiate anything for you. As Josh correctly points out, all it wants is an object that has a “call” method, and classes are objects too.

@skade

This comment has been minimized.

Show comment
Hide comment
@skade

skade Jan 20, 2009

@cypher, josh: this may be up to interpretation, but I don’t really want to push this.

On the other hand: if you skip Metals implementing #call on a class level and just instantiate them, you even save the need for self. The default construction for Objects is an empty one, so this is easy. I find it a much better interface, especially because of the symmetry to controller actions.

skade replied Jan 20, 2009

@cypher, josh: this may be up to interpretation, but I don’t really want to push this.

On the other hand: if you skip Metals implementing #call on a class level and just instantiate them, you even save the need for self. The default construction for Objects is an empty one, so this is easy. I find it a much better interface, especially because of the symmetry to controller actions.

@foca

This comment has been minimized.

Show comment
Hide comment
@foca

foca Jan 23, 2009

Contributor

@skade you can always just say

class MyMetal
  def self.call(env)
    new.call(env)
  end

  def call(env)
    # do your stuff here
  end
end
Contributor

foca replied Jan 23, 2009

@skade you can always just say

class MyMetal
  def self.call(env)
    new.call(env)
  end

  def call(env)
    # do your stuff here
  end
end
@jduff

This comment has been minimized.

Show comment
Hide comment
@jduff

jduff Jun 18, 2009

Contributor

Does anyone know of an actual workaround or patch so that I can return a 404 from my metal? The docs(http://guides.rubyonrails.org/rails_on_rack.html) say to use some other Rack middleware but I'm not really sure how I'm supposed to differentiate between the 404 my metal returns to say pass on to rails and the 404 I actually want to return to the user.

Contributor

jduff replied Jun 18, 2009

Does anyone know of an actual workaround or patch so that I can return a 404 from my metal? The docs(http://guides.rubyonrails.org/rails_on_rack.html) say to use some other Rack middleware but I'm not really sure how I'm supposed to differentiate between the 404 my metal returns to say pass on to rails and the 404 I actually want to return to the user.

Please sign in to comment.