I have an util method into a Sinatra application and I would like to tested from my TestCase.
The problem is that I don't know how to invoke it, if I just use app.util_method I have the error NameError: undefined local variable or method 'util_method' for #<Sinatra::ExtendedRack:0x007fc0c43305b8>
my_app.rb:
class MyApp < Sinatra::Base
# [...] routes methods
# utils methods
def util_method
return "hi"
end
end
my_app_test.rb:
require "my_app.rb"
require "test/unit"
require "rack/test"
class MyAppTest < Test::Unit::TestCase
include Rack::Test::Methods
def app
MyApp.new
end
# [...] routes methods tests
def test_util_method
assert_equal( "hi", app.util_method )
end
end
Sinatra aliases the new method to new! before redefining it, so the simplest solution is to use that instead:
def app
MyApp.new!
end
Of course I only noticed that after I’d come up with the following, which I’ll leave in as it could be useful/informative.
A possible way to get round Sinatra redefining the new method and returning a complete Rack app a get hold of an instance your actual base class is to do what the “real” new method does yourself:
def app
a = MyApp.allocate
a.send :initialize
a
end
This is a bit of a hack, but it might be useful for testing.
Another technique would be to “walk” the middleware stack until you got to your class. The following is a little fragile, as it depends on all the middleware involved to use the name #app to refer to the next app in the stack, but this is fairly common.
def app
a = MyApp.new
while a.class != MyApp
a = a.instance_variable_get(:#app)
end
a
end
That won’t work on the yet to be released Sinatra 1.4 though (at least not on the current master, which is commit 41840746e866e8e8e9a0eaafc53d8b9fe6615b12), as new now returns a Wrapper class and the loop never ends. In this case you can grab the base class directly from the #instance variable:
def app
MyApp.new.instance_variable_get :#instance
end
(note this last technique may well change before the final 1.4 release).
The problem you are encountering is, that MyApp.new does not return an instance of MyApp but an instance of the middleware wrapping your App (usually Rack::Head or Sinatra::ShowExceptions). A good explanation can be found in the thread Sinatra Usage Question / Rack App.
The only solution I can think of is to change your instance method to a class method which can be called without the instance itself. As the instance of your App may be freshly instantiated for every request, an instance method probably doesn't have much advantages over a class method in your scenario.
Edit:
In the upcoming Sinatra 1.4 the initialization will change. Sinatra::Base.new will return a Sinatra::Wrapper instance, which exposes #settings and #helpers. This may help solve the problem of accessing Sinatra::Base instance methods. See the Sinatra Changelog for more information.
Related
I'm splitting a large Sinatra file into different files using registers. This is one way to have a modular Sinatra app using extensions.
I'm ending up with something like the following code:
MyApp < Sinatra::Base
register OneRegister
register SecondRegister
end
module OneRegister
def self.registered(app)
app.helpers OneRegisterHelper
app.get "/one-endpoint" do
do_stuff
end
end
module OneRegisterHelper
def do_stuff
# Some code
end
end
end
module SecondRegister
def self.registered(app)
app.helpers SecondRegisterHelper
app.get "/second-endpoint" do
do_stuff
end
end
module SecondRegisterHelper
def do_stuff
# Different code
end
end
end
The problem is how Sinatra works with registers and helpers. Every time I create a new helper for a register I'm polluting the main Sinatra app scope with the methods in the helpers.
So, the method do_stuff is going to be overwritten by the SecondRegisterHelper (this is how Ruby works when including a module) but I'd like to have different implementations for the methods without worry if I'm using the same method name or a different one (image an app with 25 registers with small methods in each one).
Basically, I'd like to have different registers with private methods because I usually write very small private methods with a single responsibility. Any ideas, how I can achieve this?
I don't think this is achievable in the way you are trying. If you have methods with similar names in different modules mixed into a single class the last just wins.
So in this case I would create a modular app combined with a config.ru to setup your application.
class OneRegister < Sinatra::Base
# helpers here
end
class SecondRegister < Sinatra::Base
# helpers here
end
In config.ru
app = Rack::URLMap.new(
'/one-endpoint' => OneRegister,
'/second-endpoint' => TwoRegister
)
run app
No you helpers are scoped to a single class.
I'm stumped I've tried everything I can think of to work around this and I got nothing.
When does Sinatra's request object start existing and where does it actually exist and how can I get to it from anywhere? i.e. another class that inherits from a class that inherits from a class that inherits from Sinatra::Base for example.
I've managed to get an idea of where it exists from this question but I just can't seem get any further.
Things I've tried:
def self.request
self.superclass.superclass.superclass.request
end
Various ways of changing the code execution context using `instance_eval or using:
def self.method_added method_name
a = self.new.method(method_name)
def self.define_method method_name do
a.call
end
end
and anything else I can think of but no matter what I do request is always nil, so I ask again, when and where does the request object come into existence during a request?
EDIT:
No offence but how is it hard to tell what I'm asking?
Here's the question, it's the in the title:
When and where does Sinatra's request object exist?
Sinatra has a request object, when does the object start to exist? (as in when is it not nil during the execution of code?)
When it does exist, where does it exist, within Sinatra::Base an instance of Sinatra::Base or within `Wrapper or some where else?
EDIT:
Here's what I'm doing:
in this example:
r[:action] is 'get'
r[:url] is '/'
method is get_root
instance is a variable storing self.new so I can access any instance methods.
def method_added method
return if Delegation::SIN_DSL[:basic].include?(method)
r,p = get_r_data method
m = self.instance.method(method)
self.send r[:action], r[:url].first, (#options || {}) do
(m.arity < 1 ) ? m.call : m.call(*p)
end
#options = nil
end
A sinatra app is a rack app. If you have
class MyApp < Sinatra::Base
end
then where the MyApp class gets instantiated depends on how you start up a webserver that runs it; generally speaking the rack handler (which may be of different types depending on the http server you're using) will store an instance of the sinatra app. When a rack request comes in, the server or another rack app will call the app instance with the rack env hash. Sinatra::Base#call will then do dup.call!(env), meaning that a shallow copy of the existing instance is made and then call! is invoked on the copy. The body of call! is where the request object is initialized:
def call!(env) # :nodoc:
#env = env
#request = Request.new(env)
and it's this duped app's request accessor for this instance variable that you're typically invoking when you refer to request in a route handler.
Not sure if that helps you, but it should at least answer the question.
WARNING: Answer valid for sinatra v1.4.5, but you should not expect it to remain valid. These implementation details are not part of the public sinatra API and are not documented for a reason -- you're not intended to mess with it, and doing so will quite possibly break your app if you upgrade sinatra versions. I don't recommend writing code that depends on these details.
I am using sinatra and I have the code:
class App < Sinatra::Base
configure do
#logger = Logger.new "./log"
end
#logger.info "App started" #this line works
get "/info" do
#logger.info "/info inquired" #this does not work and complain #logger is nilClass
end
end
Why #logger inside get block gives a nil object? How can I use #logger in this case?
PS. If I use a class variable like ##logger, the code above works. But why the instance variable is not working in this case?
Instance variables attach themselves to whatever object is self at the time the instance variables spring into existence.
On the face of things, these are the values for self:
class App < Sinatra::Base
#In here, self=App
#When a block executes, it sees the value for self that existed in
#the surrounding scope at the time the block was CREATED:
configure do #a block
#So...in here self=App
#logger = Logger.new "./log"
end
#logger.info "App started" #this line works
get "/info" do #a block
#Similarly, in here self=App
#logger.info "/info inquired" ##logger is NilClass
end
end
Based on that state of things, you are right to be confused: it looks like when configure() executes the block that is passed to it, #logger will spring into existence and attach itself to App, then when get() calls the block that is passed to it, the #logger instance variable will refer to the instance variable attached to App.
But...ruby offers ways to change the value of self that a block sees when the block EXECUTES. Here is an example:
p = Proc.new { puts self }
p.call
class Dog
def initialize(a_proc)
#In here, self is a Dog instance
instance_eval &a_proc
end
end
Dog.new p
--output:--
main
#<Dog:0x000001009b6080>
Based on your error, you have to suspect that Sinatra must be employing some ruby tricks to change self when it executes the block passed to get().
How can we know this?
Ruby is the wild west of programming languages, so you can't ever know what is going to happen unless you look at the source code or good docs if they exist. The source code is pretty convoluted. I found this in the docs:
Some knowledge of Sinatra’s internal design is required to write good
extensions. This section provides a high level overview of the classes
and idioms at the core of Sinatra’s design.
Sinatra has two distinct modes of use that extensions should be aware
of:
The “Classic” style, where applications are defined on main / the
top-level – most of the examples and documentation target this usage.
Classic applications are often single-file, standalone apps that are
run directly from the command line or with a minimal rackup file. When
an extension is required in a classic application, the expectation is
that all extension functionality should be present without additional
setup on the application developers part (like included/extending
modules).
The “Modular” style, where Sinatra::Base is subclassed explicitly and
the application is defined within the subclass’s scope. These
applications are often bundled as libraries and used as components
within a larger Rack-based system. Modular applications must include
any desired extensions explicitly by calling register ExtensionModule
within the application’s class scope.
Most extensions are relevant to both styles but care must be taken by
extension authors to ensure that extensions do the right thing under
each style. The extension API (Sinatra.register and Sinatra.helpers)
is provided to help extension authors with this task.
Important: The following notes on Sinatra::Base and
Sinatra::Application are provided for background only - extension
authors should not need to modify these classes directly.
Sinatra::Base The Sinatra::Base class provides the context for all
evaluation in a Sinatra application. The top-level DSLish stuff exists
in class scope while request-level stuff exists at instance scope.
Applications are defined within the class scope of a Sinatra::Base
subclass. The “DSL” (e.g., get, post, before, configure, set, etc.) is
simply a set of class methods defined on Sinatra::Base. Extending the
DSL is achieved by adding class methods to Sinatra::Base or one of its
subclasses. However, Base classes should not be extended with extend;
the Sinatra.register method (described below) is provided for this
task.
Requests are evaluated within a new Sinatra::Base instance – routes,
before filters, views, helpers, and error pages all share this same
context. The default set of request-level helper methods (e.g, erb,
haml, halt, content_type, etc.) are simple instance methods defined on
Sinatra::Base or within modules that are included in Sinatra::Base.
Providing new functionality at the request level is achieved by adding
instance methods to Sinatra::Base.
As with DSL extensions, helper modules should not be added directly to
Sinatra::Base by extension authors with include; the Sinatra.helpers
method (described below) is provided for this task.
http://www.sinatrarb.com/extensions.html
You can define your own logger in your Sinatra::Base and use it in your get block by doing:
class App < Sinatra::Base
set :logger, Logger.new("./log")
helpers do
def logger; self.class.logger; end
end
logger.info self
get "/info" do
logger.info self
end
# ...
end
Or by using the class variable as you note in your edit. The log file from the above configuration shows why:
I, [2014-06-01T16:36:51.593033 #16144] INFO -- : App
I, [2014-06-01T16:36:59.438078 #16144] INFO -- : #<App:0x9aa919c #default_layout=:layout, #app=nil ...
In the first case, self is the application class, while in the get block, self is the instance of the class.
To clarify, in your example: Ruby interprets the first #logger.info (called from the context of your class) to be a class instance variable, while the second #logger.info is interpreted as an instance variable (which has not been defined). The variable you define in your configure block is set in the class context.
I'm trying to build a Server class for a gem I'm building. This could define call (env) which would make it a rack app. However, I want to use Rack::Builder and map different kinds of URLs, according to my needs.
I'm not sure how to explain this but is there a way to inherit from Rack::Builder or something? I want to isolate the URL mappings into its own methods so I can test them in one class, so as to give meaning and isolation to it.
Thank you.
You do not need to inherit from Rack::Builder. You can do like that.
config.ru
class WrapperClass
def call(env)
#your_url_mapping_logic env
YourApp.new.call(env)
end
end
run WrapperClass.new
or
class WrapperClass
def call(env)
#your_url_mapping_logic env
#your app logic
# ....
[status, header, body]
end
end
run WrapperClass.new
In my app (jruby, rails 3, mongodb) I setup my data access objects (and other 'singletons') in an initializer /config/initializers/app_constants.rb
DATA_COLLECTION_STORE = DataCollectionStore.new(...)
SOME_OTHER_SINGLETON = SomeOtherClassTreatedLikeSingleton.new(...)
I'm new to rails (and ruby) and I realize a couple things. First, setting up these "singletons" must not be the correct approach, since those classes can be instantiated at any time anywhere else in the code (for now it's assumed that won't happen). Secondly, putting these "constants" in this initializer seems wrong b/c when I try to run tests (rake spec) or build a WAR (using warble) I can see that the initializer stuff is running so I'm creating connections to mongo, starting my "some_other_singleton", etc.
Where should this sort of stuff go?
thanks in advance for being patient with my noobness :)
You don't need to actually initialize your singletons in any config file.
Instead, you can just implement a class method in your class that will return a singleton. The following code will allow u to access the singleton using DataCollectionStore.instance
class DataCollectionStore
##instance = DataCollectionStore.new
def self.instance
##instance
end
def initialize
#intitialize you instance here
end
end
Keep in mind that you will have to set config.cache_classes to true in development.rb for this to work in your development environment. Classes are cached by default in production though.
Alternatively, you could just use the class itself as a singleton object and implement your functionality in class methods.
If your initialization is expensive, you can do it lazily using the snippet below. That way you only pay the price if your test actually invokes the instance class method.
You can test this in your app by outputting DataCollectionStore.instance in a view and using different browsers to load the page. If every thing works right, you should see the same timestamp in both browsers.
class DataCollectionStore
##instance = nil
def self.instance
##instance || ##instance = self.new
end
def initialize
#initialized_time = Time.now
end
def to_s
#initialized_time
end
end