Sinatra Set Settings (Ruby) - ruby

Using Sinatra in Ruby you can set the server's settings by doing:
set :myvariable, "MyValue"
and then access it anywhere in templates etc with settings.myvariable.
In my script I need to be able to re-set these variables falling back to a bunch of defaults. I figured the easiest way to do this would be to have a function that performs all the sets calling it at the start of the Sinatra server and when I need to make the alterations:
class MyApp < Sinatra::Application
helpers do
def set_settings
s = settings_from_yaml()
set :myvariable, s['MyVariable'] || "default"
end
end
# Here I would expect to be able to do:
set_settings()
# But the function isn't found!
get '/my_path' do
if things_go_right
set_settings
end
end
# Etc
end
As explained in the code above, the set_settings function isn't found, am I going about this the wrong way?

You're trying to call set_settings() inside the class scope of MyApp, but the helper method you used to define it only defines it for use inside that get... do...end block.
If you want set_settings() to be available statically (at class-load time instead of at request-process time), you need to define it as a class method:
class MyApp < Sinatra::Application
def self.set_settings
s = settings_from_yaml()
set :myvariable, s['MyVariable'] || "default"
end
set_settings
get '/my_path' do
# can't use set_settings here now b/c it's a class
# method, not a helper method. You can, however,
# do MyApp.set_settings, but the settings will already
# be set for this request.
end

Related

Dry::Web::Container yielding different objects with multiple calls to resolve

I'm trying write a test to assert that all defined operations are called on a successful run. I have the operations for a given process defined in a list and resolve them from a container, like so:
class ProcessController
def call(input)
operations.each { |o| container[o].(input) }
end
def operations
['operation1', 'operation2']
end
def container
My::Container # This is a Dry::Web::Container
end
end
Then I test is as follows:
RSpec.describe ProcessController do
let(:container) { My::Container }
it 'executes all operations' do
subject.operations.each do |op|
expect(container[op]).to receive(:call).and_call_original
end
expect(subject.(input)).to be_success
end
end
This fails because calling container[operation_name] from inside ProcessController and from inside the test yield different instances of the operations. I can verify it by comparing the object ids. Other than that, I know the code is working correctly and all operations are being called.
The container is configured to auto register these operations and has been finalized before the test begins to run.
How do I make resolving the same key return the same item?
TL;DR - https://dry-rb.org/gems/dry-system/test-mode/
Hi, to get the behaviour you're asking for, you'd need to use the memoize option when registering items with your container.
Note that Dry::Web::Container inherits Dry::System::Container, which includes Dry::Container::Mixin, so while the following example is using dry-container, it's still applicable:
require 'bundler/inline'
gemfile(true) do
source 'https://rubygems.org'
gem 'dry-container'
end
class MyItem; end
class MyContainer
extend Dry::Container::Mixin
register(:item) { MyItem.new }
register(:memoized_item, memoize: true) { MyItem.new }
end
MyContainer[:item].object_id
# => 47171345299860
MyContainer[:item].object_id
# => 47171345290240
MyContainer[:memoized_item].object_id
# => 47171345277260
MyContainer[:memoized_item].object_id
# => 47171345277260
However, to do this from dry-web, you'd need to either memoize all objects auto-registered under the same path, or add the # auto_register: false magic comment to the top of the files that define the dependencies and boot them manually.
Memoizing could cause concurrency issues depending on which app server you're using and whether or not your objects are mutated during the request lifecycle, hence the design of dry-container to not memoize by default.
Another, arguably better option, is to use stubs:
# Extending above code
require 'dry/container/stub'
MyContainer.enable_stubs!
MyContainer.stub(:item, 'Some string')
MyContainer[:item]
# => "Some string"
Side note:
dry-system provides an injector so that you don't need to call the container manually in your objects, so your process controller would become something like:
class ProcessController
include My::Importer['operation1', 'operation2']
def call(input)
[operation1, operation2].each do |operation|
operation.(input)
end
end
end

Sinatra Multiple Parallel Requests Variable Behaviour

I am fairly new to ruby and would like to understand how class instance variables behave in case of multiple parallel requests.
I have a method inside my controller class which is called everytime for each request for a specific operation (create in this case)
class DeployProvision
def self.create(data)
raise "Input JSON not received." unless data
# $logger.info input_data.inspect
failure = false
response_result = ""
response_status = "200"
#validator = SchemaValidate.new
validation = #validator.validate_create_workflow(data.to_json)
end
end
This method is called as (DeployProvision.create(data))
I am a little confused on how #validator class instance variable behaves when multiple requests come. Is it shared among multiple requests. Is it a good idea to declare this as class instance variable instead of a local variable ?
I am working on an existing code base and would like to understand the intent of creating #validator as a class instance variable instead of local variable.
You can write ultra-simple script like this:
require 'sinatra'
class Foo
def self.bar
#test = Time.now
puts #test
end
end
get '/' do
Foo.bar
end
and you'll see it does nothing, because with every call, you're creating new instance of Time(SchemaValidate in your code).
If you used memoization and had something like #validator ||= SchemaValidate.new you would have one instance of SchemaValidate stored between requests.
I don't think that'd change anything in terms of performance and I don't have idea why would anyone do something like that.
You can have some fun with ultra-simple scripts with sinatra to test how it behaves.
Good luck with this code!

Inserting middleware in Padrino

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

Pass arguments to new sinatra app

Simple question: I want to be able to pass options into my sinatra app in config.ru. How is that possible? My config.ru looks like this:
run MyApp
But I want to have this in my MyApp class to take arguments:
class MyApp < Sinatra::Base
def initialize(config)
#config = config
end
end
But I can't figure out a way to do this. Ideas?
Use set/settings
require 'sinatra/base'
class MyApp < Sinatra::Base
get '/' do
settings.time_at_startup.to_s
end
end
# Just arbitrarily picking time as it'll be static but, diff for each run.
MyApp.set :time_at_startup, Time.now
run MyApp
Use a config file. See Sinatra::ConfigFile in contrib (which also uses set and settings, but loads params from a YAML file)
If you want to configure with params, I figured out that you could do this:
require 'sinatra/base'
class AwesomeApp < Sinatra::Base
def initialize(app = nil, params = {})
super(app)
#bootstrap = params.fetch(:bootstrap, false)
end
end
rnicholson's response will be the best answer in most cases but if what you want is to have access to an instance variable in your routes, you can set these up using the before filter as explained in the Sinatra README:
Before filters are evaluated before each request within the same context as the routes will be and can modify the request and response. Instance variables set in filters are accessible by routes and templates:
before do
#note = 'Hi!'
request.path_info = '/foo/bar/baz'
end
get '/foo/*' do
#note #=> 'Hi!'
params['splat'] #=> 'bar/baz'
end

Alternative initialize for a Class to avoid processing already known information

I have a class, Autodrop, that contains several methods , a.o. 'metadata', that call an external API (dropbox). They are slow.
However, I already often have that metadata around when initializing the AutodropImage, so I should make the methods smarter.
What I have in mind is this:
class Autodrop
include Dropbox
attr_reader :path
def initialize(path)
#path = path
end
def self.from_entry(drop_entry)
#drop_entry = drop_entry
self.initialize(#drop_entry.path)
end
def metadata
if #drop_entry = nil
return heavy_lifting_and_network_traffic
else
return #drop_entry.metadata
end
end
#...
end
Now, I would expect to call
entry = BarEntry.new()
foo = Autodrop.from_entry(entry)
foo.metadata
In order to avoid that heavy lifting and network traffic call.
But this does not work. And somehow, in all my newbieness, I am sure I am goind at this all wrong.
Is there a term I should look for and read about first? How would you go for this?
Note, that the examples are simplified: in my code, I inherit AutodropImage < Autodrop for example, which is called from withing AutodropGallery < Autodrop. The latter already knows all metadata for the AutodropImage, so I mostly want to avoid AutodropImage going over the heavy lifting again.
You are creating an instance variable #drop_entry in your class method from_entry and obviously it wont be available to your object that you are creating in this method. One workaround is to pass it as a parameter when you are initializing the class. It should work if you do the following modifications:
In your from_entry class method change
self.initialize(#drop_entry)
to
new(#drop_entry)
Modify initialize method to:
def initialize(drop_entry)
#drop_entry = drop_entry
#path = #drop_entry.path
end
Or if your class is tied up to pass only the path parameter, ie. you dont want to change the other existing code then you can use an optional parameter drop entry like so
def initialize(path, drop_entry=nil)
You would need to cache the metadata in a class variable.
Edit: Or in a class level instance variable.
Maybe this read will help: http://railstips.org/blog/archives/2006/11/18/class-and-instance-variables-in-ruby/

Resources