What's the correct way to call the erb function (which is available via Sinatra) if I have a helper class outside the Sinatra main application.
For example, I have in my_app.rb:
require 'sinatra'
require 'my_external_class.rb'
get '/' do
MyExternalClass.some_function(request)
end
Then I have a file called: my_external_class.rb
class MyExternalClass
def self.some_function request
erb :some_template
end
end
When running Sinatra and executing a get request, I get a undefined method `erb' for MyExternalClass. I assume I am missing either some require, or maybe I need to pass the Sinatra object to the class (but I don't know how to achieve that).
How could I achieve something like that?
You can achieve this by creating a helpers module for your methods:
# module instead of a class
module MyHelpersModule
# no need for 'self'
def some_function(request)
erb :some_template
end
end
Then in your main app file call helpers MyHelpersModule. This will make all the methods in MyHelpersModule available in your application and also, since they are executed in the same context, the existing Sinatra methods (like erb) will be available to your helpers.
require 'sinatra'
require './my_helpers_module'
helpers MyHelpersModule
get '/' do
some_function(request)
end
To imitate rendering behavior of Sinatra controller in some other class you can create module like this:
module ErbRender
include Sinatra::Templates
include Sinatra::Helpers
include Sinatra::ContentFor
def settings
#settings ||= begin
settings = Sinatra::Application.settings
settings.root = "#{ROOT}/app"
settings
end
end
def template_cache
#template_cache ||= Tilt::Cache.new
end
end
Here you may need to tune settings.root
Usage example:
class ArticleIndexingPostBody
include ErbRender
def get_body
erb :'amp/articles/show', layout: :'amp/layout'
end
end
This will properly render templates with layouts including content_for
Related
I'm splitting a large Sinatra file into different files using registers. This is one way to have a modular Sinatra app using extensions.
I'm ending up with something like the following code:
MyApp < Sinatra::Base
register OneRegister
register SecondRegister
end
module OneRegister
def self.registered(app)
app.helpers OneRegisterHelper
app.get "/one-endpoint" do
do_stuff
end
end
module OneRegisterHelper
def do_stuff
# Some code
end
end
end
module SecondRegister
def self.registered(app)
app.helpers SecondRegisterHelper
app.get "/second-endpoint" do
do_stuff
end
end
module SecondRegisterHelper
def do_stuff
# Different code
end
end
end
The problem is how Sinatra works with registers and helpers. Every time I create a new helper for a register I'm polluting the main Sinatra app scope with the methods in the helpers.
So, the method do_stuff is going to be overwritten by the SecondRegisterHelper (this is how Ruby works when including a module) but I'd like to have different implementations for the methods without worry if I'm using the same method name or a different one (image an app with 25 registers with small methods in each one).
Basically, I'd like to have different registers with private methods because I usually write very small private methods with a single responsibility. Any ideas, how I can achieve this?
I don't think this is achievable in the way you are trying. If you have methods with similar names in different modules mixed into a single class the last just wins.
So in this case I would create a modular app combined with a config.ru to setup your application.
class OneRegister < Sinatra::Base
# helpers here
end
class SecondRegister < Sinatra::Base
# helpers here
end
In config.ru
app = Rack::URLMap.new(
'/one-endpoint' => OneRegister,
'/second-endpoint' => TwoRegister
)
run app
No you helpers are scoped to a single class.
I know that in Cucumber that I can create a module and then include that module into the World object and all the methods that I have created within the newly created module are available globally
module MyModule
def my_method
end
end
World(MyModule)
Now anywhere in my cucumber tests I can call my_method and it will work
The issue I see here and an issue I have come across is duplication of method names, as the application gets bigger or other testers/developers work on the application.
So if I was to wrap everything up in its own module and create module methods like so
module MyModule
def self.my_method
page.find('#element')
end
end
World(MyModule)
MyModule.my_method
# This will return undefined variable or method 'page' for MyModule module
So being new to using modules I have read that you can extend other modules so that you can use those methods within another module
So to access the page method I would need to access Capybara::DSL
module MyModule
extend Capybara::DSL
def self.my_method
page.find('#element')
end
end
World(MyModule)
MyModule.my_method now works, but my question is rather than extend individual namespaces for every module that I need access to is there a way to extend/include everything or is this a bad practice?
Another example of where things fail are when I try to access instances of a class
module SiteCss
def login_page
Login.new
end
end
class Login < SitePrism::Page
element :username, "#username"
end
module MyModule
extend Capybara::DSL
def self.my_method
page.find('#element')
login_page.username.set('username')
end
end
World(MyModule)
So with this example if I was it try and call login_page.username I would get
undefined method `login_page`
I'm really looking for the correct way to be doing this.
In conclusion I am trying to understand how to use custom modules and classes in cucumber without having to load everything into the World object.
Yes, it's not pretty to extend a module multiple times, if that's really what you want to do, I can suggest a way you can improve it a bit:
base class which will inherit your page object framework and extend(include is probably the correct option here):
module Pages
class BasePage < SitePrism::Page
extend Capybara::DSL
end
end
Now your login class:
module Pages
class Login < BasePage
element :username, "#username"
def yourmethod
page.find('#element')
username.set('username')
end
end
end
Now the bits you are probably interested in, expose yourmethod to cucumber world:
module SiteCss
def page_object
#page_object ||= Pages::Login.new
end
end
World(SiteCss)
Now you should have access to yourmethod in a cleaner way...
Usage:
#page_object.yourmethod
Although the most important suggestion I could give you is run from SitePrism... Create your own page object framework... don't fall into SitePrism trap...
I've defined a Sinatra helper in the usual way:
module Sinatra
module FooHelper
# code goes here
end
end
In my helper, among other things, I'd like to add a method to Numeric:
module Sinatra
module FooHelper
class ::Numeric
def my_new_method
end
end
end
end
However, in the interests of being unobtrusive, I only want to do add this method if my Sinatra helper is actually included in an application; if nobody runs helpers Sinatra::FooHelper, I don't want to affect anything (which seems like a reasonable thing to expect of a Sinatra extension).
Is there any hook that's fired when my helper is included, that would enable me to add my method only once that happens?
You can use the Module#included method to do this. I think you will need to modify your class definition slightly to use class_eval. I've tested the following and it works as expected:
module Sinatra
module FooHelper
def self.included(mod)
::Numeric.class_eval do
def my_new_method
return "whatever"
end
end
end
end
end
Probably something quite basic but I want to be able to use some custom helper methods in a modular Sinatra app. I have the following in ./helpers/aws_helper.rb
helpers do
def aws_asset( path )
File.join settings.asset_host, path
end
end
and then in my view i want to be able to use this method like so
<%= image_tag(aws_asset('/assets/images/wd.png')) %>
but i get the above area, so within my app.rb file i am
require './helpers/aws_helper'
class Profile < Sinatra::Base
get '/' do
erb :index
end
end
So is my issue that i am requiring it outside of my Profile class. which doesn't make sense as I am requiring my config files for ENV variables the same way and they are being read, but then again they are not methods so i guess that does make sense.
I think maybe im struggling to get my head around what a modular app is as opposed to using a classic styled sinatra app.
Any pointers appreciated
Error message
NoMethodError at / undefined method `aws_asset' for #<Profile:0x007f1e6c4905c0> file: index.erb location: block in singletonclass line: 8
When you use helpers do ... in the top level like this you are adding the methods as helpers to Sinatra::Application and not your Profile class. If you are using the Sinatra modular style exclusively make sure you only ever use require 'sinatra/base', and not require sinatra, this will prevent you from mixing up the two styles like this.
In this case you should probably create a module for your helpers instead of using helpers do ..., and then add that module with the helpers method in your Profile class.
In helpers/aws_helper.rb:
module MyHelpers # choose a better name of course
def aws_asset( path )
File.join settings.asset_host, path
end
end
In app.rb:
class Profile < Sinatra::Base
helpers MyHelpers # include your helpers here
get '/' do
erb :index
end
end
I have a situation where I can access a module's functions from one file but not another. These files are both in the same directory. I'll try to recreate the code the best I can:
Directory Structure:
init.rb
lib/FileGenerator.rb
lib/AutoConfig.rb
lib/modules/helpers.rb
lib/AutoConfig.rb
#!/usr/bin/env ruby
require 'filegenerator'
require 'modules/helpers'
class AutoConfig
include Helpers
def initialize
end
def myFunction
myhelper #here's the module function
end
def mySecondFunction
FileGenerator.generatorFunction # call to the FileGenerator
end
end
lib/FileGenerator.rb
#!/usr/bin/env ruby
require 'modules/helpers'
class FileGenerator
include Helpers
def initialize
end
def self.generatorFunction
myhelper #here's the module function that doesn't work
end
end
lib/modules/helper.rb
#!/usr/bin/env ruby
module Helpers
def myhelper
#Do Stuff
end
end
The AutoConfig file is the main workhorse of the app. When it calls to the myhelper module function it gives me no problems what-so-ever. The AutoConfig part way through calls the FileGenerator.generatorFunction.
The FileGenerator.generatorFunction also contains this same module function, but for some reason when I run the program I get the following error:
filegenerator.rb:26:in `generatorFunction': undefined method `myhelper' for FileGenerator:Class (NoMethodError)
I've been at this now for several hours trying many different combinations and can't figure out where I'm going wrong. Any help would be appreciated.
generatorFunction is a class method. It doesn't see instance-level methods. And myhelper (brought in by include Helpers) is an instance method. To remedy that, you should extend Helpers instead. It works like include, but makes class methods.
class FileGenerator
extend Helpers
end
BTW, the name generatorFunction is not in ruby style. You should name methods in snake_case (generator_function).