Slide 1

Slide 1 text

Applying the Module Builder Pattern Bradley.Schaefer@stitchfix.com @soulcutter

Slide 2

Slide 2 text

Experiments: Terminology Control Cell Experiment Cell

Slide 3

Slide 3 text

Experiments: Terminology A B

Slide 4

Slide 4 text

Experiments: Terminology loser WINNER

Slide 5

Slide 5 text

A/B Experiments The Way Things Were class BodyShape attr_reader :api, :client NAME = 'New Womens Body Shape Question' CONTROL_CELL = 'Control' TEST_CELL = 'New Question' def self.define! StitchFix::Experiments::API.define_experiment do name NAME treatment CONTROL_CELL, control: true treatment TEST_CELL end end def initialize(client) @api = StitchFix::Experiments::API.new(experiment_name: NAME, test_subject: client) @client = client end def show_new_body_shape_questions? api.allocating?(time: client.create_date) && api.in_cell?(cell_name: TEST_CELL) end end

Slide 6

Slide 6 text

A/B Experiments The Way Things Were class ItemTypeGroupPreferences attr_reader :api, :client NAME = 'New Womens Item Type Group Preferences' CONTROL_CELL = 'Control' TEST_CELL = 'New Question' def self.define! StitchFix::Experiments::API.define_experiment do name NAME treatment CONTROL_CELL, control: true treatment TEST_CELL end end def initialize(client) @api = StitchFix::Experiments::API.new(experiment_name: NAME, test_subject: client) @client = client end def show_new_womens_item_type_group_preferences_questions? api.allocating?(time: client.create_date) && api.in_cell?(cell_name: TEST_CELL) end end

Slide 7

Slide 7 text

A/B Experiments The Way Things Were class PrimaryObjectives attr_reader :api, :client NAME = 'New Womens Primary Reasons Question' CONTROL_CELL = 'Control' TEST_CELL = 'New Question' def self.define! StitchFix::Experiments::API.define_experiment do name NAME treatment CONTROL_CELL, control: true treatment TEST_CELL end end def initialize(client) @api = StitchFix::Experiments::API.new(experiment_name: NAME, test_subject: client) @client = client end def show_primary_objectives_question? api.allocating?(time: client.create_date) && api.in_cell?(cell_name: TEST_CELL) end end

Slide 8

Slide 8 text

A/B Experiments The Way Things Were class WomensBrands attr_reader :api, :client NAME = 'New Womens Brands Question' CONTROL_CELL = 'Control' TEST_CELL = 'New Question' def self.define! StitchFix::Experiments::API.define_experiment do name NAME treatment CONTROL_CELL, control: true treatment TEST_CELL end end def initialize(client) @api = StitchFix::Experiments::API.new(experiment_name: NAME, test_subject: client) @client = client end def show_womens_brands_question? api.allocating?(time: client.create_date) && api.in_cell?(cell_name: TEST_CELL) end end

Slide 9

Slide 9 text

A/B Experiments The Way Things Were class BottomsAttributePreferences attr_reader :api, :client NAME = 'New Womens Bottoms Attribute Preferences' CONTROL_CELL = 'Control' TEST_CELL = 'New Question' def self.define! StitchFix::Experiments::API.define_experiment do name NAME treatment CONTROL_CELL, control: true treatment TEST_CELL end end def initialize(client) @api = StitchFix::Experiments::API.new(experiment_name: NAME, test_subject: client) @client = client end def show_new_bottoms_attribute_preferences_question? api.allocating?(time: client.create_date) && api.in_cell?(cell_name: TEST_CELL) end end

Slide 10

Slide 10 text

What Stands Out?

Slide 11

Slide 11 text

What Stands Out? class BodyShape attr_reader :api, :client NAME = 'New Womens Body Shape Question' CONTROL_CELL = 'Control' TEST_CELL = 'New Question' def self.define! StitchFix::Experiments::API.define_experiment do name NAME treatment CONTROL_CELL, control: true treatment TEST_CELL end end def initialize(client) @api = StitchFix::Experiments::API.new( experiment_name: NAME, test_subject: client ) @client = client end def show_new_body_shape_questions? api.allocating?(time: client.create_date) && api.in_cell?(cell_name: TEST_CELL) end end class PrimaryObjectives attr_reader :api, :client NAME = 'New Womens Primary Reasons Question' CONTROL_CELL = 'Control' TEST_CELL = 'New Question' def self.define! StitchFix::Experiments::API.define_experiment do name NAME treatment CONTROL_CELL, control: true treatment TEST_CELL end end def initialize(client) @api = StitchFix::Experiments::API.new( experiment_name: NAME, test_subject: client ) @client = client end def show_primary_objectives_question? api.allocating?(time: client.create_date) && api.in_cell?(cell_name: TEST_CELL) end end

Slide 12

Slide 12 text

Duplication! class BodyShape attr_reader :api, :client NAME = 'New Womens Body Shape Question' CONTROL_CELL = 'Control' TEST_CELL = 'New Question' def self.define! StitchFix::Experiments::API.define_experiment do name NAME treatment CONTROL_CELL, control: true treatment TEST_CELL end end def initialize(client) @api = StitchFix::Experiments::API.new( experiment_name: NAME, test_subject: client ) @client = client end def show_new_body_shape_questions? api.allocating?(time: client.create_date) && api.in_cell?(cell_name: TEST_CELL) end end class PrimaryObjectives attr_reader :api, :client NAME = 'New Womens Primary Reasons Question' CONTROL_CELL = 'Control' TEST_CELL = 'New Question' def self.define! StitchFix::Experiments::API.define_experiment do name NAME treatment CONTROL_CELL, control: true treatment TEST_CELL end end def initialize(client) @api = StitchFix::Experiments::API.new( experiment_name: NAME, test_subject: client ) @client = client end def show_primary_objectives_question? api.allocating?(time: client.create_date) && api.in_cell?(cell_name: TEST_CELL) end end

Slide 13

Slide 13 text

Removing Duplication def initialize(client) @api = StitchFix::Experiments::API.new(experiment_name: NAME, test_subject: client) @client = client end

Slide 14

Slide 14 text

Removing Duplication module ABExperiment def initialize(client) @api = StitchFix::Experiments::API.new(experiment_name: NAME, test_subject: client) @client = client end end

Slide 15

Slide 15 text

Removing Duplication class BodyShape attr_reader :api, :client NAME = 'New Womens Body Shape Question' CONTROL_CELL = 'Control' TEST_CELL = 'New Question' def self.define! StitchFix::Experiments::API.define_experiment do name NAME treatment CONTROL_CELL, control: true treatment TEST_CELL end end def initialize(client) @api = StitchFix::Experiments::API.new(experiment_name: NAME, test_subject: client) @client = client end def show_new_body_shape_questions? api.allocating?(time: client.create_date) && api.in_cell?(cell_name: TEST_CELL) end end

Slide 16

Slide 16 text

Replace all initializers class BodyShape include ABExperiment attr_reader :api, :client NAME = 'New Womens Body Shape Question' CONTROL_CELL = 'Control' TEST_CELL = 'New Question' def self.define! StitchFix::Experiments::API.define_experiment do name NAME treatment CONTROL_CELL, control: true treatment TEST_CELL end end def initialize(client) @api = StitchFix::Experiments::API.new(experiment_name: NAME, test_subject: client) @client = client end def show_new_body_shape_questions? api.allocating?(time: client.create_date) && api.in_cell?(cell_name: TEST_CELL) end end

Slide 17

Slide 17 text

Looking better! class BodyShape include ABExperiment attr_reader :api, :client NAME = 'New Womens Body Shape Question' CONTROL_CELL = 'Control' TEST_CELL = 'New Question' def self.define! StitchFix::Experiments::API.define_experiment do name NAME treatment CONTROL_CELL, control: true treatment TEST_CELL allocation Time.now..6.months.from_now live true owner Client.first end end def show_new_body_shape_questions? api.allocating?(time: client.create_date) && api.in_cell?(cell_name: TEST_CELL) end end

Slide 18

Slide 18 text

But wait… there's a problem module ABExperiment def initialize(client) @api = StitchFix::Experiments::API.new(experiment_name: NAME, test_subject: client) @client = client end end

Slide 19

Slide 19 text

But wait… there's a problem module ABExperiment def initialize(client) @api = StitchFix::Experiments::API.new(experiment_name: NAME, test_subject: client) @client = client end end Let's run the tests…

Slide 20

Slide 20 text

But wait… there's a problem module ABExperiment def initialize(client) @api = StitchFix::Experiments::API.new(experiment_name: NAME, test_subject: client) @client = client end end Failure/Error: @api = StitchFix::Experiments::API.new(experiment_name: NAME, test_subject: client) NameError: uninitialized constant ABExperiment::NAME

Slide 21

Slide 21 text

But wait… there's a problem module ABExperiment def initialize(client) @api = StitchFix::Experiments::API.new(experiment_name: NAME, test_subject: client) @client = client end end Need a way to look up a constant in the correct scope

Slide 22

Slide 22 text

Scope? Credit: Craig McMillan 2008 https://mccraigmccraig.wordpress.com/2008/10/29/ruby-objects-classes-and-eigenclasses/

Slide 23

Slide 23 text

But wait… there's a problem module ABExperiment def initialize(client) @api = StitchFix::Experiments::API.new(experiment_name: NAME, test_subject: client) @client = client end end Failure/Error: @api = StitchFix::Experiments::API.new(experiment_name: NAME, test_subject: client) NameError: uninitialized constant ABExperiment::NAME

Slide 24

Slide 24 text

const_get does the trick module ABExperiment def initialize(client) @api = StitchFix::Experiments::API.new( experiment_name: self.class.const_get(:NAME), test_subject: client ) @client = client end end

Slide 25

Slide 25 text

What stands out now? class BodyShape include ABExperiment attr_reader :api, :client NAME = 'New Womens Body Shape Question' CONTROL_CELL = 'Control' TEST_CELL = 'New Question' def self.define! StitchFix::Experiments::API.define_experiment do name NAME treatment CONTROL_CELL, control: true treatment TEST_CELL end end def show_new_body_shape_questions? api.allocating?(time: client.create_date) && api.in_cell?(cell_name: TEST_CELL) end end

Slide 26

Slide 26 text

What stands out now? class BodyShape include ABExperiment attr_reader :api, :client NAME = 'New Womens Body Shape Question' CONTROL_CELL = 'Control' TEST_CELL = 'New Question' def self.define! StitchFix::Experiments::API.define_experiment do name NAME treatment CONTROL_CELL, control: true treatment TEST_CELL end end def show_new_body_shape_questions? api.allocating?(time: client.create_date) && api.in_cell?(cell_name: TEST_CELL) end end

Slide 27

Slide 27 text

Encapsulate api/client module ABExperiment attr_reader :api, :client def initialize(client) @api = StitchFix::Experiments::API.new( experiment_name: self.class.const_get(:NAME), test_subject: client ) @client = client end end

Slide 28

Slide 28 text

Encapsulate api/client class BodyShape include ABExperiment attr_reader :api, :client NAME = 'New Womens Body Shape Question' CONTROL_CELL = 'Control' TEST_CELL = 'New Question' def self.define! StitchFix::Experiments::API.define_experiment do name NAME treatment CONTROL_CELL, control: true treatment TEST_CELL end end def show_new_body_shape_questions? api.allocating?(time: client.create_date) && api.in_cell?(cell_name: TEST_CELL) end end

Slide 29

Slide 29 text

Looking even better! class BodyShape include ABExperiment NAME = 'New Womens Body Shape Question' CONTROL_CELL = 'Control' TEST_CELL = 'New Question' def self.define! StitchFix::Experiments::API.define_experiment do name NAME treatment CONTROL_CELL, control: true treatment TEST_CELL end end def show_new_body_shape_questions? api.allocating?(time: client.create_date) && api.in_cell?(cell_name: TEST_CELL) end end

Slide 30

Slide 30 text

What stands out now? class BodyShape include ABExperiment NAME = 'New Womens Body Shape Question' CONTROL_CELL = 'Control' TEST_CELL = 'New Question' def self.define! StitchFix::Experiments::API.define_experiment do name NAME treatment CONTROL_CELL, control: true treatment TEST_CELL end end def show_new_body_shape_questions? api.allocating?(time: client.create_date) && api.in_cell?(cell_name: TEST_CELL) end end

Slide 31

Slide 31 text

What stands out now? class BodyShape include ABExperiment NAME = 'New Womens Body Shape Question' CONTROL_CELL = 'Control' TEST_CELL = 'New Question' def self.define! StitchFix::Experiments::API.define_experiment do name NAME treatment CONTROL_CELL, control: true treatment TEST_CELL end end def show_new_body_shape_questions? api.allocating?(time: client.create_date) && api.in_cell?(cell_name: TEST_CELL) end end

Slide 32

Slide 32 text

We can extract that too! module ABExperiment extend ActiveSupport::Concern # ... module ClassMethods def define! StitchFix::Experiments::API.define_experiment do name NAME treatment CONTROL_CELL, control: true treatment TEST_CELL end end end end

Slide 33

Slide 33 text

Oh yeah, I've seen this before module ABExperiment extend ActiveSupport::Concern # ... module ClassMethods def define! StitchFix::Experiments::API.define_experiment do name NAME treatment CONTROL_CELL, control: true treatment TEST_CELL end end end end

Slide 34

Slide 34 text

Oh yeah, I've seen this before module ABExperiment extend ActiveSupport::Concern # ... module ClassMethods def define! StitchFix::Experiments::API.define_experiment do name const_get(:NAME) treatment const_get(:CONTROL_CELL), control: true treatment const_get(:TEST_CELL) end end end end

Slide 35

Slide 35 text

Oh yeah, I've seen this before module ABExperiment extend ActiveSupport::Concern # ... module ClassMethods def define! StitchFix::Experiments::API.define_experiment do name const_get(:NAME) treatment const_get(:CONTROL_CELL), control: true treatment const_get(:TEST_CELL) end end end end Let's run the tests…

Slide 36

Slide 36 text

This is new… Failure/Error: name const_get(:NAME) NoMethodError: undefined method `const_get' for #

Slide 37

Slide 37 text

Not the expected scope Failure/Error: name const_get(:NAME) NoMethodError: undefined method `const_get' for # def define! StitchFix::Experiments::API.define_experiment do name const_get(:NAME) treatment const_get(:CONTROL_CELL), control: true treatment const_get(:TEST_CELL) end end

Slide 38

Slide 38 text

Let's fix that Failure/Error: name const_get(:NAME) NoMethodError: undefined method `const_get' for # def define! klass = self StitchFix::Experiments::API.define_experiment do name klass.const_get(:NAME) treatment klass.const_get(:CONTROL_CELL), control: true treatment klass.const_get(:TEST_CELL) end end

Slide 39

Slide 39 text

module ABExperiment extend ActiveSupport::Concern attr_reader :client, :api def initialize(client) @client = client @api = StitchFix::Experiments::API.new( experiment_name: self.class.const_get(:NAME), test_subject: client ) end module ClassMethods def define! klass = self StitchFix::Experiments::API.define_experiment do name klass.const_get(:NAME) treatment klass.const_get(:CONTROL_CELL), control: true treatment klass.const_get(:TEST_CELL) end end end end

Slide 40

Slide 40 text

Looking really good! class BodyShape include ABExperiment NAME = 'New Womens Body Shape Question' CONTROL_CELL = 'Control' TEST_CELL = 'New Question' def show_new_body_shape_questions? api.allocating?(time: client.create_date) && api.in_cell?(cell_name: TEST_CELL) end end

Slide 41

Slide 41 text

But Wait, There's MORE!

Slide 42

Slide 42 text

There's something still bothering me class BodyShape include ABExperiment NAME = 'New Womens Body Shape Question' CONTROL_CELL = 'Control' TEST_CELL = 'New Question' def show_new_body_shape_questions? api.allocating?(time: client.create_date) && api.in_cell?(cell_name: TEST_CELL) end end

Slide 43

Slide 43 text

An Encapsulation Problem class BodyShape include ABExperiment NAME = 'New Womens Body Shape Question' CONTROL_CELL = 'Control' TEST_CELL = 'New Question' def show_new_body_shape_questions? api.allocating?(time: client.create_date) && api.in_cell?(cell_name: TEST_CELL) end end

Slide 44

Slide 44 text

The Module Builder Pattern! class BodyShape include ABExperimentBuilder.new( name: 'New Womens Body Shape Question', control_cell: 'Control', test_cell: 'New Question' ) def show_new_body_shape_questions? api.allocating?(time: client.create_date) && api.in_cell?(cell_name: TEST_CELL) end end

Slide 45

Slide 45 text

class ABExperimentBuilder < Module def initialize(name:, test_cell:, control_cell: 'Control') const_set(:NAME, name) const_set(:CONTROL_CELL, control_cell) const_set(:TEST_CELL, test_cell) attr_reader :api, :client define_method(:initialize) do |client| @client = client @api = StitchFix::Experiments::API.new( experiment_name: self.class.const_get(:NAME), test_subject: client ) end end module ClassMethods def define! klass = self StitchFix::Experiments::API.define_experiment do name klass.const_get(:NAME) treatment klass.const_get(:CONTROL_CELL), control: true treatment klass.const_get(:TEST_CELL) end end end def included(base) base.extend ClassMethods end end

Slide 46

Slide 46 text

We did it!

Slide 47

Slide 47 text

class ABExperimentBuilder < Module def initialize(name:, test_cell:, control_cell: 'Control') const_set(:NAME, name) const_set(:CONTROL_CELL, control_cell) const_set(:TEST_CELL, test_cell) attr_reader :api, :client define_method(:initialize) do |client| @client = client @api = StitchFix::Experiments::API.new( experiment_name: self.class.const_get(:NAME), test_subject: client ) end end module ClassMethods def define! klass = self StitchFix::Experiments::API.define_experiment do name klass.const_get(:NAME) treatment klass.const_get(:CONTROL_CELL), control: true treatment klass.const_get(:TEST_CELL) end end end def included(base) base.extend ClassMethods end end

Slide 48

Slide 48 text

Extending Module? class ABExperimentBuilder < Module

Slide 49

Slide 49 text

Module is a Class module ABExperiment # … end ABExperiment = Module.new do # … end

Slide 50

Slide 50 text

Why extend Module? class ABExperimentBuilder < Module Instances of ABExperimentBuilder can be treated like a Module

Slide 51

Slide 51 text

class ABExperimentBuilder < Module def initialize(name:, test_cell:, control_cell: 'Control') const_set(:NAME, name) const_set(:CONTROL_CELL, control_cell) const_set(:TEST_CELL, test_cell) attr_reader :api, :client define_method(:initialize) do |client| @client = client @api = StitchFix::Experiments::API.new( experiment_name: self.class.const_get(:NAME), test_subject: client ) end end module ClassMethods def define! klass = self StitchFix::Experiments::API.define_experiment do name klass.const_get(:NAME) treatment klass.const_get(:CONTROL_CELL), control: true treatment klass.const_get(:TEST_CELL) allocation Time.now..6.months.from_now live true owner Client.first end end end def included(base) base.extend ClassMethods end end

Slide 52

Slide 52 text

Configuring a Module def initialize(name:, test_cell:, control_cell: 'Control') const_set(:NAME, name) const_set(:CONTROL_CELL, control_cell) const_set(:TEST_CELL, test_cell) attr_reader :api, :client define_method(:initialize) do |client| @client = client @api = StitchFix::Experiments::API.new( experiment_name: self.class.const_get(:NAME), test_subject: client ) end end

Slide 53

Slide 53 text

Configuring a Module def initialize(name:, test_cell:, control_cell: 'Control') const_set(:NAME, name) const_set(:CONTROL_CELL, control_cell) const_set(:TEST_CELL, test_cell) attr_reader :api, :client define_method(:initialize) do |client| @client = client @api = StitchFix::Experiments::API.new( experiment_name: self.class.const_get(:NAME), test_subject: client ) end end

Slide 54

Slide 54 text

Configuring a Module def initialize(name:, test_cell:, control_cell: 'Control') const_set(:NAME, name) const_set(:CONTROL_CELL, control_cell) const_set(:TEST_CELL, test_cell) attr_reader :api, :client define_method(:initialize) do |client| @client = client @api = StitchFix::Experiments::API.new( experiment_name: self.class.const_get(:NAME), test_subject: client ) end end

Slide 55

Slide 55 text

Configuring a Module def initialize(name:, test_cell:, control_cell: 'Control') const_set(:NAME, name) const_set(:CONTROL_CELL, control_cell) const_set(:TEST_CELL, test_cell) attr_reader :api, :client define_method(:initialize) do |client| @client = client @api = StitchFix::Experiments::API.new( experiment_name: self.class.const_get(:NAME), test_subject: client ) end end

Slide 56

Slide 56 text

Configuring a Module def initialize(name:, test_cell:, control_cell: 'Control') const_set(:NAME, name) const_set(:CONTROL_CELL, control_cell) const_set(:TEST_CELL, test_cell) attr_reader :api, :client define_method(:initialize) do |client| @client = client @api = StitchFix::Experiments::API.new( experiment_name: self.class.const_get(:NAME), test_subject: client ) end end ABExperimentBuilder.new( name: 'New Womens Body Shape Question', test_cell: 'New Question' )

Slide 57

Slide 57 text

Template for Building Modules module ABExperimentTemplate NAME = 'Foo' CONTROL_CELL = 'Control' TEST_CELL = 'Test' attr_reader :api, :client def initialize(client) @client = client @api = StitchFix::Experiments::API.new( experiment_name: NAME, test_subject: client ) end end ABExperimentBuilder.new( name: 'Foo', test_cell: 'Test', control_cell: 'Control' ) {

Slide 58

Slide 58 text

Template for Building Modules module ABExperimentTemplate NAME = 'Foo' CONTROL_CELL = 'Control' TEST_CELL = 'Test' attr_reader :api, :client def initialize(client) @client = client @api = StitchFix::Experiments::API.new( experiment_name: NAME, test_subject: client ) end end ABExperimentBuilder.new( name: 'Foo', test_cell: 'Test', control_cell: 'Control' ) {

Slide 59

Slide 59 text

Template for Building Modules module ABExperimentTemplate NAME = 'Foo' CONTROL_CELL = 'Control' TEST_CELL = 'Test' attr_reader :api, :client def initialize(client) @client = client @api = StitchFix::Experiments::API.new( experiment_name: NAME, test_subject: client ) end end ABExperimentBuilder.new( name: 'Foo', test_cell: 'Test', control_cell: 'Control' ) {

Slide 60

Slide 60 text

What about the class methods?

Slide 61

Slide 61 text

class ABExperimentBuilder < Module def initialize(name:, test_cell:, control_cell: 'Control') const_set(:NAME, name) const_set(:CONTROL_CELL, control_cell) const_set(:TEST_CELL, test_cell) attr_reader :api, :client define_method(:initialize) do |client| @client = client @api = StitchFix::Experiments::API.new( experiment_name: self.class.const_get(:NAME), test_subject: client ) end end module ClassMethods def define! klass = self StitchFix::Experiments::API.define_experiment do name klass.const_get(:NAME) treatment klass.const_get(:CONTROL_CELL), control: true treatment klass.const_get(:TEST_CELL) allocation Time.now..6.months.from_now live true owner Client.first end end end def included(base) base.extend ClassMethods end end

Slide 62

Slide 62 text

You don't need a Concern module ClassMethods # … end def included(base) base.extend ClassMethods end

Slide 63

Slide 63 text

The Final Result class BodyShape include ABExperimentBuilder.new( name: 'New Womens Body Shape Question', control_cell: 'Control', test_cell: 'New Question' ) def show_new_body_shape_questions? api.allocating?(time: client.create_date) && api.in_cell?(cell_name: TEST_CELL) end end

Slide 64

Slide 64 text

The Interesting Bits, Front and Center class BodyShape include ABExperimentBuilder.new( name: 'New Womens Body Shape Question', control_cell: 'Control', test_cell: 'New Question' ) def show_new_body_shape_questions? api.allocating?(time: client.create_date) && api.in_cell?(cell_name: TEST_CELL) end end

Slide 65

Slide 65 text

Module Builder Tradeoffs Good Bad Experiment Class Readability I wrote a talk to explain the code in ABExperimentBuilder Can test module builder code and be confident it works across all experiments Too much magic? It's a neat Ruby technique metaprogramming: bad Didn't change tests/interface Changing interface potentially valuable

Slide 66

Slide 66 text

Alternatives

Slide 67

Slide 67 text

Configuring via a method class BodyShape include ABExperiment ab_experiment( name: 'New Womens Body Shape Question', control_cell: 'Control', test_cell: 'New Question' ) def show_new_body_shape_questions? api.allocating?(time: client.create_date) && api.in_cell?(cell_name: TEST_CELL) end end

Slide 68

Slide 68 text

Config Method Tradeoffs Good Bad A more-familiar-looking pattern? Just as much if not more metaprogramming Have to jump through hoops to NOT define methods directly on the class If you define methods on the class, no ability to override behavior via super I didn't write a talk about that

Slide 69

Slide 69 text

Credits Chris Salzberg http://dejimata.com/2017/5/20/the-ruby-module-builder-pattern Gal Steinitz

Slide 70

Slide 70 text

Questions?

Slide 71

Slide 71 text

Applying the Module Builder Pattern Bradley.Schaefer@stitchfix.com @soulcutter