How to mount a Sinatra application inside another Sinatra app? - ruby

I am trying to write a Sinatra application that groups components together (sort of like controllers). So for the "blog" related things, I want an app called Blog mounted at /blog. All of the routes contained in the Blog app would be relative to its mounted path, so I could simply define an index route without having to specify the mount path in the route.
I originally handled this by using a config.ru file and maping the routes to the different apps. The problem with this that I ran into, was that I was using various sinatra gems/extensions/helpers that needed to be included in all of the apps, so there was a lot of duplicate code.
How can I mount one sinatra app inside of another so that the routes defined in the app are relative to where the app is mounted? If this is not possible out-of-the-box, can you show a code sample of how this could be done?
Here's a simplified example of what it might look like:
class App
mount Blog, at: '/blog'
mount Foo, at: '/bar'
end
class Blog
get '/' do
# index action
end
end
class Foo
get '/' do
# index action
end
end

Take a look at https://stackoverflow.com/a/15699791/335847 which has some ideas about namespacing.
Personally, I would use the config.ru with mapped routes. If you're really in that space between "should this be a separate app or is it just helpful to organize it like this" it allows that, and then later you can still farm off one of the apps on its own without changing the code (or only a little). If you're finding that there's a lot of duplicated set up code, I'd do something like this:
# base_controller.rb
require 'sinatra/base'
require "haml"
# now come some shameless plugs for extensions I maintain :)
require "sinatra/partial"
require "sinatra/exstatic_assets"
module MyAmazingApp
class BaseController < Sinatra::Base
register Sinatra::Partial
register Sinatra::Exstatic
end
class Blog < BaseController
# this gets all the stuff already registered.
end
class Foo < BaseController
# this does too!
end
end
# config.ru
# this is just me being lazy
# it'd add in the /base_controller route too, so you
# may want to change it slightly :)
MyAmazingApp.constants.each do |const|
map "/#{const.name.downcase}" do
run const
end
end
Here's a quote from Sinatra Up and Running:
Not only settings, but every aspect of a Sinatra class will be inherited by its subclasses. This includes defined routes, all the error handlers, extensions, middleware, and so on.
It has some good examples of using this technique (and others). Since I'm in shameless plug mode I recommend it, even though I've nothing to do with it! :)

I just meet the same problems.
I found that whatever inclued extend register or rack middleware style use had problems.
You can use Ruby to solve this problems like that:
require "sinatra"
class App < Sinatra::Base
class << self
def define_routes(&block)
class_eval(&block)
end
end
end
App.define_routes do
get "/hello"
"hello world"
end
end

Related

How to split a Sinatra app with different scopes?

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.

Is there a way in Sinatra to separate helpers used in views and helpers used in routes?

All helpers in Sinatra can be accessed from both my HAML code and my routes. But I'm finding myself writing two completely separate sets of code for these two.
Ideally I'd like to keep them apart. Currently I have them in two different directories, but that seems kind of pointless. Can anyone suggest a way I can limit the potential damage caused by namespace collision?
At first I thought modules. From the Sinatra readme:
module FooUtils
def foo(name) "#{name}foo" end
end
module BarUtils
def bar(name) "#{name}bar" end
end
helpers FooUtils, BarUtils
But is there any point in actually doing this? Aren't they going to have to share the same namespace inside my Sinatra app?
Some tentative and not-entirely-satisfactory answers:
1) Methods defined in the same way as helpers but without the call to Sinatra::helpers don't appear to be accessible from views, but are accessible from within the Sinatra application block. That is:
require 'sinatra'
module Sinatra
module MyThing
# Helper classes and methods go here
end
helpers MyThing # <- leave this out if you don't want access from views
end
2) You can of course also have MyThingHelpers and MyThingLib modules, instead of just MyThing, to prevent namespace collision.
3) And you can put them in separate subdirectories.
I've yet to be convinced that these represent a comprehensive solution to my concerns. Time and testing will tell, I suppose, unless someone can give me a better answer here.

NoMethodError Sinatra Modular app

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

Loading attributes only once in Grape

I'm little new to Ruby, but I have the following coded using Grape API in Ruby. I have #data = YAML.load() called every time it hits GET /api/v1/foo, is there a way in Grape to only load it once and use it? This way is more optimized and not calling YAML.load() at every time. Should I override the initialize method and put a super() for this operation?
Thanks,
require 'grape'
require 'json'
require "yaml"
module MyProject
CONFIG_FILE = "./config.yml"
class Api < Grape::API
rescue_from :all
prefix 'api'
version 'v1'
format :json
resources :foo do
get do
#data = YAML.load(File.open(MyProject::CONFIG_FILE))
end
end
end
end
The short answer is that Grape doesn't work quite how you think, and attribute variables of the MyProject::Api are not the way forward for your new web service. However, it is an interesting question, and worth exploring why this is so.
If you add a puts self.inspect inside the resources :foo block, and run using rackup, when you call the route you should see that self is in fact a Grape::Endpoint object. Also, no matter what you try to do with instance variables, they will always start in the same state for each request. That is because Grape turns your route definitions into prepared Grape::Endpoint objects, with a lot of the definition data and setup put into a quickly-accessible form (so that it is not figured out on each request). Eventually, on each request, the matching Grape::Endpoint object including your block (and other details you defined for the route) is duplicated before being called, meaning that state is not maintained between requests.
This may seem complicated, but most frameworks covering web service requests will do something similar. Generally you don't want request-handling state to persist between requests. Frameworks with larger scope - e.g. Rails - have places to put more persistent data planned out for you. Grape does not have this defined, which has its pros and cons. One obvious plus point is that you are more free to use whatever other data persistence approach that you wish to.
23tux's answer will sort you out immediately for loading config. Although I'm not entirely sure how ##data becomes accessible to the endpoint block (it may even be creating a closure around the variable).
Longer term, you should look to moving config management out of your MyProject::Api class, and including it as a module via Grape's helpers method (I'm happy to provide an example if you are interested).
Edit: Example based on your current code, but moving config management to a separate module:
require 'grape'
require 'json'
require "yaml"
module MyProject
module Config
CONFIG_FILE = "./config.yml"
##data = nil
def config
##data ||= YAML.load( File.open( CONFIG_FILE ) )
end
end
class Api < Grape::API
rescue_from :all
prefix 'api'
version 'v1'
format :json
helpers MyProject::Config
resources :foo do
get do
config
end
end
end
end
This is one step further, structurally, than 23tux's answer, but is still not completely separating concerns of storage (and caching etc) versus api access. As you progress towards a more sophisticated web service, you will want to keep the Grape route definitions simple, with only a small amount of logic that manages or manipulates the data - well, at least as seen directly in the blocks.
One way to link between your Grape definitions, and other gems that might manage config, logging, authentication and other services, is via Grape's helpers method. Grape also has some built-in helper methods for common tasks.
The main exception to using helpers MyModule to add shared functions into Grape is when you want to manage displaying data objects (aka "models") from your core application. For that you have a few choices, but the grape-entity gem and the present method is not a bad place to start.
If the #data is the same for the whole api, and doesn't change at any time, just use a class variable
require 'grape'
require 'json'
require "yaml"
module MyProject
CONFIG_FILE = "./config.yml"
class Api < Grape::API
##data = YAML.load(File.open(MyProject::CONFIG_FILE))
rescue_from :all
prefix 'api'
version 'v1'
format :json
resources :foo do
get do
puts ##data
end
end
end
end
Not tested, but with this way, you ensure that the data is only loaded once, when your Api class is loaded

How to skip before filters in Rails Forem Gem

I would like to allow anonymous access to my forums. I am using the Forem Gem and the Devise Gem. I have a before_filter in my ApplicationController that ensures users are logged in.
before_filter :authenticate_user!
In my own controllers I use skip_filter to allow anonymous access to actions but I do not know how to do this for the controllers in the Forem Gem.
I could move the before_filter statement from my ApplicationController into each of my own Controllers but I would rather not.
Thanks for the answer Ryan, Just like to point out that the file should be:
forem/application_controller_decorator.rb otherwise it won't load with that initialiser code (note: underscore - not dash).
And to re-iterate, the initialiser code should be :
Rails.application.config.to_prepare do
Dir.glob(Rails.root.join("app/**/*_decorator*.rb")) do |c|
Rails.configuration.cache_classes ? require(c) : load(c)
end
end
So that it will reload on every request in development mode.
Hopefully that will save someone a few hours!
The best place for this would be a file inside your app/controllers directory at forem/application_controller-decorator.rb that contains this content:
Forem::ApplicationController.class_eval do
skip_before_filter :authenticate_member!
end
You would then need to include this file using lines like this in a file called config/initiailizers/load_decorators.rb:
Dir.glob(Rails.root.join("app/**/*_decorator*.rb")) do |c|
Rails.configuration.cache_classes ? require(c) : load(c)
end
This would allow you to specify other decorators as well, for any other class of Forem, if you so desired.
I stole this idea from Spree (which I work on full-time now), and I think it's pretty solid. You can see the original implementation of it here.

Resources