using redis and ruby to implement a tiny short url app - ruby

I'm making a short URL app, using Ruby, Sinatra, and Redis. Currently it's under 15 lines:
require 'rubygems'
require 'sinatra'
require 'redis'
require 'uri'
configure do
REDISTOGO_URL = "redis://localhost:6379/"
uri = URI.parse(REDISTOGO_URL)
REDIS = Redis.new(:host => uri.host, :port => uri.port, :password => uri.password)
end
get '/' do
haml :index
end
post '/shorten' do
a = rand(9999)
REDIS.set(a.to_s, params[:long])
"<pre>http://199.19.118.186/get/#{a}</pre>"
#haml :shorten
end
get '/get/:url' do
redirect REDIS.get(params[:url])
end
Where index.haml is a form that POSTs long to /shorten. I've no problem with that.
Right now, however, when I try to use Redis (with the server running, yes), I get this error:
What am I doing wrong?
EDIT: Copy/paste from Emacs... facepalm
EDIT: When trying to access redis alone from ruby (code below), I get this:
/var/lib/gems/1.8/gems/redis-2.2.2/lib/redis/client.rb:47:in `call': ERR unknown command (RuntimeError)
from /var/lib/gems/1.8/gems/redis-2.2.2/lib/redis.rb:841:in `set'
from /usr/lib/ruby/1.8/monitor.rb:242:in `synchronize'
from /var/lib/gems/1.8/gems/redis-2.2.2/lib/redis.rb:840:in `set'
from test_redis.rb:9
With this code:
require 'rubygems'
require 'redis'
require 'uri'
REDISTOGO_URL = "redis://localhost:6379/"
uri = URI.parse(REDISTOGO_URL)
REDIS = Redis.new(:host => uri.host, :port => uri.port, :password => uri.password)
REDIS.set("test", "blah")
puts REDIS.get("test")

Ruby being case sensitive, I would try to replace REDIS.SET by REDIS.set and REDIS.GET by REDIS.get. You can find the documentation of the Redis client here:
https://github.com/ezmobius/redis-rb
I have tested your example with ruby 1.8.7. (default on my Linux box).
After installing sinatra, haml, redis and hiredis gems, I have modified the code as follows:
require 'rubygems'
require 'sinatra'
require 'redis'
require 'uri'
configure do
REDISTOGO_URL = "redis://localhost:6379/"
uri = URI.parse(REDISTOGO_URL)
REDIS = Redis.new(:host => uri.host, :port => uri.port, :password => uri.password)
end
get '/' do
"Hello"
haml :index
end
post '/shorten' do
a = rand(9999)
REDIS.set(a.to_s, "http://"+params[:long])
"<pre>http://localhost:4567/get/#{a}</pre>"
end
get '/get/:url' do
redirect REDIS.get(params[:url])
end
I have added the following template in views/index.haml.
!!!
%html
%head
%title My Sinatra Website
%body
%h1 Welcome
%p
Welcome to my website made with Sinatra and HAML
%form{ :action => "/shorten", :method=>"POST" }
%fieldset
%input{ :type =>"text", :name=>"long" }
%input{ :type =>"submit" }
Once Redis is started on port 6379 and sinatra on port 4567, it works like a charm.
I suggest you check your ruby installation and try to access Redis from ruby with a simple non sinatra script.
UPDATE:
The error message is peculiar because normally, when an unknown command is sent to the server, the faulty command is provided:
ERR unknown command 'dummy'
while you just have:
ERR unknown command
Actually, this specific fix was introduced in Redis server more than 2 years ago (in December 2009) - an eternity for Redis.
https://github.com/antirez/redis/commit/2c14807b2dd5c15f1471bec32a7c6dbb077720ee
In other words, you are trying to use a very old (i.e. pre 1-3) version of Redis server with the last version of the Redis client ruby gem, which probably does not support anymore the initial protocol. You may want to compile and install a recent version of Redis server (it is easy), it should work better.

Related

Set cookie expiration time in Ruby

I am using Ruby with Sinatra do develop a web application.
I have Ruby version 2.3.0, Sinatra 5.0.30
Following the suggestions from Rails cookies, set start date and expire date, I tried this:
#language = 'en-US'
response.set_cookie(:USER_LANGUAGE, :value => #language, :expires => 1.hour.from_now, :domain => '.example.com')
At first I thought it worked because the cookie set except the expiration time is still just only for the session. The error in my Apache error log says this:
NoMethodError - undefined method 'hour' for 1:Fixnum:
Please note: none of these worked to resolve the problem (none of them could be properly found by the compiler)
require 'active_support'
require 'active_support/all'
require 'activesupport'
So, I tried this instead:
#language = 'en-US'
response.set_cookie(:USER_LANGUAGE, :value => #language, :expires => 30, :domain => '.example.com')
Just to see what would happen and nothing changed, it still only expires with the session.
How should I go about setting an expiration time for my cookies in Ruby with Sinatra?
Sinatra doesn't have the ActiveSupport library which provides a helper for number-to-time, so 1.hour.from_now doesn't works here.
You should use this:
class SinatraApp < Sinatra::Base
use Rack::Session::Cookie, :key => 'rack.session',
:domain => 'foo.com',
:path => '/',
:expire_after => 2592000, # In seconds
:secret => 'some_secret'
And set a time in seconds. Because the Sinatra session comes from Rack::Session.
HOW TO ENABLE SESSIONS WITH SINATRA

Creating a variable per instance, rather than per request, with Sinatra modular style

I have a Sinatra app, written in modular style, running on Heroku. It uses Redis and I have a limited number (10) of Redis connections. I found that it would often throw errors complaining that it had run out of Redis connections. So I started using connection_pool in the hope that would fix things; a single pool of Redis connections and the app would choose one of those each time, rather than try to create a new connection on each request.
But I'm still getting the same issue. I can do loads of Redis queries on a single query without complaints. But if I reload a single test page, which just does some Redis queries, several times in fairly quick succession, I get the "Redis::CommandError - ERR max number of clients reached" error again.
So I'm assuming, maybe, it's creating a new instance of connection_pool on each request... I don't know. But it's not "pooling" as I would expect it to.
I have this kind of thing:
# myapp.rb
$LOAD_PATH.unshift(File.dirname(__FILE__))
$stdout.sync = true
require 'thin'
require 'myapp/frontend'
MyApp::Frontend.run!
And the Sinatra app:
# myapp/frontend.rb
require 'sinatra/base'
require 'redis'
require 'connection_pool'
require 'uuid'
module MyApp
class Frontend < Sinatra::Base
helpers do
def redis_pool
#redis_pool ||= ConnectionPool.new(:size => 8, :timeout => 5) do
redis_uri = URI.parse(ENV['REDISCLOUD_URL'])
client = ::Redis.new(:host => redis_uri.host,
:port => redis_uri.port,
:password => redis_uri.password)
end
end
end
get '/tester/'
redis_pool.with do |r|
id = UUID.generate
r.hset(:user, id, "Some data")
r.hget(:user, id)
r.hdel(:user, id)
end
p "DONE"
end
end
end
The Procfile looks like:
web: ruby myapp.rb
Any ideas? The current site is pretty low traffic, so this should be possible.
A new instance of #redis_pool is created every time a get request for /tester/ is processed because the helper method redis_pool is called every time.
You can use sinatra's settings helper to initialize a redis connection only once:
config do
redis_uri = URI.parse(ENV['REDISCLOUD_URL'])
set :redis, Redis.new(:host => redis_uri.host,
:port => redis_uri.port,
:password => redis_uri.password)
end
Now the each instance of the app has one redis connection that persists for all requests. Access the setting like so
get '/tester/'
id = UUID.generate
settings.redis.hset(:user, id, "some data")
settings.redis.hget(:user, id)
settings.redis.hdel(:user, id)
p "DONE"
end

sending file with pony by sinatra app - missing file

I want to send a email from my sinatra application.
Here is the code:
require 'pony'
class Cms < Application
get "/mail" do
Pony.mail :to => 'to#gmail.com',
:from => "from#gmail.com",
:subject => "Thanks for signing my guestbook!",
:via => :sendmail,
:via_options => {
:address => 'smtp.gmail.com',
:port => '587',
:user_name => 'user#gmail.com',
:pass => 'pass',
:enable_starttls_auto => false
},
:body => erb(:"cms/mail")
redirect '/'
end
end`
Thin is starting application with no errors, but When i request myapp.local/mail i've got an error:
LoadError - no such file to load -- mail/network/delivery_methods/smtp:
/var/lib/gems/1.8/gems/mail-2.4.4/lib/mail/configuration.rb:31:in lookup_delivery_method'
/var/lib/gems/1.8/gems/mail-2.4.4/lib/mail/configuration.rb:25:in delivery_method'
/var/lib/gems/1.8/gems/mail-2.4.4/lib/mail/mail.rb:111:in delivery_method'
/var/lib/gems/1.8/gems/mail-2.4.4/lib/mail/message.rb:116:in initialize'
/var/lib/gems/1.8/gems/mail-2.4.4/lib/mail/mail.rb:50:in new'
/var/lib/gems/1.8/gems/mail-2.4.4/lib/mail/mail.rb:50:in new'
/var/lib/gems/1.8/gems/pony-1.4/lib/pony.rb:174:in build_mail'
/var/lib/gems/1.8/gems/pony-1.4/lib/pony.rb:138:in mail'
./app/controllers/cms.rb:8:in GET /mail'
File /var/lib/gems/1.8/gems/mail-2.4.4/lib/mail/network/delivery_methods/smtp.rb exists.
I was getting this same error when I was using the inline configuration of the Mail gem:
mail.delivery_method :sendmail
mail.deliver!
Removing that first line, and moving the configuration to immediately following the loading of the mail gem fixed it.
Wherever in your app you require 'mail' just configure it immediately:
require 'mail'
Mail.defaults do
delivery_method :sendmail
end
Update: This worked for awhile... But then for some reason I began seeing this error:
rbenv/versions/1.8.7-p374/lib/ruby/gems/1.8/gems/mail-2.5.4/lib/mail/fields/common/common_address.rb:9:in `parse': no such file to load -- mail/elements/address_list (LoadError)
Update2: The failures happen randomly it seems. Something about the way the autoload works in Ruby 1.8.7-p374 is causing it to not be able to find files that do in fact exist. Also, I am using slimgems not rubygems.
These are the hacks I've had to implement so far to use Mail with multi-part email and sendmail delivery method:
require 'mail'
require 'mail/network/delivery_methods/sendmail'
require 'mail/elements/address_list'
require 'mail/fields/common/common_address'
require 'mail/elements/content_type_element'
require 'mail/elements/address'
require 'mail/elements/content_transfer_encoding_element'
Mail.defaults do
delivery_method :sendmail
end

Can't connect to Redis from Sinatra

I'm trying to hook up redis to a Sinatra app I'm building:
require 'rubygems'
require 'sinatra'
#require 'sinatra/synchrony'
require 'redis'
require 'mongo_mapper'
require './startup'
def stats_connect
uri = URI.parse('redis://redistogo:xxxxxxxxxxxxxxxxxx#barb.redistogo.com:1337/')
puts 'connecting to... ' + uri.to_s
redis = Redis.new(:host => uri.host, :port => uri.port, :password => uri.password)
puts "Statistics connected >> OK" if redis
return redis
end
stats = stats_connect
post('/') do
#...
end
If I run the following app with foreman, I get this error:
18:09:02 web.1 | started with pid 825
18:09:08 web.1 |
/Users/vladdypwnz/.rvm/gems/ruby-1.9.2-p180/gems/redis-3.0.1/lib/redis/connection/ruby.rb:113:in
`connect_nonblock': Can't assign requested address - connect(2)
(Errno::EADDRNOTAVAIL)
18:09:08 web.1 | from
/Users/vladdypwnz/.rvm/gems/ruby-1.9.2-p180/gems/redis-3.0.1/lib/redis/connection/ruby.rb:113:in
`connect'
When I push to heroku, the error changes to this:
/app/vendor/bundle/ruby/1.9.1/gems/redis-3.0.1/lib/redis/client.rb:260:in `rescue in establish_connection': Timed out connecting to Redis on barb.redistogo.com:0 (Redis::CannotConnectError)
If I pop into IRB, require redis and use the same exact stats_connect() method that I created, redis works just fine, I can access everything and create keys.
What's going on? I'm completely stumped.
Are you sure your redis is running on barb.redistogo.com:1337?
A couple weeks ago I deployed an app using redis to heroku and used this configuration:
uri = URI.parse(ENV["REDISTOGO_URL"])
redis = Redis.new(:host => uri.host, :port => uri.port, :password => uri.password)
to run it locally I did:
redis = Redis.new(:host => "localhost", :port => 6379)

Ruby namespacing issues

I'm attempting to build a gem for interacting w/ the Yahoo Placemaker API but I'm running into an issue. When I attempt to run the following code I get:
NameError: uninitialized constant Yahoo::Placemaker::Net
from /Users/Kyle/.rvm/gems/ruby-1.9.2-p290/gems/yahoo-placemaker-0.0.1/lib/yahoo-placemaker.rb:17:in `extract'
from (irb):4
from /Users/Kyle/.rvm/rubies/ruby-1.9.2-p290/bin/irb:16:in `<main>'
yahoo-placemaker.rb
require "yahoo-placemaker/version"
require 'json'
require 'ostruct'
require 'net/http'
module Yahoo
module Placemaker
def self.extract (text = '')
host = 'wherein.yahooapis.com'
payload = {
'documentContent' => text,
'appid' => APP_ID,
'outputType' => 'json',
'documentType' => 'text/plain'
}
req = Net::HTTP::Post.new('/v1/document')
req.body = to_url_params(payload)
response = Net::HTTP.new(host).start do |http|
http.request(req)
end
json = JSON.parse(response.body)
Yahoo::Placemaker::Result.new(json)
end
end
end
I have yet to figure out how exactly constant name resolution works in Ruby (I think the rules are a bit messy here), but from my experience it could well be that Net is looked up in the current namespace instead of the global one. Try using the fully qualified name:
::Net::HTTP::Post.new
A similar problem could occur in this line:
Yahoo::Placemaker::Result
You should replace it with either ::Yahoo::Placemaker::Result or better Result (as it lives in the current namespace).
Try requiring net/http before. Ruby is falling back to find it in the module if it isn't defined.
require 'net/http'

Resources