I'm trying to set up a basic routing system in Rack, however.. I can't understand why the first route ('/') works, and the second ('/help') doesn't. What gets returned in the case of '/help' is a NoMethodError. Why is that, and how can I fix it? Thank you.
require 'rack'
class MyApp
def self.call(env)
new.call(env)
end
def self.get(path, &block)
##routes ||= {}
##routes[path] = block
end
def call(env)
dup.call!(env)
end
def call!(env)
#req = Rack::Request.new(env)
#res = Rack::Response.new
#res.write route(#req.path)
#res.finish
end
def route(path)
if ##routes[path]
##routes[path].call
else
'Not Found'
end
end
def to_do(arg)
arg
end
end
MyApp.get '/' do
'index'
end
MyApp.get '/help' do
to_do 'help'
end
run MyApp
Related
So there should be a class called Registers::Internal::Routes with a class method called draw that takes a block as a parameter:
def self.draw(&block)
...
end
It should be possible to pass the structures of the routes in this block. Example:
Registers::Internal::Routes.draw do
namespace :api do
namespace :internal do
namespace :companies do
resources :registrations, only: [:index, :show] do
post :request
member do
post :cancel
end
resources :bank_accounts, only: :index
end
end
end
end
end
it should be possible to acquire the URL of the routes, for example:
Registers::Internal::Routes.api_internal_companies_registrations_url
# api/internal/companies/registrations
Registers::Internal::Routes.api_internal_companies_registration_url("aef54ea9-239c-42c7-aee7-670a0d454f1d")
# api/internal/companies/registrations/aef54ea9-239c-42c7-aee7-670a0d454f1d
Registers::Internal::Routes.api_internal_companies_registrations_request_url
# api/internal/companies/registrations/request
Registers::Internal::Routes.api_internal_companies_registration_cancel_url("aef54ea9-239c-42c7-aee7-670a0d454f1d")
# api/internal/companies/registrations/aef54ea9-239c-42c7-aee7-670a0d454f1d/cancel
Registers::Internal::Routes.api_internal_companies_registration_bank_accounts_url("aef54ea9-239c-42c7-aee7-670a0d454f1d")
# api/internal/companies/registrations/aef54ea9-239c-42c7-aee7-670a0d454f1d/bank_accounts
So after playing a bit with this, i "managed" to try to generate an "url" everytime the namespace/resources/member/post method is called, the problem is that the "url" variable doesnt reset, therefore resulting in it being overwritten. Is there any other way to do this, simple and cleaner?
module Registers
module Internal
class Routes
def self.draw(&block)
#url = ""
class_eval(&block)
end
def self.namespace(key, &block)
generate_url("namespace", key)
class_eval(&block)
end
def self.resources(key, options, &block)
generate_url("resources", key, options)
class_eval(&block) if block_given?
end
def self.member(&block)
class_eval(&block)
end
def self.post(action)
generate_url("action", action)
end
def self.generate_url(name, key, options = nil)
if name != "action"
#url << "#{key.to_s + '/'}"
else
#url << "/#{key.to_s}"
define_class_method(#url)
end
set_options(options) if options.present?
end
def self.set_options(options)
Array(options[:only]).each do |option|
case option
when :index
#url.chop!
when :show
#url
end
define_class_method(#url)
end
end
def self.define_class_method(url)
method_name = "#{url.gsub('/', '_')}_url".to_sym
self.class.instance_eval do
define_method(method_name) do
url
end
end
end
end
end
end
I have a plain ruby class Espresso::MyExampleClass.
module Espresso
class MyExampleClass
def my_first_function(value)
puts "my_first_function"
end
def my_function_to_run_before
puts "Running before"
end
end
end
With some of the methods in the class, I want to perform a before or after callback similar to ActiveSupport callbacks before_action or before_filter. I'd like to put something like this in my class, which will run my_function_to_run_before before my_first_function:
before_method :my_function_to_run_before, only: :my_first_function
The result should be something like:
klass = Espresso::MyExampleClass.new
klass.my_first_function("yes")
> "Running before"
> "my_first_function"
How do I use call backs in a plain ruby class like in Rails to run a method before each specified method?
Edit2:
Thanks #tadman for recommending XY problem. The real issue we have is with an API client that has a token expiration. Before each call to the API, we need to check to see if the token is expired. If we have a ton of function for the API, it would be cumbersome to check if the token was expired each time.
Here is the example class:
require "rubygems"
require "bundler/setup"
require 'active_support/all'
require 'httparty'
require 'json'
module Espresso
class Client
include HTTParty
include ActiveSupport::Callbacks
def initialize
login("admin#example.com", "password")
end
def login(username, password)
puts "logging in"
uri = URI.parse("localhost:3000" + '/login')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
request = Net::HTTP::Post.new(uri.request_uri)
request.set_form_data(username: username, password: password)
response = http.request(request)
body = JSON.parse(response.body)
#access_token = body['access_token']
#expires_in = body['expires_in']
#expires = #expires_in.seconds.from_now
#options = {
headers: {
Authorization: "Bearer #{#access_token}"
}
}
end
def is_token_expired?
#if Time.now > #expires.
if 1.hour.ago > #expires
puts "Going to expire"
else
puts "not going to expire"
end
1.hour.ago > #expires ? false : true
end
# Gets posts
def get_posts
#Check if the token is expired, if is login again and get a new token
if is_token_expired?
login("admin#example.com", "password")
end
self.class.get('/posts', #options)
end
# Gets comments
def get_comments
#Check if the token is expired, if is login again and get a new token
if is_token_expired?
login("admin#example.com", "password")
end
self.class.get('/comments', #options)
end
end
end
klass = Espresso::Client.new
klass.get_posts
klass.get_comments
A naive implementation would be;
module Callbacks
def self.extended(base)
base.send(:include, InstanceMethods)
end
def overridden_methods
#overridden_methods ||= []
end
def callbacks
#callbacks ||= Hash.new { |hash, key| hash[key] = [] }
end
def method_added(method_name)
return if should_override?(method_name)
overridden_methods << method_name
original_method_name = "original_#{method_name}"
alias_method(original_method_name, method_name)
define_method(method_name) do |*args|
run_callbacks_for(method_name)
send(original_method_name, *args)
end
end
def should_override?(method_name)
overridden_methods.include?(method_name) || method_name =~ /original_/
end
def before_run(method_name, callback)
callbacks[method_name] << callback
end
module InstanceMethods
def run_callbacks_for(method_name)
self.class.callbacks[method_name].to_a.each do |callback|
send(callback)
end
end
end
end
class Foo
extend Callbacks
before_run :bar, :zoo
def bar
puts 'bar'
end
def zoo
puts 'This runs everytime you call `bar`'
end
end
Foo.new.bar #=> This runs everytime you call `bar`
#=> bar
The tricky point in this implementation is, method_added. Whenever a method gets bind, method_added method gets called by ruby with the name of the method. Inside of this method, what I am doing is just name mangling and overriding the original method with the new one which first runs the callbacks then calls the original method.
Note that, this implementation neither supports block callbacks nor callbacks for super class methods. Both of them could be implemented easily though.
I'm trying to use jruby as a script language (for writing quests for a game), but I need to prevent usage of potentially dangerous classes like Dir, Process or RubyVM.
I already found a cumbersome way to blacklist single methods and classes:
class Dir
def Dir.[]
end
def Dir.chdir
end
def Dir.chroot
end
def Dir.delete
end
def Dir.entries
end
def Dir.exist?
end
def Dir.exists?
end
def Dir.foreach
end
def Dir.getwd
end
def Dir.glob
end
def Dir.home
end
def Dir.mkdir
end
def Dir.initialitze
end
def Dir.open
end
def Dir.pwd
end
def Dir.rmdir
end
def Dir.unlink
end
def close
end
def each
end
def fileno
end
def inspect
end
def path
end
def pos
end
def read
end
def rewind
end
def seek
end
def tell
end
def to_path
end
end
But I really hope that there is a much easier way to perform this task or even better whitelist the classes that should be able to use.
You can just make specific methods private:
class Dir
private :close, :each ....
private_class_method :glob, :home ...
end
Or you can undefine whole class (bad idea as for me):
Object.send(:remove_const, :Dir)
Or remove methods (also bad idea to remove methods from Ruby core class):
class Dir
remove_method :close, :each ....
end
RSpec 3.4.0 has example.metadata[:extra_failure_lines] thing that is to be printed on failure. How do I log Selenium/Capybara actions into it?
RSpec.configure do |c|
c.before do |example|
example.metadata[:extra_failure_lines] = []
end
end
Capybara::DSL.module_eval do
alias :old_visit :visit
def visit path
example.metadata[:extra_failure_lines] << "visit #{path}"
old_visit path
end
end
This will fail because Capybara::DSL doesn't know about example.
I've exposed the example as RSpec static module variable:
RSpec.module_eval do
class << self
attr_accessor :exposed_example
end
end
RSpec.configure do |c|
c.before do |example|
RSpec.exposed_example = example
example.metadata[:extra_failure_lines] = []
end
end
# you may use another approaches of monkeypatching
Capybara::DSL.module_eval do
old = instance_method :visit
define_method :visit do |path|
RSpec.exposed_example.metadata[:extra_failure_lines] << "visit #{path.inspect}"
old.bind(self).(path)
end
end
For one of my ruby applications i need the server to route requests based on the subdomain. There are ways to do this using other gems but i decided to make my own "middleware". This code runs applications based on where the request is going to.
config.ru
require './subdomain'
require './www'
run Rack::Subdomain.new([
{
:subdomain => "test",
:application => Sinatra::Application
}
]);
subdomain.rb
module Rack
class Subdomain
def initialize(app)
#app = app
end
def call(env)
#app.each do |app|
match = 'test'.match(app[:subdomain])
if match
return app[:application].call(env)
end
end
end
end
end
My question is how can i modify this working code to work exactly the same but have it called by code that looks like this:
run Rack::Subdomain do
map 'www' do
Example::WWW
end
map 'api' do
Example::API
end
end
With suggested code:
config.ru
require './subdomain'
require './www'
run Rack::Subdomain.new do |x|
x.map 'test' do
Sinatra::Application
end
end
subdomain.rb
module Rack
class Subdomain
def initialize(routes = nil)
#routes = routes
yield self
end
def map(subdomain)
#routes << { subdomain: subdomain, application: yield }
end
def call(env)
#routes.each do |route|
match = 'test'.match(route[:subdomain])
if match
return route[:application].call(env)
end
end
end
end
end
You call the above "working code" but it doesn't seem to detect the subdomain at all, but wires it to the literal 'test'. At any rate, you can implement a pattern similar to what you want by making a map method which adds entries to your list of subdomain->application routes. I've renamed your #app to #routes since it is a hash of routes, not an application reference.
module Rack
class Subdomain
def initialize(routes = [])
#routes = routes
yield self if block_given?
end
def map(subdomain)
#routes << { subdomain: subdomain, application: yield }
end
def call(env)
#routes.each do |route|
match = 'test'.match(route[:subdomain])
if match
return route[:application].call(env)
end
end
end
end
end
rsd = Rack::Subdomain.new do |x|
x.map 'www' do
Example::WWW
end
x.map 'api' do
Example::API
end
end
run rsd