Complications with users and customers

November 08, 2008

I find it very easy to get paralysed by complexity. This has been happening on my current project with users and customers. You see I want people to be able to buy things without having to be users. However I also want it to be especially easy for users to buy things. Finally I don't want any particular person to have seperate records in user and customer but a customer can be user.

Now this doesn't really make sense, and I haven't really written the half of it! And all these possibilities have ... well to be frank left me paralysed. Now eventually I might get my head around all of this but is there a better way? Can I use features to explore this problem bit by bit, just dealing with one little piece at a time. Will a reasonable solution emerge

Here is the first scenario I'm going to use

Scenario: Customer with existing user
  Given there is an activated user shirley
  Given I am logged out
    And I am asked for my customer details
   When I fill in my customer details with shirleys details
    And I press "next"
   Then I should see an error explanation

This scenario is at a pretty high level and implemented using a number of compound steps.

What this does is go through a number of things to get to a form where we enter in the details required to become a customer. This is part of a wizard, after the customer step comes the billing step.

So what I'm saying is that if I fill in my customer details with an existing user then I should get an error with an explanation. Making this story pass was actually very simple, perhaps I have made a bit of progress with this problem - so what next?

Well lets assume the error explanation tells the user the correct thing to do then we can

When I login as shirley
Then I should be at billing step

When I login as shirley has a number of design implications which we can now deal with. These are made real by implementing the step. With the stuff I have so far the step would be implemented by

When /^I login as shirley$/ do
  visits login_path
  fills_in(:login, :with => @shirley.login) 
  fills_in(:password, :with => @shirley.password) 
  clicks_button
end

And to make my story work I have to go back to where I was

When I login as shirley
And I go to checkout
Then I should be at billing step

Getting this to work is a good thing. But now I really need to refine this as my customer has to

  1. visit the login path
  2. fill in the login form and submit it
  3. return back to the checkout

and this is far to much to expect. So going back to our original story

When I login as shirley
Then I should be at billing step

we can see that we want to implement When I login as shirley as

When /^I login as shirley$/ do
  fills_in(:login, :with => @shirley.email) 
  fills_in(:password, :with => @shirley.password) 
  clicks_button
end

which means we need a login form on our page, and this form needs the ability to login using an email. A couple of options come to mind here

  1. Implement the login form as part of the error explanation with the login field filled in with the email address.
  2. Have the login form on every page and point the user to it with the error explanation.

So I'll have a think about this for a while; but note how we have made some progress, have a simpler decision in front of us, and have some code which is testing what we are doing and will show whether our solution is working.

Update

By setting

after_filter :store_location, :only => [:edit]

in the OrdersController I can now make

When I login as shirley
Then I should be at billing step

pass, as when I return from the login form to the wizard when I log in. So for now I can postpone the decision about having a login form on this page or on every page and have the alternative of putting some better instructions in response to the email in use error.

To deal with login with email I created a step

When /^I login as (\S+) with my email address$/ do |login|  

used this step in the scenario

and implemented the functionality by changing how User authenticates from

def self.authenticate(login, password)  
  u = find_in_state :first, :active, :conditions => {:login => login} # need to get the salt
  u && u.authenticated?(password) ? u : nil
end       

to

def self.authenticate(login, password)  
  if RE_EMAIL_OK.match(login)
    u = find_in_state :first, :active, :conditions => {:email => login} # need to get the salt
  else  
    u = find_in_state :first, :active, :conditions => {:login => login} # need to get the salt
  end
  u && u.authenticated?(password) ? u : nil
end       

n.b. RE_EMAIL_OK is a regex