How to access Sinatra's params in helpers? - ruby

How can I access Sinatra's params hash in a custom helper?
E.g.:
# in app/helpers/my_helper.rb
module MyApp
module MyHelper
def self.test ()
params.inspect
end
end
end
# in app.rb
helpers MyHelper
test_result = test # undefined method `params' for MyApp:Module

You don’t need self for helpers:
module MyApp
module MyHelper
def test()
params.inspect
end
end
end
Note that the helper is only available in the request context (i.e. during the processing of a request):
get '/' do
test_result = test
# ...
end

Replace params.inspect with params[:inspect]. This should work.

Related

How to handle errors in Sinatra from external file?

I'm trying to handle errors in a Sinatra App including a Helper Module. I tried doing it like this:
# application_controller.rb
require './application_helper'
class ApplicationController < Sinatra::Base
helpers Sinatra::ApplicationHelper
end
# application_helper.rb
require 'sinatra/base'
module Sinatra
module ApplicationHelper
error StandardError do |e|
handle_error 500, e
end
end
helpers ApplicationHelper
end
But I cannot make it work, it is raising the error:
NoMethodError:
undefined method `error' for Sinatra::ApplicationHelper:Module
How can I use error in an external module?
Rather than using the helpers method, you need to define a registered method on your helper module. In this method the parameter passed is your app, and you can use it to configure errors, for example:
module Sinatra
module ApplicationHelper
def self.registered(app)
app.error StandardError do |e|
handle_error 500, e
end
end
end
register ApplicationHelper
end
Then in your app file you use register instead of helpers:
class ApplicationController < Sinatra::Base
register Sinatra::ApplicationHelper
end
You might want to add some helpers at the same time you register your extension, for example in this case you might want to include the handle_error method in the extension. To do that you can add another helper module inside your extension module, and call app.helpers to include it in registered.
module Sinatra
module ApplicationHelper
module Helpers
def handle_error(code, error)
#... Helper code here.
end
# Other helpers if needed.
end
def self.registered(app)
app.error StandardError do |e|
handle_error 500, e
end
# Include helpers at registration.
app.helpers Helpers
end
end
register ApplicationHelper
end

Access a constant defined in class from a mixin included in the class

I have a mixin (Api::Utility) that is referenced by two classes: Api::Utility::One::WorkstationClient and Api::Utility::Two::WorkstationClient. Both classes define a constant, BASE_URL, that I'd like to access in the mixin. When I attempt to access the constant, I get an error that reads `NameError (uninitialized constant Epic::Api::Utility::BASE_URL).
#!/usr/bin/env ruby
module Api
class WorkstationClientFactory
def self.create_client(version = :v2)
case version
when :v2
Two::WorkstationClient.new()
when :v1
One::WorkstationClient.new()
else
raise ArgumentError, "'#{version.inspect}' is not a valid version symbol. Valid: [:v1|:v2]."
end
end
end # /WorkstationClientFactory
# Api::Utility
module Utility
def bar
puts "BASE_URL: #{BASE_URL}"
end
end
module One
# Api::One::WorkstationClient
class WorkstationClient
include Api::Utility
BASE_URL = 'https://domain.tld/api/v1'
def initialize()
puts "Creating #{self.class.name}"
end
end # /WorkstationClient
end # One
module Two
# Api::Two::WorkstationClient
class WorkstationClient
include Api::Utility
BASE_URL = 'https://domain.tld/api/v2'
def initialize()
puts "Creating #{self.class.name}"
end
end # /WorkstationClient
end # /Two
end # /module
v1 = Api::WorkstationClientFactory.create_client(:v1)
v1.bar <-- error here
BASE_URL: https://domain.tld/api/v1 <-- desired result
v2 = Api::WorkstationClientFactory.create_client(:v2)
v2.bar <-- error here
BASE_URL: https://domain.tld/api/v2 <-- desired result
What's the right way to access the BASE_URL in the mixin, in this situation?
You can use self.class::BASE_URL.

Ruby nested const_missing method_missing stack too deep

I have the following class:
module APIWrapper
include HTTParty
BASE_URI = 'https://example.com/Api'
def self.const_missing(const_name)
anon_class = Class.new do
def self.method_missing method_name, *params
params = {
'Target' => const_name.to_s,
'Method' => method_name.to_s,
}
APIWrapper.call_get params
end
end
end
def self.call_get(params)
get(APIWrapper::BASE_URI, {:query => params})
end
def self.call_post(params)
post(APIWrapper::BASE_URI, params)
end
end
I want to be able to make a call to my wrapper like this:
APIWrapper::User::getAll
I'm getting a stack level too deep error:
1) Error:
test_User_getAll(APITest):
SystemStackError: stack level too deep
api_test.rb:16
What am I doing wrong?
After using the keyword def, a new scope is created, so the issue here is that the const_name variable is no longer in scope inside the body of the method_missing method.
You can keep the variable in scope by using blocks like so:
def self.const_missing(const_name)
anon_class = Class.new do
define_singleton_method(:method_missing) do |method_name, *params|
params = {
'Target' => const_name.to_s,
'Method' => method_name.to_s,
}
APIWrapper.call_get params
end
end
end
You might want to also set the constant to the anonymous class you just created:
anon_class = Class.new do
...
end
const_set const_name, anon_class
The problem is that const_name is recursively calling method_missing. When you pass a block to Class.new the block is evaluated in the scope of the class. (See the docs)
Methods do not see any local outside variables. In your case it is const_name, which is triggering method_missing recursively.
name = "Semyon"
def greet
puts "hello, #{name}!"
end
greet # undefined local variable or method ‘name’
You can name anonymous modules (and classes) in Ruby using const_set, and from there you can easily see the name. I'd also not recommend defining new methods for every class, this is what modules are for. Here is my short, self-contained example:
module Greeting
module Base
def method_missing(word)
Greeting.greet word, self.name.split("::").last
end
end
def self.greet(word, name)
puts "#{word}, #{name}!"
end
def self.const_missing(name)
const_set name, Module.new.extend(Base)
end
end
Greeting::Semyon.hello # hello, Semyon!

How to make modular helper in Sinatra

i want to make a method inside a module (for grouping reason) that can be called as a module.method, something like this:
helpers do
module UserSession
def logged_in?
not session[:email].nil?
end
def logout!
session[:email] = nil
end
end
end
but when i try to call it using UserSession.logged_in? it said that logged_in is not UserSession's method
undefined method `logged_in?' for UserSession:Module
when i move the method as UserSession's method:
helpers do
module UserSession
def self.logged_in?
not session[:email].nil? # error
end
def self.logout!
session[:email] = nil
end
end
end
it gives an error, that i could not access the session variable
undefined local variable or method `session' for UserSession:Module
what is the best solution for this problem?
You can use a different convention for the helpers method.
module UserSession
def logged_in?
not session[:email].nil?
end
def logout!
session[:email] = nil
end
end
helpers UserSession
get '/foo' do
if logged_in?
'Hello you!'
else
'Do I know you?'
end
end
The module definition can of course be in another (required) file.
Behind the scenes, helpers <Module> is doing an include, but not simply into the Sinatra application sub-class you are using for your app. The include needs to be made compatible with how get, post etc work their magic, and helpers does that for you.
nevermind, i found the answer, i have tried define_method('UserSession.logged_in?') also, but no luck
last thing i've tried is:
# outside helpers
class UserSession
##session = nil
def initialize session
##session ||= session
end
def self.check
throw('must be initialized first') if ##session.nil?
end
def self.logged_in?
self.check
not ##session[:email].nil?
end
def self.logout
self.check
##session.delete :email
end
end
but something must be called first
before // do
UserSession.new session
end
then it can be used as desired:
get '/' do
if UserSession.logged_in?
# do something here
end
end

How do I access a class instance variable across class of same module?

I need to access the config variables from inside another class of a module.
In test.rb, how can I get the config values from client.rb? #config gives me an uninitialized var. It's in the same module but a different class.
Is the best bet to create a new instance of config? If so how do I get the argument passed in through run.rb?
Or, am I just structuring this all wrong or should I be using attr_accessor?
client.rb
module Cli
class Client
def initialize(config_file)
#config_file = config_file
#debug = false
end
def config
#config ||= Config.new(#config_file)
end
def startitup
Cli::Easy.test
end
end
end
config.rb
module Cli
class Config
def initialize(config_path)
#config_path = config_path
#config = {}
load
end
def load
begin
#config = YAML.load_file(#config_path)
rescue
nil
end
end
end
end
test.rb
module Cli
class Easy
def self.test
puts #config
end
end
end
run.rb
client = Cli::Client.new("path/to/my/config.yaml")
client.startitup
#config is a instance variable, if you want get it from outside you need to provide accessor, and give to Easy class self object.
client.rb:
attr_reader :config
#...
def startitup
Cli::Easy.test(self)
end
test.rb
def self.test(klass)
puts klass.config
end
If you use ##config, then you can acces to this variable without giving a self object, with class_variable_get.
class Lol
##lold = 0
def initialize(a)
##lold = a
end
end
x = Lol.new(4)
puts Lol.class_variable_get("##lold")
I recommend to you read metaprogramming ruby book.

Resources