12. 5 languages
14 standard user roles
166 controllers
183 database tables
1577 permissions
58k lines of code
>100k API calls / day
3m rows in biggest table
$5.6b spend through system
13. Internationalization
Localization
XSS protection
Single sign-on
Data security
Extensibility
Customization
API integration
S*@P
14. co sta
m te
m
e
pl m
i
ex a t
m ac
ch
xo
s
qu i
i d
n e
er ne
ie s
s
RU
LE
15. co sta
m te
m
e
pl m
i
ex a t
m ac
ch
xo
s
qu i
i d
n e
er ne
ie s
s
RU
LE
24. class Api::DepartmentsController
< Api::BaseController
api_scaffold :department
end
Coupa::Application.routes.draw do
namespace :api do
resources :departments
end
end
25. class Department
< ActiveRecord::Base
api_in [:name, :active], [],
{:keys => ["id", "name"]}
api_out [:name, :active]
end
26. (lack of)
class Department associations
< ActiveRecord::Base
api_in [:name, :active], [],
{:keys => ["id", "name"]}
api_out [:name, :active]
end
27. (lack of)
class Department associations
< ActiveRecord::Base
api_in [:name, :active], [],
{:keys => ["id", "name"]}
api_out [:name, :active]
end
28. class Api::BaseController < AppController
def self.api_scaffold(model_id, options={})
options.assert_valid_keys(:class_name,
:scope, :user_scope, :validate_if,
:before_save)
singular_name = model_id.to_s
class_name = options[:class_name] ||
singular_name.camelize
model_class = class_name.constantize
[way more code than I can fit]
self.module_eval(overlay_methods)
end
end
41. class RequisitionHeader < ActiveRecord::Base
validates_presence_of :ship_to_address,
:if => Proc.new { |requisition_header|
requisition_header.status &&
!%w(draft cart pending_buyer_action).
include?(requisition_header.status)
}
end
42. class RequisitionHeader < ActiveRecord::Base
def editable?
user = User.current_user
case self.status
when 'pending_approval'
approvable_by? && user && user.authorized?('approver', 'edit')
when 'cart', 'draft'
user == self.requested_by || user == self.created_by
when 'pending_buyer_action'
user && user.authorized?('buying', 'edit')
else
false
end
end
end
44. I18n loose end tying: Ugly but useful
module ActiveSupport::Inflector
def humanize_with_translation(underscored_word)
begin
(I18n.translate!("statuses.#{underscored_word}",
:default => :"activerecord.models.#{underscored_word}",
:count => 1) unless underscored_word.blank?) || ''
rescue I18n::MissingTranslationData => e
humanize_without_translation(underscored_word)
end
end
alias_method_chain :humanize, :translation
end
45. co sta
m te
m
e
pl m
i
ex a t
m ac
ch
xo
s
qu i
i d
n e
er ne
ie s
s
RU
LE
46. IF YOU TREAT YOUR DB AS DUMB
HOW CAN IT LOVE YOU?
47. SELECT distinct suppliers.id
FROM suppliers
JOIN supplier_items ON supplier_items.supplier_id = suppliers.id
LEFT OUTER JOIN catalogs ON catalogs.id = supplier_items.catalog_id
LEFT OUTER JOIN contracts ON contracts.id =
supplier_items.contract_id
LEFT OUTER JOIN business_group_assignments ON
(business_group_assignments.securable_id = contracts.id AND
business_group_assignments.securable_type = 'Contract')
STRAIGHT_JOIN items ON (items.id = supplier_items.item_id AND
items.active = 1 AND (items.connect_item_id IS NULL OR
items.imported_from_connect = 1))
WHERE ( suppliers.status = 'active'
AND (supplier_items.catalog_id IS NULL
OR ( catalogs.status = 'accepted'
AND (catalogs.start_date IS NULL OR '2011-11-15 18:30:00' >=
catalogs.start_date)
AND (catalogs.end_date IS NULL OR '2011-11-15 18:30:00' <
catalogs.end_date) ))
AND (supplier_items.contract_id IS NULL
OR (contracts.status = 'published'
AND business_group_assignments.business_group_id in (3,2,1)
AND contracts.start_date <= '2011-11-15 18:30:00'
AND (contracts.end_date IS NULL OR contracts.end_date > '2011-11-15
18:30:00') ))
48. SELECT distinct suppliers.id
FROM suppliers
sy
JOIN supplier_items ON supplier_items.supplier_id = suppliers.id
LEFT OUTER JOIN catalogs ON catalogs.id = supplier_items.catalog_id
LEFT OUTER JOIN contracts ON contracts.id =
il
supplier_items.contract_id
LEFT OUTER JOIN business_group_assignments ON
(business_group_assignments.securable_id = contracts.id AND
Ra
business_group_assignments.securable_type = 'Contract')
STRAIGHT_JOIN items ON (items.id = supplier_items.item_id AND
items.active = 1 AND (items.connect_item_id IS NULL OR
items.imported_from_connect = 1))
ry
WHERE ( suppliers.status = 'active'
AND (supplier_items.catalog_id IS NULL
OR ( catalogs.status = 'accepted'
AND (catalogs.start_date IS NULL OR '2011-11-15 18:30:00' >=
ve
catalogs.start_date)
AND (catalogs.end_date IS NULL OR '2011-11-15 18:30:00' <
catalogs.end_date) ))
AND (supplier_items.contract_id IS NULL
OR (contracts.status = 'published'
t
AND business_group_assignments.business_group_id in (3,2,1)
No
AND contracts.start_date <= '2011-11-15 18:30:00'
AND (contracts.end_date IS NULL OR contracts.end_date > '2011-11-15
18:30:00') ))
49. So let’s step back a moment and
talk about a common problem...
53. Users Roles Permissions
Suppliers Addresses Countries
Commodities Contacts
And this is a
simple one...
54. Users Roles Permissions
Online Shipping Payment
Stores Terms Terms
Suppliers Addresses Countries
Parent Phone
Suppliers Numbers
Commodities Contacts
And this is a
simple one...
66. MySQL count query avoidance hack. YMMV.
.paginate({
:select => 'SQL_CALC_FOUND_ROWS
DISTINCT #{table}.id',
:per_page => 20,
:page => options[:page]
})
[Then query just the rows you want]
67. Like deep_clone? Try deep_exec.
class Array
def deep_exec(&blk)
result = []
each do |e|
if e.respond_to?(:deep_exec)
result << e.deep_exec(&blk)
else
result << yield(e)
end
end
result
end
end
68. Like deep_clone? Try deep_exec.
class Array class Hash
def deep_exec(&blk)
def deep_exec(&blk)
result = {}
result = []
each do |k,v|
each do |e|
result[yield k] =
if e.respond_to?(:deep_exec)
if v.respond_to?(:deep_exec)
result << e.deep_exec(&blk)
v.deep_exec(&blk)
else
else
result << yield(e)
yield v
end
end
end
end
result
result
end
end
end
end
71. Photo Credits
Used under Creative Commons from typedow
Used under Creative Commons from gaymerbear
Used under Creative Commons from Mandy_Jansen
http://popartmachine.com/blog/pop-art-based-on-hawaiian-shirt-pattern.html
Used under Creative Commons from casey.marshall