I'm developing a module that contains standard methods as well as CLI (Thor) and API (Sinatra). I've created a Submodule that includes a class that inherits Sinatra::Base:
module Monteverde
module Activity
def self.version
"1.0.1"
end
class API < Sinatra::Base
set :port, 22340
get "/version" do
Monteverde::Activity.version
end
run! if defined? Monteverde::OPERATION
end
end
end
This "Activity" is invokes from the CLI (Thor):
module Monteverde
class CLI < Thor
desc "api", "Start Activity API"
def api
Monteverde.const_set("OPERATION", "started")
Monteverde::Activity::API
end
desc "aversion", "Get Activity Version"
def aversion
puts Monteverde::Activity.version
end
end
end
If I don't add an "if" to Sinatra's run! it will run automatically and take over the rest of the methods in the module:
...
class API < Sinatra::Base
register Sinatra::DefaultRoutes
set :port, 22340
get "/version" do
Monteverde::Activity.version
end
run!
end
$ ruby monteverde.rb aversion
$ == Sinatra (v2.0.0) has taken the stage on 22340 for development with backup from Puma
$ ...
My issue is that the OPERATION constant is not recognized even though it's set right before the module is called.
Why isn't OPERATION being recognized and how can I get it to be?
Is there another more intuitive/useful way to get Sinatra not to fire when I call the module?
In your API class definition, the run! line happens as soon as you require that file. It doesn't run again if you reference the class, as you're trying to do with the last line of your def api method.
To get around this, you can move the run! command into a method, and call that from Thor:
class API < Sinatra::Base
# ... other stuff
def self.start
run! if defined? Monteverde::OPERATION
end
end
Then in the Thor file:
def api
Monteverde.const_set("OPERATION", "started")
Monteverde::Activity::API.start
end
You can deduce that run! is a class method since it's callable in the scope of the class definition (not in an instance method), and so I define a class method to call it.
It would be simpler, though, to not define def self.start, and instead just call the run! method from the Thor file directly:
def api
Monteverde::Activity::API.run!
end
Related
This is something that I thought would be straightforward but I'm having issues around testing the rack-timeout gem. I have a sinatra base class with an endpoint which does some logic.
module MyModule
class MySinatra < Sinatra::Base
use Rack::Timeout
Rack::Timeout.timeout = 10
get '/dosomething' do
#do the normal logic.
end
end
end
More information on the rack-timeout gem is here. I'm trying to setup a test where I can send a request which I know will take more than a few seconds in order for it to fail.
Here is the test so far
require "test/unit"
require "mocha/setup"
require 'rack/timeout'
def test_rack_timeout_should_throw_timed_out_exception_test
Rack::Timeout.stubs(:timeout).returns(0.0001)
assert_raises TimeoutError do
get "/dosomething"
end
Rack::Timeout.unstub
end
There are a number of ways this could be done but I am not sure how they would be implemented
Override the '/dosomething' method as part of the test to {sleep 3}
Do the same as above but with a stubbing or mocking library
instead of using get "/dosomething" in the test, create a net::http response which will keep the request open.
Any thoughts on this would be very much appreciated.
First of all your test will not actually pass, because the error is not handed through to the test. It is only raised on the server side. Luckily, rack-test provides the last_response.errors method to check whether there were errors. Therefore i would write the above test as follows:
def test_rack_timeout_should_throw_timed_out_exception
Rack::Timeout.stubs(:timeout).returns(0.0001)
get '/dosomething'
assert last_response.server_error?, 'There was no server error'
assert last_response.errors.include?('Timeout::Error'), 'No Timeout::Error raised'
Rack::Timeout.unstub
end
Now the only thing left to do is to simulate a slow response by overriding the route. It seemed simple at first but then i realized it is not so simple at all when i got my hands on it. I fiddled around a lot and came up with this here:
class Sinatra::Base
def self.with_fake_route method, route, body
old_routes = routes.dup
routes.clear
self.send(method.to_sym, route.to_s, &body.to_proc)
yield
routes.merge! old_routes
end
end
It will allow you to temporarily use only a route, within the block you pass to the method. For example now you can simulate a slow response with:
MyModule::MySinatra.with_fake_route(:get, '/dosomething', ->{ sleep 0.0002 }) do
get '/dosomething'
end
Note that the get '/dosomething' inside the block is not the definition of the temporary route, but a method of rack-test firing a mock request. The actual override route is specified in form of arguments to with_route.
This is the best solution i could come up with but i would love to see a more elegant way to solve this.
Complete working example (ran on Ruby 1.9.3.p385):
require 'sinatra/base'
require 'rack/timeout'
module MyModule
class MySinatra < Sinatra::Base
use Rack::Timeout
Rack::Timeout.timeout = 10
get '/dosomething' do
'foo'
end
end
end
require 'test/unit'
require 'rack/test'
require 'mocha/setup'
class Sinatra::Base
def self.with_fake_route method, route, body
old_routes = routes.dup
routes.clear
self.send(method.to_sym, route, &body)
yield
routes.merge! old_routes
end
end
class Tests < Test::Unit::TestCase
include Rack::Test::Methods
def app
MyModule::MySinatra
end
def test_rack_timeout_should_throw_timed_out_exception
Rack::Timeout.stubs(:timeout).returns(0.0001)
MyModule::MySinatra.with_fake_route(:get, '/dosomething', ->{ sleep 0.0002 }) do
get '/dosomething'
end
assert last_response.server_error?, 'There was no server error'
assert last_response.errors.include?('Timeout::Error'), 'No Timeout::Error raised'
Rack::Timeout.unstub
end
end
produces:
1 tests, 2 assertions, 0 failures, 0 errors, 0 skips
I have a skeleton Padrino (0.10.7) project, with pretty much no code. I am trying to insert a middleware in boot.rb:
##
# Add your after (RE)load hooks here
#
Padrino.after_load do
DataMapper.finalize
Padrino.use MyClass #Line (1) added by me
end
Padrino.load!
In MyClass,
class MyClass
def initialize arg
#arg = arg
end
end
If I try to use thin server (1.5.x), I get this exception (only when I insert my middleware):
Uncaught exception: app required
Same works fine with builtin webrick.
Any idea on how to make it work with thin?
Never mind, found it. Basically, you need to define the call (env) method too, otherwise it wont even start the server. This is what minimum is required from a middleware:
class MyClass
def initialize app
#app = app
end
def call env
#app.call env
end
end
I'm trying to do some monkey patching in ActiveShipping UPS class .
I need to add a class level method (starting with .self), so here it's what I'm trying to do:
module ActiveMerchant
module Shipping
class UPS < Carrier
def self.process_request(receiver, sender, packages, options = {})
# some code
end
def regular_method
"foobar"
end
end
end
end
Unfortunately when I'm trying to use it:
ActiveMerchant::Shipping::UPS.process_request(receiver etc)
I get an error:
NoMethodError: undefined method `process_request' for ActiveMerchant::Shipping::UPS:Class
from (irb):6
from C:/Ruby19/bin/irb.bat:19:in `<main>'
There is no class method named process_request in original class.
In original UPS class provided in gem there is one static method defined self.retry_safe = true
and I can use it without errors.
I can also use regular_method after creating instance of UPS class.
More details provided:
I'm working with Rails 2.3 ( :-( ) and Ruby 1.9.2. I have no influce on environment.
Monkey patched code is under plugins/my_plugin/lib/active_shipping/ext/carriers/ups.rb
In /active_shipping I have file named extensions.rb in which i have:
require 'active_shipping'
require_relative 'ext/carriers'
require_relative 'ext/carriers/ups'
It deals with loading everything properly (I suppose basing on regular_method beheaviour from first chunk of code in my question).
I try to invoke process_request in one of my Controllers. This part is little tricky, beacuse i'm using sth like this:
MyModel.courier_service.process_request(parameters)
where courier_service, in this case holds the ActiveMerchant::Shipping::UPS class.
I'm still a newbie in Ruby and don't know what sort of details i should provide.
Maybe you want to do it in another way
File patch_classes.rb:
module ActiveMerchantExpand
module Shipping
module ClassMethods
def self.process_request(receiver, sender, packages, options = {})
# some code
end
end
module InstanceMethods
def regular_method
"foobar"
end
end
def self.included(receiver)
receiver.extend ClassMethods
receiver.send :include, InstanceMethods
end
end
end
Then you have to load your class "ActiveMerchant::Shipping::UPS"
and after that you can attach your methods to your class via
Rails.configuration.to_prepare do
require_dependency [[file for ActiveMerchant::Shipping::UPS]]
require 'patch_classes' )
ActiveMerchant::Shipping::UPS.send(:include, ::ActiveMerchantExpand::Shipping)
end
This is from rails plugin writing, i hope this helps.
regards tingel2k
Do you explicitly require file with your monkey patch? If you just put it under your app or lib path without requiring, it wouldn't load because constant ActiveMerchant::Shipping::UPS is defined in gem and it doesn't trigger dependency resolution mechanism.
I have a modular Sinatra app. I'm setting some custom variables in my configure block and want to access these settings in my model.
The problem is, I get a NoMethodError when I try and access my custom settings from MyModel. Standard settings still seem to work fine though. How can I make this work?
# app.rb
require_relative 'models/document'
class App < Sinatra::Base
configure do
set :resource_path, '/xfiles/i_want_to_believe'
end
get '/' do
#model = MyModel.new
haml :index
end
end
# models/my_model.rb
class MyModel
def initialize
do_it
end
def do_it
...
settings.resource_path # no method error
...
settings.root # works fine
end
end
i think that you should be able to access it via
Sinatra::Application.settings.documents_path
I ended up doing:
#document.rb
class Document
def self.documents_path=(path)
#documents_path = path
end
def self.documents_path
#documents_path
end
...
end
#app.rb
configure do
set :documents_path, settings.root + "/../documents/"
Document.documents_path = settings.documents_path
end
then just using Document.documents_path inside my find method.
In Sinatra, I'm unable to create global variables which are assigned values only once in the application lifetime. Am I missing something? My simplified code looks like this:
require 'rubygems' if RUBY_VERSION < "1.9"
require 'sinatra/base'
class WebApp < Sinatra::Base
#a = 1
before do
#b = 2
end
get '/' do
puts #a, #b
"#{#a}, #{#b}"
end
end
WebApp.run!
This results in
nil
2
in the terminal and ,2 in the browser.
If I try to put #a = 1 in the initialize method, I'm getting an error in the WebApp.run! line.
I feel I'm missing something because if I can't have global variables, then how can I load large data during application instantiation?
before do seems to get called every time there is a request from the client side.
class WebApp < Sinatra::Base
configure do
set :my_config_property, 'hello world'
end
get '/' do
"#{settings.my_config_property}"
end
end
Beware that if you use Shotgun, or some other Rack runner tool that reloads the code on each request the value will be recreated each time and it will look as if it's not assigned only once. Run in production mode to disable reloading and you will see that it's only assigned on the first request (you can do this with for example rackup --env production config.ru).
I ran into a similar issue, I was trying to initialize an instance variable #a using the initialize method but kept receiving an exception every time:
class MyApp < Sinatra::Application
def initialize
#a = 1
end
get '/' do
puts #a
'inside get'
end
end
I finally decided to look into the Sinatra code for initialize:
# File 'lib/sinatra/base.rb', line 877
def initialize(app = nil)
super()
#app = app
#template_cache = Tilt::Cache.new
yield self if block_given?
end
Looks like it does some necessary bootstrapping and I needed to call super().
def initialize
super()
#a = 1
end
This seemed to fix my issue and everything worked as expected.
Another option:
helpers do
def a
a ||= 1
end
end
Building on Theo's accepted solution, it is also possible to do:
class App < Sinatra::Application
set :blabla, ''
namespace '/b' do
get '/baby' do
# do something where bouh is assigned a value
settings.blabla = 'bouh'
end
end
namespace '/z'
get '/human' do
# settings.blabla is available here with newly assigned value
end
end
end
You could use OpenStruct.
require 'rubygems'
require 'sinatra'
require 'ostruct'
configure do
Struct = OpenStruct.new(
:foo => 'bar'
)
end
get '/' do
"#{Struct.foo}" # => bar
end
You can even use the Struct class in views and other loaded files.