Deploying Sinatra app on Dreamhost/Passenger with custom gems - ruby

I've got a Sinatra app that I'm trying to run on Dreamhost that makes use of pony to send email. In order to get the application up and running at the very beginning (before adding pony), I had to gem unpack rack and gem unpack sinatra into the vendor/ directory, so this was my config.ru:
require 'vendor/rack/lib/rack'
require 'vendor/sinatra/lib/sinatra'
set :run, false
set :environment, :production
set :views, "views"
require 'public/myapp.rb'
run Sinatra::Application
I have already done gem install pony and gem unpack pony (into vendor/). Afterwards, I tried adding require 'vendor/sinatra/lib/pony' to config.ru only to have Passenger complain about pony's dependencies (mime-types, tmail) not being found either!
There has to be a better way to use other gems and tone down those long, ugly, redundant requires. Any thoughts?

I'd recommend creating your own gem path "somewhere" then adding it in your config.ru
like:
ENV['GEM_PATH'] = xxx
Gem.clear_paths
then install your gems into that

Install Ruby gems on dreamhost
https://c.kat.pe/installing-ruby-gems-on-dreamhost
Change config.ru (works for Sinatra 1.0)
require 'rubygems'
require 'vendor/sinatra/lib/sinatra.rb'
ENV['GEM_HOME'] = '/home/username/.gems'
ENV['GEM_PATH'] = '$GEM_HOME:/usr/lib/ruby/gems/1.8'
require 'rubygems'
Gem.clear_paths
disable :run, :reload
set :environment, :production
require 'yourapp'
run Sinatra::Application
Hope it helps someone.
I am using pony and a lot of other gems for my Sinatra. It should work well for you too. It's just those two lines (GEM_HOME and GEM_PATH) you have to add on your config.

It took me ages to find that you can simply use "gem install sinatra" and gem will figure out (because the system directories are read-only) that you will need to use a local gem install directory. As of now, there seems to be no need to set any special environment at all. It figures out to use $HOME/.gem as the local gem path and everything just works. No need for require 'vendor/stuff' at all. I did find I had to add $HOME/.gem/ruby/1.8/bin to my path in order to execute binaries installed by gems.
Here's my config.ru (for Dreamhost)
## Passenger should set RACK_ENV for Sinatra
require 'test'
set :environment, :development
run Sinatra::Application
Later edit: This is all well and fine, but there remains the issue that Passenger can't find my gems when the job initially starts up.

My config.ru is just simple as:
require 'rubygems'
require 'vendor/sinatra/lib/sinatra.rb'
require 'app.rb'
and app.rb head:
require 'yaml'
require 'haml'
require 'ostruct'
require 'date'
require 'pp'
module FlytoFB
log = File.new("sinatra.log", "a")
STDOUT.reopen(log)
STDERR.reopen(log)
configure do
enable :logging, :dump_errors
set :app_file, __FILE__
set :reload, true
set :root, File.dirname(__FILE__)
set :environment, :production
set :env, :production
set :run, false
set :raise_errors, true
set :public, 'public'
error do
e = request.env['sinatra.error']
puts e.to_s
puts e.backtrace.join("\n")
"Application Error!"
end
not_found do
"Page not found!"
end

Related

Sinatra app has session issues in production

I've been working on a web application in Ruby using Sinatra. While in development, I really never had a lot of issues with sessions. However, now i'm ussing passenger to actually deploy the application I have quite a lot of issues regarding that session data keeps getting 'reset'.
I've seen other stack overflow questions related to this problem but no answer has yet fixed it for me. I've tried a couple of things:
Putting 'use Rack::Session::Pool' inside config.ru as suggested here
Used Rack::Session::Pool like explained here
Tried Memcache as explained here
Tried Redis-Rack (gives me an error: Exception NoMethodError in Rack application object (undefined method `foreign_key' for nil:NilClass))
Nothing seems to be helping really... I either end up having an error or my session gets reset each request. I know that passenger probably uses multiple threads and that that is the cause for the sessions to not be working, but I cannot seem to find a solution to the problem.
Am I missing something obvious here? Any suggestions?
Gemfile
source "https://rubygems.org"
gem 'mongo', '1.8.6'
gem 'sinatra', '1.4.8'
gem 'mongo_mapper'
gem 'bson_ext'
gem 'active_model_serializers'
gem 'activemodel-serializers-xml'
gem 'sinatra-flash'
gem 'sinatra-param', require: 'sinatra/param'
gem 'rack-recaptcha2', git: 'https://github.com/nicolas-simplex/rack- recaptcha'
gem 'mail'
gem 'slugify'
gem 'biz'
gem 'bcrypt'
gem 'redis-rack'
group :development do
gem 'mailcatcher', '~> 0.6.4'
end
config.ru
It had different forms depending on if I used just Rack::Session::Pool, Memcache or Redis. This one was the one I used for redis:
require 'rubygems'
require 'sinatra'
require File.expand_path '../app.rb', __FILE__
require 'rack'
require 'rack/session/redis'
require_relative './app'
app = App.new
sessioned = use Rack::Session::Redis.new(app)
run sessioned
App.rb
require 'rubygems'
require 'bundler/setup'
require 'sinatra'
require 'slugify'
class App < Sinatra::Base
use Rack::Protection
set :bind, '0.0.0.0'
end
require_relative './config/init' # Initialize configuration files
require_relative './helpers/init' # Initialize helpers
require_relative './routes/init' # Initialize routes
require_relative './models/init' # Initialize models
require_relative './util/tokens' # Token utility
... # Some database seeding, basic setup for some data I use
TL;DR
Sinatra and Passenger together resets sessions in production, while this doesn't happen in development.
After some more researching I found the configuration option passenger_sticky_sessions. Because in my web application session storage is quite important, this works fine for me. However, the use of this is not recommended in all use cases because all the clients sends will be routed to the same originating application process.
Documentation: https://www.phusionpassenger.com/library/config/nginx/reference/#passenger_sticky_sessions
With this configuration parameter 'on' in my conf file in nginx/sites-enabled, I was able to just use the simple rack session pool:
use Rack::Session::Pool, path: '/', expire_after: 2592000

Can't get request when config routes.rb file with Sinatra

My app structure like this
Gemfile
app.rb
config.ru
lib/routes.rb
# app.rb
require 'sinatra'
class Todo < Sinatra::Base
set :environment, ENV['RACK_ENV']
Dir[File.join(File.dirname(__FILE__), 'lib', '*.rb')].each {|lib| load lib}
end
#config.ru
require 'sinatra'
require 'bundler/setup'
Bundler.require
ENV['RACK_ENV'] = 'development'
require File.join(File.dirname(__FILE__), 'app.rb')
Todo.start!
#lib/routes.rb
get '/' do
"Hello world"
end
When I run with ruby config.rb then locate to localhost:4567, it doesn't recognize the route /. But if I move the code get '/' do into class Todo it works.
Anyone can explain that for me?
If you have a config.ru file, that means that the application has to be invoked by the rackup utility and not ruby config.ru. The reason for this is that rackup sets a lot of settings before invoking the code inside config.ru. Rackup utility is part of Rack gem that Sinatra builds on, so it will be present if you install Sinatra.
Although not a problem for the example you provided, in a modular style application, you need to require sinatra/base rather than just sinatra.

Why is bundler loading all my gems instead of specific ones

I have a Sinatra application and I'm trying to use groups in my Gemfile in order to load only specified gems. But, when I restrict loading to just one group bundler still loads every gem in the file. Here's my Gemfile:
source 'https://rubygems.org'
group :one do
gem 'sinatra'
end
group :two do
gem 'bitly'
end
And here's my application:
require 'bundler/setup'
Bundler.require(:one)
class App < Sinatra::Base
configure do
puts Gem.loaded_specs.keys.sort.join("\t")
end
get '/foo' do
end
end
And I can clearly see the Bitly gem loaded when the application starts. What am I doing wrong?
Use
require 'bundler'
instead of
require 'bundler/setup'
The last one automatically loads all gems in Gemfile: Why do you need "require 'bundler/setup'"?

"require if" in Ruby

Here is what I have in my rack app
#rb file
require 'pry'
class .....
#GemFile
group :development do
gem "pry"
gem "pry-nav"
end
Of course, in production it causes an error. How do make a kind of "require if"?
require 'pry' if ENV['RACK_ENV'] == 'development'
May be you can embed it inside a if block
according to docs Sinatra provides a environment variable
http://www.sinatrarb.com/intro#Environments
if development?
require 'pry'
end
wherever you need to use it.
this may not be the exact solution you may be looking for just a wild guess
I suggest to write such method in Object or Kernel in your app:
def require_pry
require 'pry' if ENV['RACK_ENV'] == 'development'
end
After that you can call require_pry if you need it in your code. But I have doubts why it cannot be handled by Bundler, Bundle.require will require all gems needed for environment.

Sinatra + Bundler?

I'm wondering how one can use Bundler with Sinatra. The idea is to use the gems that Bundler downloads inside the .gems folder.
Inside your Sinatra app, you just have to require the bundler setup:
require "bundler/setup"
require "sinatra"
get "/" do
"Hello world!"
end
Alternatively, if you don't want to add the additional require "bundler/setup" at the top of your app, you can instead invoke sinatra via bundle exec (e.g. bundle exec ruby myapp.rb)
This assumes that you have a Gemfile in the root of your application. It might look like this:
source "http://rubygems.org"
gem "sinatra"
This also assumes that you've already installed bundler (gem install bundler) and that you ran bundle install to install all the gem dependencies.
I believe the best way is described here on EngineYard blog:
# This makes sure the bundled gems are in our $LOAD_PATH
require File.expand_path(File.join(File.dirname(__FILE__), 'vendor', 'gems', 'environment'))
# This actually requires the bundled gems
Bundler.require_env
class MyApp < Sinatra::Base
# stuff
end
As my original answer was quite old but there seems to be still attention to this topic here's the latest version of bundler/sinatra setup which will cover most of the use case:
A minimal config.ru
require './my_sinatra_app'
run MySinatraApp
An environment env.rb file that requires all the bundled gems (also supports loading the current environment's group):
require 'bundler/setup'
APP_ENV = ENV["RACK_ENV"] || "development"
Bundler.require :default, APP_ENV.to_sym
Then your app file (requiring the environment) with your sinatra app (Sinatra::Base):
require_relative 'env'
class MyApp < Sinatra::Base
get "/" do
"hello world"
end
end
Start your development server with rackup, and Sinatra will be loaded via Bundler, your app will be accessible from http://localhost:9292.
$ rackup
or bundle exec rackup if needed
Make sure you have a Gemfile like the following one and you run the bundle command before starting the app
source "https://rubygems.org"
gem "sinatra"
gem "puma" # a better rack server than the default webrick
+1 for the guide on the bundler website, but if you have a simple app and use Sinatra's dsl at the top level, then you need to do the following:
in your Gemfile (tell bundler not require sinatra):
gem 'sinatra', :require => false
and in the app's file (explicitly require sinatra):
require 'rubygems'
require 'bundler'
Bundler.require
require 'sinatra'
get '/' do
'hello world'
end
To use bundler with a Sinatra application, you only need to do two things. First, create a Gemfile.
gem 'sinatra'
Then, set up your config.ru file to load the bundle before it loads your Sinatra app.
require 'rubygems'
require 'bundler'
Bundler.require
require './my_sinatra_app'
run MySinatraApp
Start your development server with rackup, and Sinatra will be loaded via Bundler.
rackup
source bundler docs

Resources