Upgrade to Pro — share decks privately, control downloads, hide ads and more …

SpreeConf 2012: Spree's Hidden Gems

Brian Quinn
February 19, 2012

SpreeConf 2012: Spree's Hidden Gems

A random collection of all things Spree Commerce related.

Brian Quinn

February 19, 2012
Tweet

More Decks by Brian Quinn

Other Decks in Technology

Transcript

  1. Brian Quinn
    Co-founder & CTO of Spree Commerce, Inc.
    @briandq
    bdq

    View full-size slide

  2. Spree’s Hidden Gems

    View full-size slide

  3. We’re going to cover

    View full-size slide

  4. We’re going to cover
    • Core Features

    View full-size slide

  5. We’re going to cover
    • Core Features
    • Official Extensions

    View full-size slide

  6. We’re going to cover
    • Core Features
    • Official Extensions
    • Community Extensions

    View full-size slide

  7. We’re going to cover
    • Core Features
    • Official Extensions
    • Community Extensions
    • Extension Points

    View full-size slide

  8. We’re going to cover
    • Core Features
    • Official Extensions
    • Community Extensions
    • Extension Points
    • Spree Performance

    View full-size slide

  9. We’re going to cover
    • Core Features
    • Official Extensions
    • Community Extensions
    • Extension Points
    • Spree Performance
    • Deployment Options

    View full-size slide

  10. We’re going to cover
    • Core Features
    • Official Extensions
    • Community Extensions
    • Extension Points
    • Spree Performance
    • Deployment Options
    • More theming

    View full-size slide

  11. Core Features

    View full-size slide

  12. Product Properties

    View full-size slide

  13. spree_sunspot_search
    https://github.com/jbrien/spree_sunspot_search
    spree_solr_search
    https://github.com/romul/spree_solor_search

    View full-size slide

  14. # Product Properties for use with Faceting
    # gets turned to #{value}_property for the facet
    unless defined?(PRODUCT_PROPERTY_FACETS)
    PRODUCT_PROPERTY_FACETS = [:brand, :shirt_fit, :gender]
    end
    Facetted Search

    View full-size slide

  15. Product Groups

    View full-size slide

  16. http://yourstore.com/pg/spreeconf

    View full-size slide

  17. Adding a scope
    module Spree
    class Product < ActiveRecord::Base
    def self.in_permalink(words)
    like_any([:permalink], prepare_words(words))
    end
    end
    end

    View full-size slide

  18. Official Extensions

    View full-size slide

  19. spree_store_credits

    View full-size slide

  20. spree_affiliate

    View full-size slide

  21. spree_gift_cards

    View full-size slide

  22. spree_related_products

    View full-size slide

  23. spree_page_cache

    View full-size slide

  24. Community
    Extensions

    View full-size slide

  25. Community
    Extensions
    Spree’s
    Rogues
    Gallery

    View full-size slide

  26. spree_static_content

    View full-size slide

  27. globalize_spree

    View full-size slide

  28. dancinglightning

    View full-size slide

  29. spree_simple_reports

    View full-size slide

  30. spree_flexi_variants

    View full-size slide

  31. spree_essentials

    View full-size slide

  32. spree_essential_cms

    View full-size slide

  33. spree_essential_blog

    View full-size slide

  34. and more!
    • spree_essential_press
    • spree_essential_news
    • spree_redirect
    • spree_variant_options
    • spree_drop_shipping
    • spree_retailers
    • spree_wholesale

    View full-size slide

  35. Extension Points

    View full-size slide

  36. Spree::Admin::ResourceController

    View full-size slide

  37. Action Callbacks
    Spree::Admin::ProductsController.class_eval do
    update.before :fix_something
    private
    def fix_something
    @product.name ||= 'Fallback name'
    end
    end

    View full-size slide

  38. Action Callbacks
    Spree::Admin::ProductsController.class_eval do
    update.before :fix_something
    private
    def fix_something
    @product.name ||= 'Fallback name'
    end
    end

    View full-size slide

  39. Action Callbacks
    Spree::Admin::ProductsController.class_eval do
    update.before :fix_something
    private
    def fix_something
    @product.name ||= 'Fallback name'
    end
    end

    View full-size slide

  40. Action Callbacks
    Spree::Admin::ProductsController.class_eval do
    update.after :fix_something
    private
    def fix_something
    @product.variants.clear
    end
    end

    View full-size slide

  41. Action Callbacks
    Spree::Admin::ProductsController.class_eval do
    update.fails :fix_something
    private
    def fix_something
    Panic.start_now!
    end
    end

    View full-size slide

  42. core/app/controllers/.../resource_controller.rb
    require 'spree/core/action_callbacks'
    class Spree::Admin::ResourceController < Spree::Admin::BaseController
    ...
    def create
    invoke_callbacks(:create, :before)
    if @object.save
    invoke_callbacks(:create, :after)
    flash.notice = flash_message_for(@object, :successfully_created)
    respond_with(@object) do |format|
    format.html { redirect_to location_after_save }
    format.js { render :layout => false }
    end
    else
    invoke_callbacks(:create, :fails)
    respond_with(@object)
    end
    end

    View full-size slide

  43. core/app/controllers/.../resource_controller.rb
    require 'spree/core/action_callbacks'
    class Spree::Admin::ResourceController < Spree::Admin::BaseController
    ...
    def create
    invoke_callbacks(:create, :before)
    if @object.save
    invoke_callbacks(:create, :after)
    flash.notice = flash_message_for(@object, :successfully_created)
    respond_with(@object) do |format|
    format.html { redirect_to location_after_save }
    format.js { render :layout => false }
    end
    else
    invoke_callbacks(:create, :fails)
    respond_with(@object)
    end
    end

    View full-size slide

  44. core/app/controllers/.../resource_controller.rb
    require 'spree/core/action_callbacks'
    class Spree::Admin::ResourceController < Spree::Admin::BaseController
    ...
    def create
    invoke_callbacks(:create, :before)
    if @object.save
    invoke_callbacks(:create, :after)
    flash.notice = flash_message_for(@object, :successfully_created)
    respond_with(@object) do |format|
    format.html { redirect_to location_after_save }
    format.js { render :layout => false }
    end
    else
    invoke_callbacks(:create, :fails)
    respond_with(@object)
    end
    end

    View full-size slide

  45. core/app/controllers/.../resource_controller.rb
    require 'spree/core/action_callbacks'
    class Spree::Admin::ResourceController < Spree::Admin::BaseController
    ...
    def create
    invoke_callbacks(:create, :before)
    if @object.save
    invoke_callbacks(:create, :after)
    flash.notice = flash_message_for(@object, :successfully_created)
    respond_with(@object) do |format|
    format.html { redirect_to location_after_save }
    format.js { render :layout => false }
    end
    else
    invoke_callbacks(:create, :fails)
    respond_with(@object)
    end
    end

    View full-size slide

  46. Spree::Responder

    View full-size slide

  47. Spree::HomeController.class_eval do
    respond_override :index => {
    :html => {
    :success => lambda { render :partial => "shared/some_file" }
    }
    }
    end
    Respond Override

    View full-size slide

  48. Spree::HomeController.class_eval do
    respond_override :index => {
    :html => {
    :success => lambda { render :partial => "shared/some_file" }
    }
    }
    end
    Respond Override

    View full-size slide

  49. Spree::HomeController.class_eval do
    respond_override :index => {
    :html => {
    :success => lambda { render :partial => "shared/some_file" }
    }
    }
    end
    Respond Override

    View full-size slide

  50. Spree::HomeController.class_eval do
    respond_override :index => {
    :html => {
    :success => lambda { render :partial => "shared/some_file" }
    }
    }
    end
    Respond Override

    View full-size slide

  51. Spree::HomeController.class_eval do
    respond_override :index => {
    :html => {
    :success => lambda { render :partial => "shared/some_file" }
    }
    }
    end
    Respond Override

    View full-size slide

  52. Respond Override
    Spree::HomeController.class_eval do
    def index
    @searcher = Spree::Config.searcher_class.new(params)
    @products = @searcher.retrieve_products
    respond_with(@products) do |format|
    format.html { render :partial => "shared/some_file" }
    end
    end
    end

    View full-size slide

  53. Respond Override
    Spree::HomeController.class_eval do
    def index
    @searcher = Spree::Config.searcher_class.new(params)
    @products = @searcher.retrieve_products
    respond_with(@products) do |format|
    format.html { render :partial => "shared/some_file" }
    end
    end
    end

    View full-size slide

  54. Abilities & Roles

    View full-size slide

  55. def initialize(user)
    user ||= Spree::User.new
    if user.has_role? 'admin'
    can :manage, :all
    else
    can :read, User do |resource|
    resource == user
    end
    can :update, User do |resource|
    resource == user
    end
    can :create, User
    can :read, Order do |order, token|
    order.user == user || order.token && token == order.token
    end
    ...
    auth/app/models/spree/ability.rb

    View full-size slide

  56. def initialize(user)
    user ||= Spree::User.new
    if user.has_role? 'admin'
    can :manage, :all
    else
    can :read, User do |resource|
    resource == user
    end
    can :update, User do |resource|
    resource == user
    end
    can :create, User
    can :read, Order do |order, token|
    order.user == user || order.token && token == order.token
    end
    ...
    auth/app/models/spree/ability.rb

    View full-size slide

  57. def initialize(user)
    user ||= Spree::User.new
    if user.has_role? 'admin'
    can :manage, :all
    else
    can :read, User do |resource|
    resource == user
    end
    can :update, User do |resource|
    resource == user
    end
    can :create, User
    can :read, Order do |order, token|
    order.user == user || order.token && token == order.token
    end
    ...
    auth/app/models/spree/ability.rb

    View full-size slide

  58. def initialize(user)
    user ||= Spree::User.new
    if user.has_role? 'admin'
    can :manage, :all
    else
    can :read, User do |resource|
    resource == user
    end
    can :update, User do |resource|
    resource == user
    end
    can :create, User
    can :read, Order do |order, token|
    order.user == user || order.token && token == order.token
    end
    ...
    auth/app/models/spree/ability.rb

    View full-size slide

  59. def initialize(user)
    user ||= Spree::User.new
    if user.has_role? 'admin'
    can :manage, :all
    else
    can :read, User do |resource|
    resource == user
    end
    can :update, User do |resource|
    resource == user
    end
    can :create, User
    can :read, Order do |order, token|
    order.user == user || order.token && token == order.token
    end
    ...
    auth/app/models/spree/ability.rb

    View full-size slide

  60. def check_authorization
    session[:access_token] ||= params[:token]
    order = current_order || Spree::Order.find_by_number(params[:id])
    if order
    authorize! :edit, order, session[:access_token]
    else
    authorize! :create, Spree::Order
    end
    end
    auth/.../orders_controller_decorator.rb

    View full-size slide

  61. def check_authorization
    session[:access_token] ||= params[:token]
    order = current_order || Spree::Order.find_by_number(params[:id])
    if order
    authorize! :edit, order, session[:access_token]
    else
    authorize! :create, Spree::Order
    end
    end
    auth/.../orders_controller_decorator.rb

    View full-size slide

  62. def check_authorization
    session[:access_token] ||= params[:token]
    order = current_order || Spree::Order.find_by_number(params[:id])
    if order
    authorize! :edit, order, session[:access_token]
    else
    authorize! :create, Spree::Order
    end
    end
    auth/.../orders_controller_decorator.rb

    View full-size slide

  63. Adding Roles
    > packer = Spree::Role.create :name => 'packer'
    => #
    > Spree::User.last.has_role? 'packer'
    => false
    > Spree::User.last.roles << packer
    > Spree::User.last.has_role? 'packer'
    => true

    View full-size slide

  64. Adding Roles
    > packer = Spree::Role.create :name => 'packer'
    => #
    > Spree::User.last.has_role? 'packer'
    => false
    > Spree::User.last.roles << packer
    > Spree::User.last.has_role? 'packer'
    => true

    View full-size slide

  65. Adding Roles
    > packer = Spree::Role.create :name => 'packer'
    => #
    > Spree::User.last.has_role? 'packer'
    => false
    > Spree::User.last.roles << packer
    > Spree::User.last.has_role? 'packer'
    => true

    View full-size slide

  66. Adding Roles
    > packer = Spree::Role.create :name => 'packer'
    => #
    > Spree::User.last.has_role? 'packer'
    => false
    > Spree::User.last.roles << packer
    > Spree::User.last.has_role? 'packer'
    => true

    View full-size slide

  67. Adding abilities
    module Spree
    class PackerAbility
    include CanCan::Ability
    def initialize(user)
    if user.has_role? 'packer'
    can [:read, :update], Shipment
    end
    end
    end
    end
    Ability.register_ability(Spree:::PackerAbility)

    View full-size slide

  68. Adding abilities
    module Spree
    class PackerAbility
    include CanCan::Ability
    def initialize(user)
    if user.has_role? 'packer'
    can [:read, :update], Shipment
    end
    end
    end
    end
    Ability.register_ability(Spree:::PackerAbility)

    View full-size slide

  69. Adding abilities
    module Spree
    class PackerAbility
    include CanCan::Ability
    def initialize(user)
    if user.has_role? 'packer'
    can [:read, :update], Shipment
    end
    end
    end
    end
    Ability.register_ability(Spree:::PackerAbility)

    View full-size slide

  70. Adding abilities
    module Spree
    class PackerAbility
    include CanCan::Ability
    def initialize(user)
    if user.has_role? 'packer'
    can [:read, :update], Shipment
    end
    end
    end
    end
    Ability.register_ability(Spree:::PackerAbility)

    View full-size slide

  71. Adding abilities
    module Spree
    class PackerAbility
    include CanCan::Ability
    def initialize(user)
    if user.has_role? 'packer'
    can [:read, :update], Shipment
    end
    end
    end
    end
    Ability.register_ability(Spree:::PackerAbility)

    View full-size slide

  72. Versionfile

    View full-size slide

  73. Versionfile
    "1.0.x" => { :branch => 'master' }
    "0.70.x" => { :tag => "v1.2", :version => "1.2" }
    "0.60.x" => { :ref => "5172de86dd46905e6d60", :version => "1.1" }
    "0.50.x" => { :ref => "5172de86dd46905e6d60", :version => "1.1" }
    "0.30.x" => { :version => "1.0" }

    View full-size slide

  74. Versionfile
    "1.0.x" => { :branch => 'master' }
    "0.70.x" => { :tag => "v1.2", :version => "1.2" }
    "0.60.x" => { :ref => "5172de86dd46905e6d60", :version => "1.1" }
    "0.50.x" => { :ref => "5172de86dd46905e6d60", :version => "1.1" }
    "0.30.x" => { :version => "1.0" }

    View full-size slide

  75. Versionfile
    "1.0.x" => { :branch => 'master' }
    "0.70.x" => { :tag => "v1.2", :version => "1.2" }
    "0.60.x" => { :ref => "5172de86dd46905e6d60", :version => "1.1" }
    "0.50.x" => { :ref => "5172de86dd46905e6d60", :version => "1.1" }
    "0.30.x" => { :version => "1.0" }

    View full-size slide

  76. Versionfile
    "1.0.x" => { :branch => 'master' }
    "0.70.x" => { :tag => "v1.2", :version => "1.2" }
    "0.60.x" => { :ref => "5172de86dd46905e6d60", :version => "1.1" }
    "0.50.x" => { :ref => "5172de86dd46905e6d60", :version => "1.1" }
    "0.30.x" => { :version => "1.0" }

    View full-size slide

  77. Versionfile
    "1.0.x" => { :branch => 'master' }
    "0.70.x" => { :tag => "v1.2", :version => "1.2" }
    "0.60.x" => { :ref => "5172de86dd46905e6d60", :version => "1.1" }
    "0.50.x" => { :ref => "5172de86dd46905e6d60", :version => "1.1" }
    "0.30.x" => { :version => "1.0" }

    View full-size slide

  78. Versionfile

    View full-size slide

  79. Performance
    Tools & Testing

    View full-size slide

  80. Siege
    brew install siege

    View full-size slide

  81. siege -r 10 -c 10 http://yoursite.com

    View full-size slide

  82. siege -t 30M -c 10 http://yoursite.com

    View full-size slide

  83. siege -t 30M -c 10 -b http://yoursite.com

    View full-size slide

  84. siege -t 30M -c 10 -d 15 http://yoursite.com

    View full-size slide

  85. siege -t 30M -c 10 -f /path/to/urls.txt
    http://yoursite.com
    http://yoursite.com/products
    http://yoursite.com/p/something
    http://yoursite.com/login POST username=admin&password=123

    View full-size slide

  86. siege -t 30M -c 10 -i -f /path/to/urls.txt

    View full-size slide

  87. Sproxy
    brew install sproxy

    View full-size slide

  88. sproxy -p 9090 -f /path/to/urls.txt

    View full-size slide

  89. BrowerMob.com
    LoadImpact.com

    View full-size slide

  90. Performance
    Tips

    View full-size slide

  91. (if you’re using MySQL)
    Use mysql2

    View full-size slide

  92. mysql
    BEFORE
    ZOMG!

    View full-size slide

  93. mysql
    mysql2
    BEFORE
    AFTER
    ZOMG!

    View full-size slide

  94. Ruby GC Tuning

    View full-size slide

  95. 100 concurrent users, 10 reqs each

    View full-size slide

  96. Transactions:


    Availability:


    Elapsed time:


    Data transferred:

    Response time:


    Transaction rate:

    Throughput:


    Concurrency:


    Successful transactions:
    Failed transactions:

    Longest transaction:

    Shortest transaction:

    1000 hits
    100.00 %
    189.45 secs
    3.89 MB
    17.98 secs
    5.28 trans/sec
    0.02 MB/sec
    94.90
    1000
    0
    21.85
    1.08

    View full-size slide

  97. Slowest Request

    View full-size slide

  98. Change Ruby from MRI 1.8.7-p352
    to REE 1.8.7-2011.03
    Step 1

    View full-size slide

  99. Tweak Unicorn’s Configuration
    Step 2

    View full-size slide

  100. preload_app true
    GC.respond_to?(:copy_on_write_friendly=) and GC.copy_on_write_friendly = true
    before_fork do |server, worker|
    defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!
    end
    after_fork do |server, worker|
    defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
    end
    config/unicorn.rb

    View full-size slide

  101. preload_app true
    GC.respond_to?(:copy_on_write_friendly=) and GC.copy_on_write_friendly = true
    before_fork do |server, worker|
    defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!
    end
    after_fork do |server, worker|
    defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
    end
    config/unicorn.rb

    View full-size slide

  102. Get Garbage Collection times reporting to
    New Relic
    Step 3

    View full-size slide

  103. if GC.respond_to?(:enable_stats)
    GC.enable_stats
    end
    config/initializer/enable_gc_stats.rb

    View full-size slide

  104. 100 concurrent users, 10 reqs each
    BEFORE

    View full-size slide

  105. 100 concurrent users, 10 reqs each
    BEFORE
    AFTER

    View full-size slide

  106. RUBY_HEAP_MIN_SLOTS= 400000
    RUBY_HEAP_SLOTS_INCREMENT= 10000
    RUBY_HEAP_SLOTS_GROWTH_FACTOR= 1
    RUBY_GC_MALLOC_LIMIT= 55000000
    RUBY_HEAP_FREE_MIN= 80000

    View full-size slide

  107. Transactions:


    Availability:

    Elapsed time:


    Data transferred:

    Response time:


    Transaction rate:

    Throughput:


    Concurrency:


    Successful transactions:
    Failed transactions:

    Longest transaction:

    Shortest transaction:

    1000 hits
    100.00 %
    39.94 secs
    3.72 MB
    3.29 secs
    25.04 trans/sec
    0.08 MB/sec
    82.35
    1000
    0
    7.67
    0.48
    1000 hits
    100.00 %
    189.45 secs
    3.89 MB
    17.98 secs
    5.28 trans/sec
    0.02 MB/sec
    94.90
    1000
    0
    21.85
    1.08
    Before After

    View full-size slide

  108. GC Tuning can go wrong!

    View full-size slide

  109. Deployment
    Scenarios

    View full-size slide

  110. MYSQL
    RUBY
    App & DB
    RAILS
    UNICORN
    NGINX
    Single App + DB

    View full-size slide

  111. Application
    Database
    1 App + 1 DB

    View full-size slide

  112. Application
    Database
    Load Balancer
    Application
    1 x LB, 2 x App, 1 x DB

    View full-size slide

  113. Database
    Application +
    Load Balancer
    Application
    2 x App, 1 x DB

    View full-size slide

  114. Database
    Application +
    Load Balancer
    Stand By
    1 x App, 1 x StdBy, 1 x DB

    View full-size slide

  115. Database
    Application +
    Load Balancer
    Application
    2 x App, 1 x DB

    View full-size slide

  116. Database
    Application +
    Load Balancer
    Application
    Application

    View full-size slide

  117. Application
    Database
    Load Balancer
    Application
    Application
    Application

    View full-size slide

  118. Database
    Application +
    Load Balancer
    Stand By
    1 x Application
    1 x Database
    1 x Stand By App

    View full-size slide

  119. Deployment Service

    View full-size slide

  120. Deployment Service

    View full-size slide

  121. In case you missed it

    View full-size slide

  122. In case you missed it
    • A blank Ubuntu 10.04 LTS Cloud Server

    View full-size slide

  123. In case you missed it
    • A blank Ubuntu 10.04 LTS Cloud Server
    • Installed Puppet

    View full-size slide

  124. In case you missed it
    • A blank Ubuntu 10.04 LTS Cloud Server
    • Installed Puppet
    • Compiled Ruby 1.9.3 from source

    View full-size slide

  125. In case you missed it
    • A blank Ubuntu 10.04 LTS Cloud Server
    • Installed Puppet
    • Compiled Ruby 1.9.3 from source
    • Installed Rubg Gems, Git, Nginx, MySQL,
    Imagick, Bundler, Foreman, etc

    View full-size slide

  126. In case you missed it
    • A blank Ubuntu 10.04 LTS Cloud Server
    • Installed Puppet
    • Compiled Ruby 1.9.3 from source
    • Installed Rubg Gems, Git, Nginx, MySQL,
    Imagick, Bundler, Foreman, etc
    • Cloned Spree 1.0.0.rc demo app

    View full-size slide

  127. In case you missed it
    • A blank Ubuntu 10.04 LTS Cloud Server
    • Installed Puppet
    • Compiled Ruby 1.9.3 from source
    • Installed Rubg Gems, Git, Nginx, MySQL,
    Imagick, Bundler, Foreman, etc
    • Cloned Spree 1.0.0.rc demo app
    • Ran bundle install

    View full-size slide

  128. In case you missed it
    • A blank Ubuntu 10.04 LTS Cloud Server
    • Installed Puppet
    • Compiled Ruby 1.9.3 from source
    • Installed Rubg Gems, Git, Nginx, MySQL,
    Imagick, Bundler, Foreman, etc
    • Cloned Spree 1.0.0.rc demo app
    • Ran bundle install
    • Bootstrapped sample db & compiled assets

    View full-size slide

  129. In case you missed it
    • A blank Ubuntu 10.04 LTS Cloud Server
    • Installed Puppet
    • Compiled Ruby 1.9.3 from source
    • Installed Rubg Gems, Git, Nginx, MySQL,
    Imagick, Bundler, Foreman, etc
    • Cloned Spree 1.0.0.rc demo app
    • Ran bundle install
    • Bootstrapped sample db & compiled assets
    • All with one command!

    View full-size slide

  130. Open Sourcing
    (soon)

    View full-size slide

  131. Theme Editors

    View full-size slide

  132. Deface Editor

    View full-size slide

  133. Deface Editor
    Store Editor

    View full-size slide

  134. Deface Editor
    Store Editor
    ==

    View full-size slide

  135. SpreeConf Whisky

    View full-size slide