Two Customer Types - Best Design Principle?

Hi all,

I’ll do my best to explain this… I have the possibility to have two
customer types in my system:

  1. A “mailing list” type: new records are saved when user enters email
    address in the mailing list subscription form.
  2. A “full” type: new records are saved when the user buys something and
    provides all their billing/shipping details, etc.

Model:

  • I’d prefer to have 1 customers table with a column to mark their
    status.

Validation:

  • The customer email address is common to both so validation on this is
    easy.
  • I want the “full” type to have extended validation rules, first and
    last name for example.
  • I’d like to enforce a unique email address.

Questions:

  • Rails looks to support model based validation well. Is there a way
    to specify validation rules depending on which customer type is being
    saved (as I only want email validated for a mailing list type, but for
    the full type I need validation on pretty much everything)? Should I
    have 2 models? How do I map this to a single database table?

  • How do I code an upgrade on a customer? For example, if the customer
    is new and does not have an existing record, then when they buy
    something and fill out their details there is no problem here (besides
    the validation issue above). But, if the customer does have an existing
    record, is there an elegant way to “merge” the extended details from the
    form with the existing email address marked record on the database?
    Currently I have the code:

     form = params[:new_customer]
     email = form[:email]
     tmp_customer = Customer.find_by_email(email)
     if tmp_customer != nil && (tmp_customer.level == 'mailinglist'
    

|| tmp_customer.level == ‘test’)
@customer = Customer.new(params[:new_customer])
@customer.id = tmp_customer.id
@customer.level = ‘full’
if @customer.update_attributes(@customer)
flash[:notice] = “Customer details upgraded
successfully.”
redirect_to(:action => ‘payment_details’)
else
render(:action => ‘checkout’)
end
else
@customer = Customer.new(params[:new_customer])
@customer.level = ‘full’
if @customer.save
flash[:notice] = “Customer details saved successfully.”
redirect_to(:action => ‘payment_details’)
else
render(:action => ‘checkout’)
end
end

But with this I get the error: undefined method `stringify_keys!’ for
#Customer:0x38b1ac0
My attempt was to get the existing customer, set the id of the form data
to the record on the database and then perform an update. I’m not sure
however if I have covered all of the Rails requirements to do something
like this. But, possibly (most likely due to my lack of Rails
experience), there is a better way.

Sorry for the long post, I hope it makes sense, can anyone help?

Thanks,
Dan

Hi Dan,

I did something like this in the past, and I took the approach of
separating list users from full users. Logically they just didn’t
seem the same to me. I wrote a ListUser model which only had a name
and email address, and a User model which had all the info for a
regular user. When I wanted to send an email to everyone, I’d just
pull all the email addresses from each table and mail it out. This
setup also had the advantage of allowing me to direct different
messages to previous customers and potential customers - something
along the lines of, " Go to mysite.com/abcd to access the great deal
available only to current customers!" and “I see you aren’t a customer
yet…well you’re missing out on my ___ great deal. Make an order by
6pm on Tuesday and you’ll be able to take advantage of this killer
offer”

To avoid duplicate emails, when someone signed up for the site I would
just delete their entry in the list users table if it exists. Worked
pretty nicely for me.

Pat

Interesting, thanks for your insight Pat. I’ll have a think about it.

I actually got part of it to work whilst hacking away on the train. It
seems I wasn’t thinking too straight on the first attempt and was over
complicating things.

If I change:
form = params[:new_customer]
email = form[:email]
tmp_customer = Customer.find_by_email(email)
if tmp_customer != nil && (tmp_customer.level == ‘mailinglist’
|| tmp_customer.level == ‘test’)
@customer = Customer.new(params[:new_customer])
@customer.id = tmp_customer.id
@customer.level = ‘full’
if @customer.update_attributes(@customer)
to:
form = params[:new_customer]
email = form[:email]
tmp_customer = Customer.find_by_email(email)
if tmp_customer != nil && (tmp_customer.level == ‘mailinglist’
|| tmp_customer.level == ‘test’)
if tmp_customer.update_attributes(params[:new_customer])

and include the level=‘full’ as a hidden field in the form it works
beautifully! I think that the error was generated because the new
customer (@customer) didn’t have an id value because Rails only
generates it just before it is written to the database. Because I’m
already checking that the email address matches, it doesn’t matter if it
gets overwritten in the update (this issue led to a bit of reverse
thinking in the first and broken solution).

But still an issue is the validation issue. Pat’s solution certainly
gets around the problem. Can anyone else chip in with some advice?

Thanks,
Dan

Ah ha! That looks exactly like what I need. Thanks Kenneth.

Dan

I think you want to check out some of the writings on Single Table
Inheritance

Rails supports STI – check out ActiveRecord::Base under STI…