Open ericgj opened 12 years ago
If you write some ruby code that uses requie 'sinatra' then you run
$ ruby some_code_that_requires_sinatra.rb
it should automatically start the server and run the application on port 4567 by default.
Yes - our aim is to explain how that works in the code above - can you explain it?
Not all of it. But I'll try:
So skipping the require and module/class declarations...
set :app_file, caller_files.first || $0
is setting the environment variable :app_file
to either the file that called Sinatra or the name of that file (the global variable $0
is the name of the file.
set :run, Proc.new { File.expand_path($0) == File.expand_path(app_file) }
is setting the :run environment variable to the location of the app and the surrounding directory, I think. There may be more going on here.
if run? && ARGV.any?
require 'optparse'
OptionParser.new { |op|
op.on('-p port', 'set the port (default is 4567)') { |val| set :port, Integer(val) }
op.on('-o addr', 'set the host (default is 0.0.0.0)') { |val| set :bind, val }
op.on('-e env', 'set the environment (default is development)') { |val| set :environment, val.to_sym }
op.on('-s server', 'specify rack server/handler (default is thin)') { |val| set :server, val }
op.on('-x', 'turn on the mutex lock (default is off)') { set :lock, true }
}.parse!(ARGV.dup)
end
end
This is the option parser for command line switches. The defaults must live elsewhere since I don't see them declared here. However I didn't know you could call "require" in the middle of a ruby file. I guess there's no reason you can't, but I hadn't seen it done this way before where it's declared directly before it's needed like this.
at_exit { Application.run! if $!.nil? && Application.run? }
end
# include would include the module in Object
# extend only extends the `main` object
extend Sinatra::Delegator
These last few lines I'm not positive about. at_exit
is apparently a kernel method according to the ruby docs that executes code (in reverse order multiples were declared!) at exit. In this case, it's running the application if there is no Global error messages (the $!
is the global variable for the latest error message)
at_exit { Application.run! if $!.nil? && Application.run? }
This line is an interesting part, it registers a hook to execute this block of code before exiting your app. So when the app was executed the interpreter goes through all of program source code and finish the program, but this block of code was executed before exiting the program. It checks $1
to see there is no exception was raise before starting the Application instance.
I can't find where does Application.run?
get defined? I try Sinatra::Application.method(:run?).source_location
, but it doesn't help.
@codereading/readers
Nice!
set :app_file, caller_files.first || $0
Didn't quite understand caller_files
at first but it seems to be a hack to get the first file in the call stack that requires sinatra. The gory details are in sinatra/base. So as the comment says, this file is needed to set the relative paths -- in particular the root
setting and settings based on root.
It seems like the difference from the Sinatra::Base
behavior here is the || $0
-- although I'm not sure why caller_files would ever be empty, since you're always going to require 'sinatra'
somewhere.
set :run, Proc.new { File.expand_path($0) == File.expand_path(app_file) }
This sets a condition which gets used in the at_exit
hook -- namely, that it serves up the app only if the top-level file you executed from the command line is the same as the app_file
.
So say you are testing your classic app, and you require the app file. When you ruby test_my_app.rb
, it's not going to serve it up, but when you ruby my_app.rb
, it will.
To answer Samnang's question, when you call set :x
, you get a number of accessors, including x?
. So in the at_exit
, Application.run?
evaluates the Proc.
Which brings up another nice feature, when you set something to a Proc, it gets lazily-evaluated. But it's not memoized -- the Proc gets evaluated each time you get the setting. Cf. sinatra/base
# include would include the module in Object
# extend only extends the `main` object
extend Sinatra::Delegator
A remaining question is what these lines above do? Sinatra::Delegator
-- what's that about?
Could caller_files
be empty if you literally copied and pasted all of the Sinatra code into one massive file? So your app, and all the framework to run it would be in one giant .rb, so there would be no require
and thus an empty caller_files
array? It's certainly unlikely, but those 6 extra characters,|| $0
, make it possible.
People do weird stuff like that sometimes; just look at the smallest rails app.
@samnang The Application
class inherits Sinatra::Base
. run!
method is defined in Sinatra::Base at this line
@thekungfuman @ericgj Yes I was wondering why
set :app_file, caller_files.first || $0
was guarding against a scenario of caller_files returning nil. I was just about to ask the group under what situation would caller_files return nil and I guess kungfumans scenario could be one - albeit a quite unusual one. Perhaps there is another case. Any ideas?
If an exception has been raised, then I assume your sinatra app would never run anyway. So why check for an exception here
at_exit { Application.run! if $!.nil? && Application.run? }
( for those unaware $! is a predefined global variable in ruby which holds the value of any exception raised inside of a rescue block )
Exceptions can be suppressed can't they? Perhaps the developer suppressed an exception, but it's still logged in the $!
so it would still get caught by this check.
@ericgj re:
Which brings up another nice feature, when you set something to a Proc, it gets lazily-evaluated. But it's not memoized -- the Proc gets evaluated each time you get the setting.
thats a cool feature. Im wondering what benefit it would have here in
set :run, Proc.new { File.expand_path($0) == File.expand_path(app_file) }
during runtime would $0 and app_file ever change? Why not just set run like so
set :run, (File.expand_path($0) == File.expand_path(app_file) )`
probably not very important to the overall code reading session but it is something that puzzled me.
I think the difference between $0
and app_file
comes in when using an external config.ru. Trying to understand the information here
@robodisco,
during runtime would $0 and app_file ever change? Why not just set run like so
set :run, (File.expand_path($0) == File.expand_path(app_file) )
Yes - I think because your classic app could explicitly set app_file
if it wanted to (although I can't think of a practical use for doing that, it's possible).
In general, (correct me if I'm wrong), it looks like the default settings that Sinatra makes that are based on other settings, are lazily-evaluated like this, so that the app could change the underlying settings.
@ericgj @thekungfuman @kgrz Thanks for your input on those. Makes things a bit easier to understand.
Now as @ericgj said, what's this delegator all about?
# Sinatra delegation mixin. Mixing this module into an object causes all
# methods to be delegated to the Sinatra::Application class. Used primarily
# at the top-level.
module Delegator #:nodoc:
def self.delegate(*methods)
methods.each do |method_name|
define_method(method_name) do |*args, &block|
return super(*args, &block) if respond_to? method_name
Delegator.target.send(method_name, *args, &block)
end
private method_name
end
end
delegate :get, :patch, :put, :post, :delete, :head, :options, :template, :layout,
:before, :after, :error, :not_found, :configure, :set, :mime_type,
:enable, :disable, :use, :development?, :test?, :production?,
:helpers, :settings
class << self
attr_accessor :target
end
self.target = Application
end
Yes, and why didn't they use the stdlib DelegateClass or some other tool in the delegate library?
Thought it might be helpful to have the code in front of us....
https://github.com/sinatra/sinatra/blob/master/lib/sinatra/main.rb