How does Module change the scope? - ruby

My understanding of scope
I have been a Javascript developer for a long time and I am used to this...
var globalVar = "some value";
getGlobalVar = function(){
alert(globalVar);
}
getGlobalVar();
In Javascript everything defined outside of a function is attached to the window object and becomes part of the global scope. Anything that is part of the global scope is accessible within any function - at least as far as I'm aware. I am trying to do something similar in Ruby, but I am getting an error.
The problem
Here is how I have the code working:
# create CoreController
SINATRA = Sinatra
module Apollo
class Sinatra < SINATRA::Base
configure :development do
register SINATRA::Reloader
also_reload "app/**/*.rb"
dont_reload "lib/**/*.rb"
end
include CoreHelpers
include Helpers
# ----- Config ------
configure do
set :public_folder, Apollo.config[:sinatra][:public]
set :views, Apollo.config[:sinatra][:views]
set :static_cache_control, [:public, {:max_age => 600}]
enable :sessions
# Set the session secret
set :session_secret, "secret"
end
end
end
I added SINATRA = Sinatra as a hack, because I could not access Sinatra::Reloader inside of class Sinatra < SINATRA::Base.
This is what I would like the code to look like:
# create CoreController
module Apollo
class Sinatra < Sinatra::Base
configure :development do
register Sinatra::Reloader
also_reload "app/**/*.rb"
dont_reload "lib/**/*.rb"
end
include CoreHelpers
include Helpers
# ----- Config ------
configure do
set :public_folder, Apollo.config[:sinatra][:public]
set :views, Apollo.config[:sinatra][:views]
set :static_cache_control, [:public, {:max_age => 600}]
enable :sessions
# Set the session secret
set :session_secret, "secret"
end
end
end
Here is the error I'm getting:
How does putting this code inside of a class change the scope? Shouldn't global variables be accessible anywhere? Any and all help is appreciated.

I am not sure if I get all the technicalities right, but when you have class Sinatra and you use Sinatra::Something inside the class block, ruby interprets it as "he wants to access the constant Something in this class".
If you change the class name to something other than Sinatra, you should be fine. Or you can use the double colon as a prefix, like this
# create CoreController
module Apollo
class Sinatra < ::Sinatra::Base
configure :development do
register ::Sinatra::Reloader
also_reload "app/**/*.rb"
dont_reload "lib/**/*.rb"
end
include CoreHelpers
include Helpers
# ----- Config ------
configure do
set :public_folder, Apollo.config[:sinatra][:public]
set :views, Apollo.config[:sinatra][:views]
set :static_cache_control, [:public, {:max_age => 600}]
enable :sessions
# Set the session secret
set :session_secret, "secret"
end
end
end
It basically says ruby to look outside of the class Sinatra for another Sinatras.

Related

Can I create global variables that work accross routes in Sinatra (Ruby)?

I'm trying to make a ruby class manage most of what's going on in my application, and I intend to manage its params through the erb with embeded Ruby Code. I picture it goes something like this, but it's obviously not working:
require 'sinatra'
require './models/questionaire_manager'
set :bind, '0.0.0.0'
set :port, ENV['PORT']
enable :sessions
set :session_secret, 'SecretString#!$%'
get '/' do
#questionaire=Questionaire_Manager.new 0
erb :index
end
post '/' do
session[:number]=params[:number]
redirect '/quiz'
end
get '/quiz' do
#questionaire.number=session[:number]
#questionaire.genQuestionaire
erb :quiz
end
post '/quiz' do
redirect'/results'
end
get '/results' do
#number=session[:number]
erb :results
end
I guess I should also say I can't get the hang of sessions and session params, and since Sinatra's page has been down for almost a week now, I really cannot check it out.
Try something like this maybe?
require 'sinatra'
require './models/questionaire_manager'
set :bind, '0.0.0.0'
set :port, ENV['PORT']
enable :sessions
set :session_secret, 'SecretString#!$%'
helpers do
def quiz_manager
#questionaire = session[:quiz_manager] ||= Questionaire_Manager.new 0
end
end
get '/' do
# Uncomment the line below if you intend to create a new quiz each time
# session[:quiz_manager] = nil
quiz_manager # Initializes the session variable
erb :index
end
post '/' do
quiz_manager.number = params[:number]
redirect '/quiz'
end
get '/quiz' do
quiz_manager.genQuestionaire
erb :quiz
end
post '/quiz' do
redirect '/results'
end
get '/results' do
#number = quiz_manager.number
erb :results
end
Edit:
To clarify what this is doing -- I've created a helper method called quiz_manager that initializes session[:quiz_manager] if it hasn't already been set - This will persist between routes. I'm also setting the class variable #questionnaire so that you can access it within your views.

How to map routes to modules without the use of multiple Sinatra apps?

I have this structure:
module Analytics
def self.registered(app)
module DepartmentLevel
departmentParticipation = lambda do
end
departmentStatistics = lambda do
end
app.get '/participation', &departmentParticipation
end
module CourseLevel
courseParticipation = lambda do
end
end
end
And at the end of the module Analytics I would like to route each piece of the request to his specific subModule. If it is requested
'analytics/department'
it should redirect to the module DepartmentLevel which has its own routes as
app.get 'participation', &departmentParticipation
I first thought on using map. But how to use it without having to run a new or inherit Sinatra::Base object?
Not sure if this is what you need, but here's how I build my modular Sinatra apps: By using use
First, I have my ApplicationController. It's the base class for all other Controllers. It lives in controllers/application_controller.rb
class ApplicationController < Sinatra::Base
# some settings that are valid for all controllers
set :views, File.expand_path('../../views', __FILE__)
set :public_folder, File.expand_path('../../public', __FILE__)
enable :sessions
# Helpers
helpers BootstrapHelpers
helpers ApplicationHelpers
helpers DatabaseHelpers
configure :production do
enable :logging
end
end
Now, all other Controllers/Modules inherit from ApplicationController. Example controllers/website_controller.rb:
require 'controllers/application_controller'
class WebsiteController < ApplicationController
helpers WebsiteHelpers
get('/') { slim :home }
get('/updates') { slim :website_updates }
get('/test') { binding.pry; 'foo' } if settings.development?
end
At last, in app.rb is where it all comes together:
# Require some stuff
require 'yaml'
require 'bundler'
Bundler.require
require 'logger'
# Require own stuff
APP_ROOT = File.expand_path('..', __FILE__)
$LOAD_PATH.unshift APP_ROOT
require 'lib/core_ext/string'
require 'controllers/application_controller.rb'
# Some Run-Time configuration...
ApplicationController.configure do
# DB Connections, Logging and Stuff like that
end
# Require ALL models, controllers and helpers
Dir.glob("#{APP_ROOT}/{helpers,models,controllers}/*.rb").each { |file| require file }
# here I glue everything together
class MyApp < Sinatra::Base
use WebsiteController
use OtherController
use ThingController
not_found do
slim :'404'
end
end
With this Setup, all I need to do in config.ru is
require './app.rb'
run MyApp
Hope this helps!

How to request separate folder view path based on controller name in Sinatra?

Here's the contents of my app/controllers/application_controller.rb:
require 'sinatra/base'
require 'slim'
require 'colorize'
class ApplicationController < Sinatra::Base
# Global helpers
helpers ApplicationHelper
# Set folders for template to
set :root, File.expand_path(File.join(File.dirname(__FILE__), '../'))
puts root.green
set :sessions,
:httponly => true,
:secure => production?,
:expire_after => 31557600, # 1 year
:secret => ENV['SESSION_SECRET'] || 'keyboardcat',
:views => File.expand_path(File.expand_path('../../views/', __FILE__)),
:layout_engine => :slim
enable :method_override
# No logging in testing
configure :production, :development do
enable :logging
end
# Global not found??
not_found do
title 'Not Found!'
slim :not_found
end
end
As you can see I'm setting the views directory as:
File.expand_path(File.expand_path('../../views/', __FILE__))
which works out to be /Users/vladdy/Desktop/sinatra/app/views
In configure.ru, I then map('/') { RootController }, and in said controller I render a view with slim :whatever
Problem is, all the views from all the controllers are all in the same spot! How do I add a folder structure to Sinatra views?
If I understand your question correctly, you want to override #find_template.
I stick this function in a helper called view_directory_helper.rb.
helpers do
def find_template(views, name, engine, &block)
views.each { |v| super(v, name, engine, &block) }
end
end
and when setting your view directory, pass in an array instead, like so:
set :views, ['views/layouts', 'views/pages', 'views/partials']
Which would let you have a folder structure like
app
-views
-layouts
-pages
-partials
-controllers
I was faced with same task. I have little experience of programming in Ruby, but for a long time been working with PHP. I think it would be easier to do on it, where you can easily get the child from the parent class. There are some difficulties. As I understand, the language provides callback functions like self.innereted for solving of this problem. But it did not help, because I was not able to determine the particular router in a given time. Maybe the environment variables can help with this. But I was able to find a workaround way to solve this problem, by parsing call stack for geting caller class and wrapping output function. I do not think this is the most elegant way to solve the problem. But I was able to realize it.
class Base < Sinatra::Application
configure do
set :views, 'app/views/'
set :root, File.expand_path('../../../', __FILE__)
end
def display(template, *args)
erb File.join(current_dir, template.to_s).to_sym, *args
end
def current_dir
caller_class.downcase!.split('::').last
end
private
def caller_class(depth = 1)
/<class:([\w]*)>/.match(parse_caller(caller(depth + 1)[1]))[1]
end
def parse_caller(at)
Regexp.last_match[3] if /^(.+?):(\d+)(?::in `(.*)')?/ =~ at
end
end
The last function is taken from here. It can be used as well as default erb function:
class Posts < Base
get '/posts' do
display :index , locals: { variables: {} }
end
end
I hope it will be useful to someone.

Accessing global Sinatra object

I am trying to write a helper class in my Sinatra application. I'm trying to access Sinatra properties somehow but always getting nil. My code is as follows:
app.rb:
configure do
enable :sessions
set :session_secret, "STHSTHSTH"
# DATABASE_URL is provided by environment, or can be set on the command line
# For instance: DATABASE_URL=mysql://localhost/freecoins rackup
# will run the app with the database at localhost/freecoins.
DataMapper.setup(:default, ENV['DATABASE_URL'])
# These set it up to automatically create/change tables when
# their models are updated.
DataMapper.auto_migrate!
DataMapper.auto_upgrade!
# Here we read in the config file and parse the JSON from it.
config = JSON.parse(File.read("config/config.json"))
# Then we loop through each element in the JSON object and
# assign it to Sinatra's settings.
# They are accessed via settings.key anywhere in the app,
# especially in some of the routes.
config.each do |k, v|
set k.to_sym, v
end
end
set :views, 'views'
Dir["routes/*.rb"].each {|file| require_relative file }
Dir["models/*.rb"].each {|file| require_relative file }
Dir["helpers/*.rb"].each {|file| require_relative file }
# This has to be called once all the models have been defined.
DataMapper.finalize
helper class:
class WalletHelper
#currency = nil
#client = nil
def initialize(currency)
puts $settings #settings is nil here
end
end
How can I access properties of the app, settings for example, as I do in get blocks?
You should try Sinatra::Base.settings
you may want to see Sinatra.register and Sinatra.helpers
see http://www.sinatrarb.com/extensions.html for more details

Architecture for a modular, component-based Sinatra Application

I'm working on a Sinatra app that contains about 10 different components of functionality. We'd like to be able to mix and match these components into separate instances of the application, configured entirely from a config.yaml file that looks something like:
components:
- route: '/chunky'
component_type: FoodLister
component_settings:
food_type: bacon
max_items: 400
- route: 'places/paris'
component_type: Mapper
component_settings:
latitude: 48.85387273165654
longitude: 2.340087890625
- route: 'places/losangeles'
component_type: Mapper
component_settings:
latitude: 34.043556504127466
longitude: -118.23486328125
As you can see, components can be instantiated more than once, each with their own contextual settings.
Each component consists of at least one route, with the "route" property from the config file used for the base.
What is the best way to organize and instantiate the module code?
This is similar to include's proposal, but it doesn't require access to the rackup file.
Write your various Handlers like:
class FoodHandler < Sinatra::Base
get '/chunky/:food' do
"Chunky #{params[:food]}!"
end
end
Then in your main application file:
require './lib/handlers/food_handler.rb'
class Main < Sinatra::Base
enable :sessions
... bla bla bla
use FoodHandler
end
I've used this kind of structure to build some fairly complex Sinatra apps. It scales just as well as Rails.
EDIT
To have your config file define the routes, you could do something like this:
class PlacesHandler < Sinatra::Base
# Given your example, this would define 'places/paris' and 'places/losangeles'
CONFIG['components'].select { |c| c['compontent_type'] == 'Mapper' }.each do |c|
get c['route'] do
#latitude = c['component_settings']['latitude']
#longitude = c['component_settings']['longitude']
end
end
end
TIMTOWTDI - There's_more_than_one_way_to_do_it :) and that is one. But in fact I use another way. I use Sinatra/Base to dev modular apps.
I have simgle routes to each app.
# config.ru file
require 'bundler/setup' Bundler.require(:default)
require File.dirname(__FILE__) + "/main.rb"
map "/" { run BONES::Main }
map "/dashboard" { run BONES::Dashboard }
map "/app1" { run BONES::App1 }
You can have variable sets for each instance.
You can develop each 'component' on its Module.
require File.dirname(__FILE__) + "/lib/helpers.rb"
module BONES
class Main < Sinatra::Base
helpers XIXA::Helpers
configure :development do
enable :sessions, :clean_trace, :inline_templates
disable :logging, :dump_errors
set :static, true
set :public, 'public'
end
enable :static, :session
set :root, File.dirname(__FILE__)
set :custom_option, 'hello'
set :haml, { :format => :html5 }
#...
That a look here. http://codex.heroku.com/
have fun :)

Resources