Including external Ruby module inside a module? - ruby

I'm making a Rack framework for Ruby that runs a App::Router module inside of the following:
module App
Router = HttpRouter.new do
get('/') { |env| erb('home') }
end
end
Notice the erb() method I wish to use in my router. The problem is getting the methods from an external source (my framework) into a module and get passed into the do block inside.
Is there there a possible way to get modules from an external source into a module in another file?
Thanks.

Is erb a method you define somewhere? Try something like this:
require 'path/to/module/with/erb_method'
module App
include YourModule
Router = HttpRouter.new do
get('/') { |env| erb('home') }
end
end

module App
def foo
"bar"
end
end
module Route
include App
end
include Route
foo
=> "bar"

Related

Handling a method from outside of a mixin module in ruby and rspec

I have a class that looks like this:
module ReusableBitlyLinks
def shorten_url url, *args
ShortenedUrl.shorten_url_with_bitly( url, email.user )
end
end
I have a test that looks like this:
require File.expand_path("../../../../app/decorators/mixins/reusable_bitly_links", __FILE__)
include ReusableBitlyLinks
describe ReusableBitlyLinks do
describe "shorten_url" do
it "works" do
ReusableBitlyLinks.shorten_url('asdf').should == 'asdf'
end
end
end
When I run the test I get an error that says:
uninitialized constant ReusableBitlyLinks::ShortenedUrl
How do I mock stub ReusableBitlyLinks::ShortenedUrl?
Is ShortenedUrl defined inside ReusableBitlyLinks module? If not - try to access it with ::ShortenedUrl.shorten_url_with_bitly.
Not sure what stubbing has to do with it...
In your mixin module, you need to tell it that ShortenedUrl is from outside the module by prepending :::
module ReusableBitlyLinks
def shorten_url(url, *args)
::ShortenedUrl.shorten_url_with_bitly(url, email.user)
end
end
You may also need to do a require inside the mixin file to load whatever file it is that defines ShortenedUrl.
Further, in your test file, the line:
require File.expand_path("../../../../app/decorators/mixins/reusable_bitly_links", __FILE__)
could be simplified to:
require_relative '../../../../app/decorators/mixins/reusable_bitly_links'

Access Ruby Instance Variable from Included Module

I have a Sinatra API file that has following code-
require 'json'
require_relative 'api_logger'
include ApiLogger
get /myapi/:id
request_params = request.env
write_log('log message')
end
Then I have a module containing the methods 'write_log'-
module ApiLogger
def write_log(message)
file.write(request['user']+message)
end
But request['user'] is coming out blank.
So the question is how to access the request variable from Sinatra API file in ApiLogger module? Also, I'm creating service class objects from API class and pass them request object at initialization. Can the module 'ApiLogger' access that 'request' instance variable from service class if the service classes just include 'ApiLogger'?
You could pass it as an additional argument.
Something like:
require 'json'
require_relative '../../lib/helpers/api_logger'
include ApiLogger
get /myapi/:id
request_params = request.env
write_json_log('log message', request)
end
and
def write_json_log(message, request)
file.write(request['auth_subject']+message)
end
I did not want to pass request object to each method. So I made 'request_params' a global variable in all classes that need to log and added this line in 'ApiLogger' to fetch the value of request object-
request = instance_variable_get '#request_params'
You were almost there, all you needed was include your module in the helpers in order to have a direct access to the request object. Here's a slightly modified version of your code that runs as a standalone program:
require 'sinatra'
module ApiLogger
def write_log(message)
$stdout.write(request.env['sinatra.route'] + message)
end
end
helpers do
include ApiLogger
end
get '/test' do
write_log('log message')
'ok'
end

How can I use my Sinatra helpers in included modules?

I have my routes and helpers defined in external files and included by Sinatra, however I'm new to Ruby and I can't now figure out how I can use my helper methods in my routes. When I run the code in RubyMine and access a profile URL I get the error "NoMethodError - undefined method `protected!'"
## Main class
require 'sinatra/base'
class MyApp < Sinatra::Base
register Sinatra::MyHelpers
register ProfileRoutes
...
end
## Helpers include
require 'sinatra/base'
module Sinatra
module LocutusHelpers
def self.registered( app )
app.before do
...
end
def protected!
...
end
end
end
end
## Routes include
require 'sinatra/base'
module ProfileRoutes
def self.registered( app )
app.get '/profile/:userid' do
protected!
...
end
end
end
I've tried def self.protected! for the helper but then it cant access the request object.
I've also tried Sinatra::MyHelpers.protected!, Sinatra.protected! and app.protected!, errors are thrown for all of these too
Do you know how I can access the helpers from my routes? Or have I set something up incorrectly?
It looks like you are mixing up adding helpers from extensions and configuring your app from extensions.
You need to move protected! into a module, then when registering your extension add that module as a helpers module.
module LocutusHelpers
# new module, move protected! into here
module HelperMethods
def protected!
...
end
end
def self.registered( app )
# add new hlpers module
app.helpers HelperMethods
# other extension setup as before...
app.before do
...
end
end
end

Monkey patch class methods

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.

How can I test helpers blocks in Sinatra, using Rspec?

I'm writing a sinatra app and testing it with rspec and rack/test (as described on sinatrarb.com).
It's been great so far, until I moved some rather procedural code from my domain objects to
sinatra helpers.
Since then, I've been trying to figure out how to test these in isolation ?
I test my sinatra helpers in isolation by putting the helper methods within its own module.
Since my sinatra application is a little bit bigger than the usual hello world example, I need to split it up into smaller parts. A module for the common helpers suits my use case well.
If you write a quick demo, and you define your helper methods within the helpers { ... } block, I don't think testing it is absolutely necessary. Any sinatra app in production, may require more modularity anyways.
# in helpers.rb
module Helpers
def safe_json(string)
string.to_s.gsub(/[&><']/) { |special| {'&' => '\u0026', '>' => '\u003E', '<' => '\u003C', "'" => '\u0027'}[special] }
end
end
# in app.rb
helpers do
include Helpers
end
# in spec/helpers_spec.rb
class TestHelper
include Helpers
end
describe 'Sinatra helpers' do
let(:helpers) { TestHelper.new }
it "should escape json to inject it as a html attribute"
helpers.safe_json("&><'").should eql('\u0026\u003E\u003C\u0027')
end
end
Actually you don't need to do:
helpers do
include FooBar
end
Since you can just call
helpers FooBar
The helpers method takes a list of modules to mix-in and an optional block which is class-eval'd in: https://github.com/sinatra/sinatra/blob/75d74a413a36ca2b29beb3723826f48b8f227ea4/lib/sinatra/base.rb#L920-L923
maybe this can help you some way http://japhr.blogspot.com/2009/03/sinatra-innards-deletgator.html
I've also tried this (which needs to be cleaned up a bit to be reusable) to isolate each helper in its own environment to be tested:
class SinatraSim
def initialize
...set up object here...
end
end
def helpers(&block)
SinatraSim.class_eval(&block)
end
require 'my/helper/definition' # defines my_helper
describe SinatraSim do
subject { SinatraSim.new(setup) }
it "should do something"
subject.expects(:erb).with(:a_template_to_render) # mocha mocking
subject.my_helper(something).should == "something else"
end
end

Resources