Complications with users and customers
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
- visit the login path
- fill in the login form and submit it
- 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
- Implement the login form as part of the error explanation with the login field filled in with the email address.
- 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