Archive for February, 2008

Rails: Completing Requested Action After Authentication

February 26, 2008

Within Seekler, we have several actions that are only available to logged-in users. We wanted a general solution that would first redirect the user to login or create an account and then complete the action the user initially requested. The primary motivator for this was non-logged-in users clicking ‘create your own list’ on a Seekler list. If a user clicked this link, we wanted to allow them to login or register and then have the list creation action take place and return them to their newly created list. We had other cases of site actions, that also required being logged in, so I was hoping to find a simple way to do this across the site.
We use the acts_as_authenticated plugin (I know most people are now using restful_authentication, we haven’t switched yet), which relies on before_filters to verify users are logged in before using logged-in-only actions. I ended up just adding one line into that system that makes it simple to return the user to his or her previous action after logging in.
In a controller using acts_as_authenticated, there is normally a before_filter requiring authentication before any actions except the actions available to all visitors

before_filter :login_required, :except => [ :show, :list]

This means that on an action like :create the user will end up calling the login_required method in /lib/authenticated_system.rb. If you add the following line to that method, it will record the information you need to redirect the user back to the action they intended after they login.

def login_required
username, passwd = get_auth_data
self.current_user ||= User.authenticate(username, passwd) || :false if username && passwd
session[:last_action] = request.env['REQUEST_URI'] unless logged_in? && authorized? # add this line!
logged_in? && authorized? ? true : access_denied
end

At this point, the filter will direct all non-logged-in users to your associated login failure action, in our case /account/login
In the login action, (after doing any other login specific code) you just have to redirect the user if they have :last_action set

def login
# login stuff
if session[:last_action]
redirect_to session[:last_action]
else
redirect_to :controller => 'account', :action => 'index'
end
end

You can add a bunch of other things like ignoring certain actions you don’t want to forward the user to, or also return the user to wherever they were before clicking a login link, but this simple version gives the basic idea. Obviously this code is specifically for the acts_as_authenticated plugin, but it should be relatively easy to generalize this same sort of solution to work for any type of authentication that is in use on your Rails project.
If you want to see this in action, just visit a list like Best Charities on Seekler and click the ‘create your own list’ link in the top right.

The Maybe Monad in Ruby

February 14, 2008

| [tests]
I’m a little behind on my goal to learn a new programming language each year – I’m still working on really understanding last year’s language, Haskell. Judging from the number of tutorials online, understanding monads is one of the hardest parts of learning Haskell.
I won’t even try to explain monads to you, because (as this post will reveal), I’m still a bit shaky on them myself. However, I will say my attempts to build a Maybe monad in Ruby have helped me learn more about Monads – and have yielded a pretty useful piece of code.
Why should you care about the Maybe monad?
Have you ever written code like this?

if(customer && customer.order && customer.order.id==newest_customer_id)
# ... do something with customer
end

Don’t you wish there was just a way to call customer.order.id without all those intermediate checks for nil objects?
One solution is to obey the Law of Demeter – in other words, to change the call to customer.order_id. Two problems – one, sometimes obeying the LoD is more work than its worth (especially for quick and dirty solutions) and two, it still doesn’t help you if customer is nil.
Still not convinced there is a problem worth solving here? Check out better explanations by Oliver Steele or Reg Braithwaite – they explain the problem better than I did (and present some interesting solutions).
Anyway, here is my solution – a Maybe monad in Ruby. You would change the above code to:

if(Maybe.new(customer).order.id.value==newest_customer_id)
# ... do something with customer
end

How does this work? Without going into too much theory about monads, the basic idea is that Maybe is a container object. It stores (or ‘wraps’) any value and then controls how methods are called on the wrapped object.
More concretely, the call to Maybe.new wraps a value in a Maybe object. Whenever you call methods on the Maybe object, it does a simple check: if the wrapped value is nil, then it returns another Maybe object that wraps nil. If the wrapped object is not nil, it calls the method on that object, then wraps it back up in a Maybe object.
Confused? Here’s a few examples:

>> Maybe.new("10")            # => #<Maybe:0x34cbd5c @value="10">
>> Maybe.new("10").to_i       #=> #<Maybe:0x34c919c @value=10>
>> Maybe.new("10").to_i.value #=> 10
>> Maybe.new(nil)             #=> <Maybe:0x34c4408 @value=nil>
>> Maybe.new(nil).to_i        #=> #<Maybe:0x34c2644 @value=nil>
>> Maybe.new(nil).to_i.value  #=> nil

I’ve started to use this in my code on Seekler and it’s definitely helped me clean up some messy conditional code quite a bit. Try it out and please let me know if there is any way I can improve it. You can find the source code here. You can also check out the tests.
All in all, this project was a lot of fun, helped me understand monads better, and showed me how a powerful concept from Haskell can be a very useful concept in Ruby.
Update: Thanks to the help from people commenting on this post (as well as some help from James Iry), I think I’ve solved the problem I describe below. It turns out I was confusing two different methods that monads should have: fmap (which takes any function and applies it in the monad) and pass (which takes a function that returns a Maybe and applies the wrapped value to it). I’m leaving the issue below in the hopes that others who may be confused can learn from it.
There is one big problem with my code (and it reveals my ignorance regarding monads): this monad doesn’t seem to meet the third monad law. In other words, the following test fails

  def test_monad_rule_3
f = Proc.new {|x| x*2}
g = Proc.new {|x| x+1}
m = Maybe.new(3)
assert_equal m.pass{|x| f[x]}.pass{|x|g[x]}.value, m.pass{|x| f[x].pass{|y|g[y]}}.value
end

I learned about the monad laws from ‘Monads on Ruby’ on Moonbase, but I don’t yet understand how the third monad law works (in his examples at the bottom of this page). If any wise Ruby hacker (or Haskell hacker) can help me bridge this gap in my understanding, I’d really appreciate it.
For the interested reader, there’s some great information out there about this problem in general and Ruby monads in particular.
MenTaLguY has a great tutorial on Monads in Ruby over at Moonbase
Oliver Steele explores the problem in depth and looks at a number of different solutions
Reg Braithwaite explores this same problem and comes up with a different, but very cool solution in Ruby
Weave Jester has another solution, inspired by the Maybe monad
Update: James Iry has a great explanation of monads from the Scala perspective. His explanation of the monad laws (and the difference between map and flatMap, as they are called in Scala, really helped me out)

Capistrano path and environment variable issues

February 12, 2008

We have been using Capistrano for awhile now and it has been working great. Recently, for reasons we are not sure of, migrations started failing. When we executed ‘cap deploy:migrate’, we would get an error “no such file to load — rcov/rcovtask”. After a lot of investigation, I found that this meant that the system, for whatever reason, couldn’t find our gems anymore.
Since Capistrano hardly gives any information when failing, it took awhile to figure out what was going on. sshing in as our Capistrano user, and running printenv, showed that the environment variables for the Capistrano user were all set as expected. After some investigation, we learned that since Capistrano runs a non-interactive user, the environment isn’t set up the same. To figure out the environment that Capistrano was running as, I created a task and added it to config/deploy.rb

desc "Echo environment vars"
namespace :env do
task :echo do
run "echo printing out cap info on remote server"
run "echo $PATH"
run "printenv"
end
end

After saving your deploy.rb file you should be able to execute that command on your servers by running “cap env:echo”.
This printed out a different path than sshing in as our Cap user and also was missing the RUBYOPT=rubygems environment variable. Obviously, not having RUBYOPT set was causing the problem of not finding out gems.
So next, we just had to set up the correct environment for the non-interactive Cap user. This was a little tricky since non-interactive users don’t load all the environment settings like normal users. This is done for extra security. Unfortunately, it means it is a little more annoying to set up these variables, and not as well documented.
If you need to add environment variables or change the PATH for a non-interactive user, you need to edit two files. First ssh in as the user under which the non-interactive scripts will be run. Then, in that user’s home directory, create a file ~/.ssh/environment (in our case /home/cap/.ssh/environment). Edit this file and add whatever environment variables you need (in our case RUBYOPT=rubygems), and save the file. Then edit /etc/ssh/sshd_config, and add the following line
PermitUserEnvironment yes
I would run ‘man sshd_config’ to make sure your version of sshd supports this option, as some older versions and distros (we are using Debian) do not. Then restart ssh by running “sudo /etc/init.d/ssh restart”. At this point, you should try running “cap env:echo” again, which should print the RUBYOPTS environment variable set correctly. This fixed our problems and migrations began running again without a problem.

A New Resource For Colorado Entrepreneurs

February 6, 2008

For the months before we launched Seekler, we were basically holed up in our house, coding night and day. Since we launched, we’ve been making an effort to engage more with the local entrepreneur community. What we’ve found is that this area (Boulder in particular, but Denver as well) has a great ecosystem for tech startups.
Some of our friends in this community recently came up with a great idea – to try and collect all of the knowledge that tech entrepreneurs need to get started in one place – stuff like recommendations on lawyers, accountants, banks, events, health insurance, etc.
This made a lot of sense to us – after all, we spent a lot of time getting Pretheory up and running, precisely because we didn’t have such a resource. And setting up the nuts and bolts of a company is pretty boring – you want to do that stuff as quickly as possible, so you can get to the interesting part – building a technology that people will love.
So, if you’re in the Denver/Boulder area and are looking to found your own startup, I encourage you to check out the Boulder Tech Bootstrap. If you are already up and running, I encourage you to go add some information to help others.
We’ve added some info to the site – and we also have created a few lists that we hope Colorado tech entrepreneurs will find useful. Check them out and let us know if they help you!
Best Boulder/Denver Startup Blogs
Best Startup Events in Boulder/Denver, CO
Seed Funding Opportunities (ideally, we’d like you to stay in the area, but you should explore all your options for funding…)

Seekler’s top 10 reviewers

February 5, 2008

Seekler added the ability to add full reviews to items you rank awhile ago. Only recently have we been showing off this feature, and making it obvious to the users. It has been popular for users to read reviews about the items ranked on our lists. So as a little thank you to our users that have started adding reviews, I present the top ten users with the most reviews on Seekler.

  1. dan
  2. ben
  3. michaelwong38
  4. peter
  5. vacelts
  6. DomFilosa
  7. galactic_dev
  8. drew.klein
  9. nicole
  10. Jezebel

Congratulations, and thanks for your time and reviews, keep up the good work.

Creating Super Bowl Lists in Real Time at Seekler.com

February 3, 2008

Today we’re trying something new at Seekler – we’re creating a list of the best Super Bowl commercials of 2008 in real-time while we’re watching the game. If you want, feel free to join us and create your own list of favorite commercials as you watch the big game!
We’ve also got a list of the best Super Bowl commercials of all time that you can vote on as well. Check it out!