Why doesn't the sinatra-redirect-with-flash gem work with shotgun? - ruby

I want to show flash messages using sinatra-redirect-with-flash gem.
Here's my ruby code:
require 'sinatra'
require 'sinatra/base'
require 'sinatra/flash'
require 'sinatra/redirect_with_flash'
require 'data_mapper'
require 'haml'
require 'builder'
# ...
class App < Sinatra::Base
enable :sessions
register Sinatra::Flash
helpers Sinatra::RedirectWithFlash
use Rack::MethodOverride
get '/' do
#notes = Note.all :order => :id.desc
#title = 'All TODOs'
if #notes.empty?
flash.now[:error] = 'No TODOs found. Add your first below.'
end
haml :home
end
post '/' do
n = Note.new
n.content = params[:content]
n.created_at = Time.now
n.updated_at = Time.now
if n.save
redirect '/', :notice => 'TODO saved successfully.'
else
redirect '/', :error => 'Failed to save TODO.'
end
end
# ...
end
And views/layout.haml is:
!!! 5
%html{:lang => "en"}
%head
%meta{:charset => "utf8"}
%body
%header
%hgroup
%h1
%a{:href => "/"}= SITE_TITLE
%h2= SITE_DESCRIPTION
#main
=styled_flash
=yield
After adding a TODO successfully, I expected to see the flash message 'TODO saved successfully.' on the home page. But no flash messages are shown after redirection when I run my app using shotgun. Flash messages are shown well when I run ruby app.rb or rackup.
How can I solve this problem?
Another problem is also happening when I run the app using shotgun. In get '/' method, if I use flash[:error] instead of flash.now[:error], the flash message doesn't show up on the page.
I am shadowning this tutorial, but I made some differences:
erb -> haml
Classic Sinatra app -> Subclassing Sinatra::Base
rack-flash -> sinatra-flash
You can browse whole codes here.
Thanks for any answers/comments.

The shotgun gem reloads Sinatra after every request. The README says:
Each time a request is received, it forks, loads the application in
the child process, processes the request, and exits the child process. The
result is clean, application-wide reloading of all source files and templates on
each request.
As a result, you will need some sort of mechanism to preserve state between requests that doesn't rely on data stored in each child process.

Related

How to custom layout directory in sinatra

I have a Sinatra app with multiple layouts, one for admin panel, and one for public. I want to isolate them into their own subdirectory in views:
views/
views/layout.erb (for public)
views/auth/
views/auth/layout.erb (for admin)
but I got error. config.ru :
require 'sinatra'
get "/" do
erb :layout
end
get "/auth" do
erb :layout => :'auth/layout'
end
First, the second "erb" call is wrong. Change it to:
erb :layout, :layout_options => { :views => 'views/auth' }
If you want use Sinatra's "Classic Style", the code you showed should not be in config.ru. Move the code from config.ru to another file, e.g. app.rb. Correcting the second "erb" call, app.rb will contain:
# app.rb
require 'sinatra'
get "/" do
erb :layout
end
get "/auth" do
erb :layout, :layout_options => { :views => 'views/auth' }
end
Run it like this: ruby app.rb. It will by default start a local server on port 4567.
Now the URLs http://localhost:4567 and http://localhost:4567/auth should work.
Remember, you should not render a layout.erb file directly. It is used to wrap your views. Check Sinatra: Getting Started for more examples.

Facebook Graph API for websites using Ruby Koala gem in Sinatra

I want to implement Facebook login for web apps. All I need is the basic public information of a user for the account creation steps.
This is what I have done:
Created a basic Facebook app with nearly no custom permissions.
Used the APP_ID and APP_SECRET in Koala to get access_token.
Everything worked perfectly, I am able to login/logout.
Just that the only information I am able to get back when I do: graph.get_object('me') is the logged in user's name and an id (It doesn't look like the default Facebook id).
Surprised whether something changed in the new API, I tested the gem in the console using the access_token from graph explorer (where all permissions are enabled by default). And I get all data using the same method call.
When I review what all the app gets while signing up; I see that the user's basic information, profile pic and other public data will be accessible to the app.
Any idea why this is so? It seems I am missing something obvious. The code is available in Github. But this is pretty much everything to it:
require 'bundler'
Bundler.require :default
Dotenv.load '.env'
require_relative './app/constants.rb'
module Banana
class App < Sinatra::Base
use Rack::Session::Cookie, secret: COOKIE_SECRET
set :public_folder, File.dirname(__FILE__) + '/bower_components'
get '/' do
if logged_in?
haml :welcome_in, layout: :layout
else
haml :log_in, layout: :layout
end
end
get '/log_out' do
session['oauth'] = nil
session['access_token'] = nil
redirect '/'
end
get '/log_in' do
session['oauth'] = Koala::Facebook::OAuth.new(APP_ID, APP_SECRET, "#{request.base_url}/call_back")
redirect session['oauth'].url_for_oauth_code()
end
get '/call_back' do
begin
session['access_token'] = session['oauth'].get_access_token(params[:code])
rescue
redirect '/?error=user_denied'
end
redirect '/'
end
get '/test' do
if logged_in?
p graph.get_object("rakeshbs")
"e"
else
redirect '/'
end
end
def logged_in?
!session['access_token'].nil?
end
def toggle_access
logged_in? ? '/log_out' : '/log_in'
end
def graph
#graph ||= Koala::Facebook::API.new(session['access_token'])
end
def errored?
!params["error"].nil?
end
def user
p graph.get_connections(:me, :photos) # This is just nil
#user ||= OpenStruct.new(
name: graph.get_object("me")["name"], # All I get here is just a hash with the name and an id!
photo: 'http://semantic-ui.com/images/avatar/small/elliot.jpg'
)
end
end
end
You should add fields parameter.
Something like this:
graph.get_object('me', { fields: 'id,first_name,last_name,gender,birthday,photos,email' })

Run controller as Ruby script?

In my application I setup a news controller to handle news feeds using Nokogiri:
class NewsController < InheritedResources::Base
def new
require 'nokogiri'
require 'open-uri'
doc = Nokogiri::XML(open("http://www.themusicvoid.com/feed"))
#info = doc.xpath('//item').take(5).map do |i|
News.create(:title => i.xpath('title').inner_text,
:description => i.xpath('description').inner_text,
:link => i.xpath('link').inner_text,
:image => i.xpath('image').inner_text)
end
end
end
This functions by running <%= debug #info %> in the new.html.erb file. It searches the RSS feed and automatically saves to my database.
The problem is that it would do this whenever ANYBODY loaded new.html.erb (although I could add that file to my robots.txt so at least bots wouldn't trigger it.)
What I actually want is some sort of a Ruby script or Rake task that would do the exact same thing without loading a page. If I just run the code above as a script, it works, it would output the data if I wanted to echo it, but it does not save the information to the database. Any ideas on how I might do that?
You can create a rake task under lib/tasks directory:
rss_fetcher.rake
require 'nokogiri'
require 'open-uri'
namespace :rss do
desc "Fetch rss feed"
task :fetch => :environment do
doc = Nokogiri::XML(open("http://www.themusicvoid.com/feed"))
#info = doc.xpath('//item').take(5).map do |i|
News.create(:title => i.xpath('title').inner_text, :description => i.xpath('description').inner_text, :link => i.xpath('link').inner_text, :image => i.xpath('image').inner_text)
end
end
end
then somewhere call rake 'rss:fetch'
I highly recommend the whenever gem to do that kind of job
gem install whenever
config/schedule.rb
every 2.hours do
rake "rss:fetch"
end

Unable to use Warden in Sinatra App: env['warden'] returns nil

I'm writing a Sinatra Rack App and I want to use Warden for authentication. I'm using heroku's toolbelt so I use foreman to run my app. I've found some code that's presumably supposed to get this working. Unfortunately, when I attempt to actually access the Warden env object, it is nil.
I've attempted to use the sinatra_warden gem, but it also has its own bugs (might be related to this one).
config.ru:
require './web.rb'
use Rack::Static, :urls => ["/css", "/img", "/js"], :root => "public"
run MyApp
web.rb:
require 'sinatra'
require 'warden'
require 'data_mapper'
require './config/datamapper.rb'
require './config/warden.rb' # I've tried this inside of MyApp, still didn't work
class MyApp < Sinatra::Base
get '/test' do
env['warden'].authenticate! # env['warden'] is nil :(
end
end
config/warden.rb:
use Rack::Session::Cookie, :secret => ENV['SESSION_SECRET']
use Warden::Manager do |manager|
manager.default_strategies :password
manager.failure_app = MyApp.new
end
Warden::Manager.serialize_into_session { |user| user.id }
Warden::Manager.serialize_from_session { |id| User.get(id) }
Warden::Manager.before_failure do |env,opts|
# Sinatra is very sensitive to the request method
# since authentication could fail on any type of method, we need
# to set it for the failure app so it is routed to the correct block
env['REQUEST_METHOD'] = "POST"
end
Warden::Strategies.add(:password) do
def valid?
params["email"] || params["password"]
end
def authenticate!
u = User.authenticate(params["email"], params["password"])
u.nil? ? fail!("Could not log in") : success!(u)
end
end
Versions:
Sinatra: 1.1.0
Warden: 1.2.1
Rack: 1.4.1
Ruby: 1.9.3p194
Foreman: 0.60.0
Any ideas how to use Warden the set up I've described?
(P.S. Out of curiosity, what exactly is the env variable?)
Rack internally uses the class Rack::Builder to parse your config.ru file and wrap directives to build up the middleware components.
I believe your builder calls to use in config/warden.rb are getting ignored. It may work to remove the directives from that file and add them to the middleware stack in config.ru:
require './web.rb'
use Rack::Session::Cookie, :secret => ENV['SESSION_SECRET']
use Warden::Manager do |manager|
manager.default_strategies :password
manager.failure_app = MyApp.new
end
use Rack::Static, :urls => ["/css", "/img", "/js"], :root => "public"
run MyApp
Put a link to your config/warden in your config.ru
require File.dirname(__FILE__) + '/config/warden'
Read the warden readme. Or look right in the lib/warden.rb
I put
Warden.test_mode!
in place of the env call at the /test path and get a nice blank page at
http://localhost:9292/test
Some bloggers have stated that there isn't a lot of documentation for warden but I disagree. There is a whole wiki. see https://github.com/hassox/warden/wiki
Take it slow and find out how to use middleware in Rack. Here's a very good article https://blog.engineyard.com/2015/understanding-rack-apps-and-middleware
I think maybe you might want to start out with tests as I found a good example and you could use it with your app.
ENV['RACK_ENV'] = 'test'
require 'test/unit'
require 'rack/test'
require File.dirname(__FILE__) + '/web'
class AuthenticationTest < Test::Unit::TestCase
include Rack::Test::Methods
def app
WardenTest #MyApp
end
def test_without_authentication
get '/protected'
assert_equal 401, last_response.status
end
def test_with_bad_credentials
authorize 'bad', 'boy'
get '/protected'
assert_equal 401, last_response.status
end
def test_with_proper_credentials
authorize 'admin', 'admin'
get '/protected'
assert_equal 200, last_response.status
assert_equal "You're welcome, authenticated client", last_response.body
end
end
Then a few routes added to your app.
helpers do
def protected!
return if authorized?
headers['WWW-Authenticate'] = 'Basic realm="Restricted Area"'
halt 401, "Not authorized\n"
end
def authorized?
#auth ||= Rack::Auth::Basic::Request.new(request.env)
#auth.provided? and #auth.basic? and #auth.credentials and
#auth.credentials == ['admin', 'admin']
end
end
get '/' do
"Everybody can see this page"
end
get '/protected' do
protected!
"You're welcome, authenticated client"
end
In my experience working with Ruby, it's always a good idea to start out with tests for any new project. I often test little pieces first though just to gain an understanding of how they work.
Once you get a better understanding of Rack, especially Rack::Builder, you can use
map '/test' do
...all the middleware needed
run App
end
and try out different configurations to see which ones work best for your needs as I'm doing while I write this.
Enjoy! ;-)

Acceptance testing of sinatra app using webrat fails

I am trying to test a ruby authentication app using minitest and webrat but get errors.
Tests like visit '/' fail with an error Status 200 expected but was 404.
Tests containing code like fill_in :email, :with => "first#company.com" fail with error Could not find field: :email.
I read several sinatra, testing and webrat documents and forums. Some of them were old and suggested stuff like Sinatra::Default, but github.com/brynary/webrat/wiki/sinatra, Building a Sinatra App Driven By Webrat Tests and Learning From the Masters: Sinatra Internals are new, yet they still fail.
Basically, I didn't like sentence-like syntax of rspec, cucumber etc but do want to do behaviour driven development. I really like the minitest syntax, both tests and output and that is why I choose webrat for BDD. If I'm wrong about expecting webrat to fulfill acceptance testing requirements, please simply tell me that I should use this framework or that one.
Apart from that, the first parts of the main file and test file are below. I hope someone can explain me, what I am missing?
test_file
require "test/unit"
require "minitest/autorun"
require "rack/test"
require 'webrat'
require_relative "../lib/kimsin.rb"
Webrat.configure do |config|
config.mode = :rack
end
ENV["RACK_ENV"] = "test"
class KimsinTests < Test::Unit::TestCase
include Rack::Test::Methods
include Webrat::Methods
include Webrat::Matchers
def app
Sinatra::Application.new
end
def test_create_user
visit "/user/new"
fill_in :username, :with => "first#company.com"
fill_in :password, :with => "abC123?*"
fill_in :confirm_password, :with => "abC123?*"
click_link "Register"
assert 201, last_response.status, "Status 201 expected but was #{last_response.status}.\n#{error}"
assert_contain /Logged in as first#company.com./, "No user created"
assert_contain /Logout/, "Logout link not present"
end
main_file
require "sinatra"
require "erb"
require_relative "../lib/kimsin/version"
require_relative "../lib/kimsin/user"
class Kimsin < Sinatra::Application
use Rack::Session::Pool, :expire_after => 2592000
set :session_secret, BCrypt::Engine.generate_salt
configure :development do
DataMapper.auto_migrate!
end
get "/" do
if session[:user_id]
user = User.get session[:user_id]
email = user.email
erb :index, :locals => { :email => email }
else
email = nil
erb :index, :locals => { :email => email }
end
end
Using Sinatra with Webrat should work fine. I think that the errors that you are seeing are caused by the following method (around line 18 in your test file):
def app
Sinatra::Application.new
end
This is setting up the Sinatra::Application base class to run your tests against when you really need to set up your own subclass Kimsin (because you are creating a modular style Sinatra app), i.e.
def app
Kimsin.new
end
The 404 errors and missing fields are happening because Sinatra::Application doesn't define any of the routes you are testing.
You might also like to take a look at Capybara if you are looking for similar alternatives to Webrat.

Resources