A presentation given in early 2011 at Ruby on Rails Oceania about a conceptual refactoring of legacy code written to handle a reality that didn't eventuate.
6. Enumeration table
Easy to add new products or options
No new columns, just insert rows
No code changes
Can query against it
“Find all artworks available on red t-shirts”
Populate web form elements easily
7. Domain model is in the DB
Product class is a generic type
What about domain logic?
„Instances‟ need a separate set of
tables
8. Buying a product
OptionOption Value
Available Option
Value
Product
Artwork
Product Type
Selected
Product
Selected
Option Value
9. Preparing the product form
framed_print = ProductType.find_by_name(“FramedPrint”)
# Get the Framed Print product
fp_product = artwork.products.detect do |p|
p.product_type == framed_print
end
aovs = fp_product.available_option_values
# Group these into the options
options_hash = {}
aovs.each do |aov|
options_hash[aov.option_value.option] << aov.option_value
end
# Order the option values, so we have S, M, L, etc.
# Populate form elements
10. SQL view
1. Load products by artwork_id
2. Load available_product_options by
product_id
3. Load option_values by option_value_id
4. Load options by option_id
11. Troublesome requirements
Some products have default
options
T-Shirt default colour and style
Options that are not constrained
Calendar start month
Inter-option constraints
Longsleeve Tee colours
12. Default options
Flag on available_product_option
Need to enforce one default per
option type
Render artwork with default
options
14. Rendering the defaults
# Get the default product
tee_product = artwork.get_default_product
aovs = tee_product.available_option_values
defaults = aovs.select {|aov| aov.default? }
# Group these into the options (assuming one per option)
options_hash = {}
defaults.each do |aov|
options_hash[aov.option_value.option] << aov.option_value
end
# Need to validate that we have all required defaults
# and only one per option, etc.
15. SQL view
1. Load products by product_id
2. Load available_product_options by
product_id
3. Load option_values by option_value_id
4. Load options by option_id
Same joins as the first example
18. Inter option constraints
Roundneck T-shirts can have all
colours
V-Neck can only have a few
How do you model that in the DB?
Do you want to?
19. Original driver
Want to add new products weekly
Really?
Real-world constraints
Product preparation takes months
Cannot cover all eventualities
New products always required new
code anyway
20. Alternative approach
Model product types as classes
Define options and constraints (DSL)
Available products is a list of keys
on Artwork
Available product options is a hash
on the Artwork
So is the default options
23. What have we gained?
An expressive, flexible product
model
Simpler code, overall
Actually easier to add products now
And easier to modify existing ones
Nearly 25% average speed increase
24. What have we lost?
“Find all red t-shirts”
Utilise a search engine
“How many large framed prints
have we sold?”
Reporting via Data Warehouse
Online product addition
100s of millions of rows from the DB!
Notas do Editor
Legacy code slows down feature developmentWe all write itSo there’s a lot around
Need to determine what the product represents before we can do anything with it.For example, we need to render it differently
Joining 4 tables together
Each thumbnail requires the same 4 tables joined
Each work has multiple products, and each has multiple available options
In 4 years, RedBubble has 11 product types.Sourcing suppliers, defining the specifications and getting samples all was time consuming.The application needed to know how to provision, configure and render the product.Genericity ended up getting in the way.
Selected options and defaults are on the artworkOptions are modelled in a code (DSL)Rendering a default view requires only the loading of one record
Every line of code had a reason for existenceChallenge existing models for every new requirement