Slide 1

Slide 1 text

Ruby The Module Builder Pattern The Module Builder Pattern Chris Salzberg ^

Slide 2

Slide 2 text

About Me ● Writer, programmer, translator ● @shioyama on github, twitter, SO ● I program in Ruby ● I live in Tokyo ● I am the author of Mobility ● I blog at dejimata.com

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

We’re Hiring!

Slide 5

Slide 5 text

Introduction Introduction

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

MODULE

Slide 8

Slide 8 text

MODULE What the heck is a anyway?

Slide 9

Slide 9 text

“A self-contained component of a system, often interchangeable, which has a well- defined interface to the other components.”

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

Modular design is an attempt to combine the advantages of standardization with those of customization. - Wikipedia Modular Design

Slide 15

Slide 15 text

Elements of Module Design

Slide 16

Slide 16 text

Elements of Module Design ● Abstraction Extract common pattern into functional unit

Slide 17

Slide 17 text

Elements of Module Design ● Abstraction Extract common pattern into functional unit ● Specification Define contractual interface for unit

Slide 18

Slide 18 text

Elements of Module Design ● Abstraction Extract common pattern into functional unit ● Specification Define contractual interface for unit ● Encapsulation Hide implementation behind public interface

Slide 19

Slide 19 text

Scaling Rule The broader the scope of your abstraction the more expressive your specification must be the tighter your encapsulation must be

Slide 20

Slide 20 text

Modularity in Ruby ● Module (modularity through inclusion) – collection of methods and constants – callback method (included) ● Class (modularity through inheritance) – initializer, methods, constants – callback method (inherited)

Slide 21

Slide 21 text

Gems are also Modules! ● Self-contained component – collection of Classes, Modules, Objects and code ● Well-defined interface to other components – gem/ruby dependencies (gemspec), class/object interfaces ● As flexible as its components allow it to be gem install degica

Slide 22

Slide 22 text

A Modular Gem: Mobility ● Pluggable “Translation Framework” ● Abstracts storage strategies into “backends” ● Supports a flexible collection of plugins Plugins Backend

Slide 23

Slide 23 text

The Ruby Module module MyModule def foo “foo” end def self.included(base) puts “included in #{base.name}” end end module MyModule def foo “foo” end def self.included(base) puts “included in #{base.name}” end end class A include MyModule def foo super + “bar” end end # prints “included in A” a = A.new a.foo #=> “foobar” class A include MyModule def foo super + “bar” end end # prints “included in A” a = A.new a.foo #=> “foobar”

Slide 24

Slide 24 text

Flexible? Nope. module MyModule(config) def foo config end end module MyModule(config) def foo config end end

Slide 25

Slide 25 text

Flexible? Nope. module MyModule(config) #=> SyntaxError: unexpected '\n'... def foo config end end module MyModule(config) #=> SyntaxError: unexpected '\n'... def foo config end end Class A include MyModule(config) end #=> NoMethodError: undefined method `MyModule' for A:Class Class A include MyModule(config) end #=> NoMethodError: undefined method `MyModule' for A:Class

Slide 26

Slide 26 text

Encapsulated? Nope. module MyModule def foo “foo” + bar end private def bar “bar” end end module MyModule def foo “foo” + bar end private def bar “bar” end end

Slide 27

Slide 27 text

Encapsulated? Nope. module MyModule def foo “foo” + bar end private def bar “bar” end end module MyModule def foo “foo” + bar end private def bar “bar” end end class A include MyModule def bar “baz” end end a = A.new a.foo #=> “foobaz” class A include MyModule def bar “baz” end end a = A.new a.foo #=> “foobaz”

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

Solution?

Slide 30

Slide 30 text

Metaprogramming!!!

Slide 31

Slide 31 text

Building with Modules Building with Modules

Slide 32

Slide 32 text

module Addable def +(point) self.class.new(point.x + x, point.y + y) end end module Addable def +(point) self.class.new(point.x + x, point.y + y) end end A Simple Module Point = Struct.new(:x, :y) Point.include Addable p1 = Point.new(1, 2) p2 = Point.new(2, 3) p1 + p2 #=> # Point = Struct.new(:x, :y) Point.include Addable p1 = Point.new(1, 2) p2 = Point.new(2, 3) p1 + p2 #=> #

Slide 33

Slide 33 text

class Point < Struct.new(:x, :y) include Addable def +(point) puts “Enter Adder...” super.tap { “Exit Adder...” } end end p1 = Point.new(1, 2) p2 = Point.new(2, 3) p1 + p2 Enter Adder... Exit Adder... #=> # class Point < Struct.new(:x, :y) include Addable def +(point) puts “Enter Adder...” super.tap { “Exit Adder...” } end end p1 = Point.new(1, 2) p2 = Point.new(2, 3) p1 + p2 Enter Adder... Exit Adder... #=> # Module Composition

Slide 34

Slide 34 text

Problem: Abstraction ● Module only defines addition for fixed pair of accessors x and y ● Can we abstract our module so that defines addition on any set of accessors? ● Can we do it in a way that supports method composition?

Slide 35

Slide 35 text

module AdderDefiner def define_adder(*keys) define_method :+ do |other| self.class.new( *(keys.map { |key| send(key) + other.send(key) }) ) end end end module AdderDefiner def define_adder(*keys) define_method :+ do |other| self.class.new( *(keys.map { |key| send(key) + other.send(key) }) ) end end end Bootstrap Methods ● Define a bootstrap method which builds instance methods dynamically from arguments

Slide 36

Slide 36 text

class LineItem < Struct.new(:amount, :tax) extend AdderDefiner define_adder(:amount, :tax) end class LineItem < Struct.new(:amount, :tax) extend AdderDefiner define_adder(:amount, :tax) end Bootstrapping a Method

Slide 37

Slide 37 text

class LineItem < Struct.new(:amount, :tax) extend AdderDefiner define_adder(:amount, :tax) end class LineItem < Struct.new(:amount, :tax) extend AdderDefiner define_adder(:amount, :tax) end Bootstrapping a Method def +(other) self.class.new(amount + other.amount, tax + other.tax) end

Slide 38

Slide 38 text

class LineItem < Struct.new(:amount, :tax) extend AdderDefiner define_adder(:amount, :tax) end class LineItem < Struct.new(:amount, :tax) extend AdderDefiner define_adder(:amount, :tax) end Bootstrapping a Method l1 = LineItem.new(9.99, 1.50) l2 = LineItem.new(15.99, 2.40) l1 + l2 #=> # l1 = LineItem.new(9.99, 1.50) l2 = LineItem.new(15.99, 2.40) l1 + l2 #=> #

Slide 39

Slide 39 text

class LineItem < Struct.new(:amount, :tax) extend AdderDefiner define_adder(:amount, :tax) def +(other) puts "Enter Adder..." super.tap { puts "Exit Adder..." } end end class LineItem < Struct.new(:amount, :tax) extend AdderDefiner define_adder(:amount, :tax) def +(other) puts "Enter Adder..." super.tap { puts "Exit Adder..." } end end Bootstrapping + Composition

Slide 40

Slide 40 text

class LineItem < Struct.new(:amount, :tax) extend AdderDefiner define_adder(:amount, :tax) def +(other) puts "Enter Adder..." super.tap { puts "Exit Adder..." } end end class LineItem < Struct.new(:amount, :tax) extend AdderDefiner define_adder(:amount, :tax) def +(other) puts "Enter Adder..." super.tap { puts "Exit Adder..." } end end Bootstrapping + Composition l1 = LineItem.new(9.99, 1.50) l2 = LineItem.new(15.99, 2.40) l1 + l2 #=> ??? l1 = LineItem.new(9.99, 1.50) l2 = LineItem.new(15.99, 2.40) l1 + l2 #=> ???

Slide 41

Slide 41 text

NoMethodError super: no superclass method `+' for #

Slide 42

Slide 42 text

class LineItem < Struct.new(:amount, :tax) extend AdderDefiner define_adder(:amount, :tax) def +(other) puts "Enter Adder..." super.tap { puts "Exit Adder..." } end end class LineItem < Struct.new(:amount, :tax) extend AdderDefiner define_adder(:amount, :tax) def +(other) puts "Enter Adder..." super.tap { puts "Exit Adder..." } end end Bootstrapping + Composition

Slide 43

Slide 43 text

class LineItem < Struct.new(:amount, :tax) extend AdderDefiner def +(other) self.class.new(amount + other.amount, tax + other.tax) end def +(other) puts "Enter Adder..." super.tap { puts "Exit Adder..." } end end class LineItem < Struct.new(:amount, :tax) extend AdderDefiner def +(other) self.class.new(amount + other.amount, tax + other.tax) end def +(other) puts "Enter Adder..." super.tap { puts "Exit Adder..." } end end Bootstrapping + Composition

Slide 44

Slide 44 text

class LineItem < Struct.new(:amount, :tax) extend AdderDefiner def +(other) self.class.new(amount + other.amount, tax + other.tax) end def +(other) puts "Enter Adder..." super.tap { puts "Exit Adder..." } end end class LineItem < Struct.new(:amount, :tax) extend AdderDefiner def +(other) self.class.new(amount + other.amount, tax + other.tax) end def +(other) puts "Enter Adder..." super.tap { puts "Exit Adder..." } end end Bootstrapping + Composition

Slide 45

Slide 45 text

Configurable & Composable

Slide 46

Slide 46 text

Configurable & Composable Define methods directly on extending class

Slide 47

Slide 47 text

Configurable & Composable Define methods directly on extending class Define methods on module included into extending class

Slide 48

Slide 48 text

module AdderIncluder def define_adder(*keys) adder = Module.new do define_method :+ do |other| self.class.new( *(keys.map { |key| send(key) + other.send(key) }) ) end end include adder end end module AdderIncluder def define_adder(*keys) adder = Module.new do define_method :+ do |other| self.class.new( *(keys.map { |key| send(key) + other.send(key) }) ) end end include adder end end Module Boostrapper

Slide 49

Slide 49 text

module AdderIncluder def define_adder(*keys) adder = Module.new do define_method :+ do |other| self.class.new( *(keys.map { |key| send(key) + other.send(key) }) ) end end include adder end end module AdderIncluder def define_adder(*keys) adder = Module.new do define_method :+ do |other| self.class.new( *(keys.map { |key| send(key) + other.send(key) }) ) end end include adder end end Module Boostrapper AdderDefiner#define_adder

Slide 50

Slide 50 text

module AdderIncluder def define_adder(*keys) adder = Module.new do define_method :+ do |other| self.class.new( *(keys.map { |key| send(key) + other.send(key) }) ) end end include adder end end module AdderIncluder def define_adder(*keys) adder = Module.new do define_method :+ do |other| self.class.new( *(keys.map { |key| send(key) + other.send(key) }) ) end end include adder end end Module Boostrapper Anonymous module

Slide 51

Slide 51 text

class LineItem < Struct.new(:amount, :tax) extend AdderIncluder define_adder(:amount, :tax) def +(other) puts "Enter Adder..." super.tap { puts "Exit Adder..." } end end class LineItem < Struct.new(:amount, :tax) extend AdderIncluder define_adder(:amount, :tax) def +(other) puts "Enter Adder..." super.tap { puts "Exit Adder..." } end end Module Composition

Slide 52

Slide 52 text

class LineItem < Struct.new(:amount, :tax) extend AdderIncluder adder = Module.new do define_method :+ do |other| self.class.new( *(keys.map { |key| send(key) + other.send(key) }) ) end end include adder def +(other) puts "Enter Adder..." super.tap { puts "Exit Adder..." } end end class LineItem < Struct.new(:amount, :tax) extend AdderIncluder adder = Module.new do define_method :+ do |other| self.class.new( *(keys.map { |key| send(key) + other.send(key) }) ) end end include adder def +(other) puts "Enter Adder..." super.tap { puts "Exit Adder..." } end end Module Composition

Slide 53

Slide 53 text

class LineItem < Struct.new(:amount, :tax) extend AdderIncluder adder = Module.new do define_method :+ do |other| self.class.new( *(keys.map { |key| send(key) + other.send(key) }) ) end end include adder def +(other) puts "Enter Adder..." super.tap { puts "Exit Adder..." } end end class LineItem < Struct.new(:amount, :tax) extend AdderIncluder adder = Module.new do define_method :+ do |other| self.class.new( *(keys.map { |key| send(key) + other.send(key) }) ) end end include adder def +(other) puts "Enter Adder..." super.tap { puts "Exit Adder..." } end end Module Composition LineItem.ancestors #=> [LineItem, #, ...

Slide 54

Slide 54 text

class LineItem < Struct.new(:amount, :tax) extend AdderIncluder define_adder(:amount, :tax) def +(other) puts "Enter Adder..." super.tap { puts "Exit Adder..." } end end class LineItem < Struct.new(:amount, :tax) extend AdderIncluder define_adder(:amount, :tax) def +(other) puts "Enter Adder..." super.tap { puts "Exit Adder..." } end end Now it Works l1 = LineItem.new(9.99, 1.50) l2 = LineItem.new(15.99, 2.40) l1 + l2 Enter Adder... Exit Adder... #=> # l1 = LineItem.new(9.99, 1.50) l2 = LineItem.new(15.99, 2.40) l1 + l2 Enter Adder... Exit Adder... #=> #

Slide 55

Slide 55 text

In the Wild ● Technique of building anonymous modules to include in classes is widely used (e.g. Rails)

Slide 56

Slide 56 text

Recap Addable Defines + method on fixed variables x and y. AdderDefiner Adds class method define_adder which defines + method on configurable set of variables. AdderIncluder Also adds class method define_adder which defines + method, but in a composable way using anonymous module.

Slide 57

Slide 57 text

Is this Modular? ● Boostrap method(s) extended as class methods ● Instance methods defined on anonymous module ● Configuration state stored on including class, referenced by methods on anonymous module define_foo define_foo extend def foo(*args) self.class.foo end def foo(*args) self.class.foo end include MyClass @foo Module.new

Slide 58

Slide 58 text

The Module Builder The Module Builder

Slide 59

Slide 59 text

Classes, Modules & Objects

Slide 60

Slide 60 text

Classes, Modules & Objects

Slide 61

Slide 61 text

class Module Object Model Revisited ● Module is a Class is a

Slide 62

Slide 62 text

class ModuleBuilder < Module Object Model Revisited ● Module is a Class ● A class can have subclasses

Slide 63

Slide 63 text

class ModuleBuilder < Module def initialize(method_name) Object Model Revisited ● Module is a Class ● A class can have subclasses ● (Sub)classes can define methods

Slide 64

Slide 64 text

class ModuleBuilder < Module def initialize(method_name) define_method method_name do Object Model Revisited ● Module is a Class ● A class can have subclasses ● (Sub)classes can define methods ● Modules can also define methods

Slide 65

Slide 65 text

Elements of a Module Builder class Attributes < Module def initialize(*attribute_names) @names = attribute_names # ... # ... end def included(klass) @names.each do |name| define_method name do # ... end # ... end end end class Post include Attributes.new(:title, :content) end class Attributes < Module def initialize(*attribute_names) @names = attribute_names # ... # ... end def included(klass) @names.each do |name| define_method name do # ... end # ... end end end class Post include Attributes.new(:title, :content) end Subclass Module Define module methods in initialize or included hook Include instance of Module subclass in other classes Configure in initializer

Slide 66

Slide 66 text

Configure in an Initializer class Attributes < Module def initialize(*attribute_names) @names = attribute_names # ... end # ... end class Post include Attributes.new(:title, :content) end class Attributes < Module def initialize(*attribute_names) @names = attribute_names # ... end # ... end class Post include Attributes.new(:title, :content) end

Slide 67

Slide 67 text

Encapsulate in a Closure class Attributes < Module # ... def included(klass) @names.each do |name| define_method name do locale = ... mobility_backend_for(name).read(locale) end end end end class Attributes < Module # ... def included(klass) @names.each do |name| define_method name do locale = ... mobility_backend_for(name).read(locale) end end end end

Slide 68

Slide 68 text

class AdderBuilder < Module def initialize(*keys) define_method :+ do |other| self.class.new( *(keys.map { |key| send(key) + other.send(key) }) ) end end end class AdderBuilder < Module def initialize(*keys) define_method :+ do |other| self.class.new( *(keys.map { |key| send(key) + other.send(key) }) ) end end end Addable as Module Builder

Slide 69

Slide 69 text

class AdderBuilder < Module def initialize(*keys) define_method :+ do |other| self.class.new( *(keys.map { |key| send(key) + other.send(key) }) ) end end end class AdderBuilder < Module def initialize(*keys) define_method :+ do |other| self.class.new( *(keys.map { |key| send(key) + other.send(key) }) ) end end end Addable as Module Builder AdderDefiner#define_adder

Slide 70

Slide 70 text

Encapsulated Module Building class LineItem < Struct.new(:amount, :tax) include AdderBuilder.new(:amount, :tax) end l1 = LineItem.new(9.99, 1.50) l2 = LineItem.new(15.99, 2.40) l1 + l2 #=> # class LineItem < Struct.new(:amount, :tax) include AdderBuilder.new(:amount, :tax) end l1 = LineItem.new(9.99, 1.50) l2 = LineItem.new(15.99, 2.40) l1 + l2 #=> # ● No bootstrap method added to class ● No private methods mixed into class

Slide 71

Slide 71 text

Composition Too class LineItem < Struct.new(:amount, :tax) include (Module.new do define_method :+ do |other| self.class.new( *(keys.map { |key| send(key) + other.send(key) }) ) end end def +(other) puts "Enter Adder..." super.tap { puts "Exit Adder..." } end end class LineItem < Struct.new(:amount, :tax) include (Module.new do define_method :+ do |other| self.class.new( *(keys.map { |key| send(key) + other.send(key) }) ) end end def +(other) puts "Enter Adder..." super.tap { puts "Exit Adder..." } end end

Slide 72

Slide 72 text

Composition Too class LineItem < Struct.new(:amount, :tax) include AdderBuilder.new(:amount, :tax) def +(other) puts "Enter Adder..." super.tap { puts "Exit Adder..." } end end class LineItem < Struct.new(:amount, :tax) include AdderBuilder.new(:amount, :tax) def +(other) puts "Enter Adder..." super.tap { puts "Exit Adder..." } end end LineItem.ancestors [LineItem, #, ... ] It has a name!

Slide 73

Slide 73 text

Applying the Pattern Applying the Pattern

Slide 74

Slide 74 text

The Module Builder Pattern broader abstraction more expressive specification tighter encapsulation

Slide 75

Slide 75 text

Applying the Pattern module Alchemist class ModuleBuilder def initialize category @category = category end def build build_module do |category_module| define_inspect_method(category_module) define_unit_methods(category_module) end end private attr_reader :category def build_module(&block) Module.new do def self.define_unit_method(names) names.each do |name| define_method(name.to_sym) { Alchemist.measure self, name.to_sym } end end end.tap &block end def define_inspect_method(category_module) category_module.class_eval %(def self.inspect() "#" end) end def define_unit_methods(category_module) category_module.class_eval category_methods end def library Alchemist.library end def category_methods unit_names.map do |name| %(define_method("#{name}") { Alchemist.measure self, :#{name} }) + "\n" + prefixed_methods(name) end.join("\n") end def unit_names library.unit_names(category) end def prefixes_for(name) if library.si_units.include?(name.to_s) library.unit_prefixes.keys else [] end end def prefixed_methods(name) prefixes_for(name).map do |prefix| %(define_method("#{prefix}#{name}") { Alchemist.measure_prefixed self, :#{prefix}, :#{name} }) end.join("\n") end end end module Alchemist class ModuleBuilder def initialize category @category = category end def build build_module do |category_module| define_inspect_method(category_module) define_unit_methods(category_module) end end private attr_reader :category def build_module(&block) Module.new do def self.define_unit_method(names) names.each do |name| define_method(name.to_sym) { Alchemist.measure self, name.to_sym } end end end.tap &block end def define_inspect_method(category_module) category_module.class_eval %(def self.inspect() "#" end) end def define_unit_methods(category_module) category_module.class_eval category_methods end def library Alchemist.library end def category_methods unit_names.map do |name| %(define_method("#{name}") { Alchemist.measure self, :#{name} }) + "\n" + prefixed_methods(name) end.join("\n") end def unit_names library.unit_names(category) end def prefixes_for(name) if library.si_units.include?(name.to_s) library.unit_prefixes.keys else [] end end def prefixed_methods(name) prefixes_for(name).map do |prefix| %(define_method("#{prefix}#{name}") { Alchemist.measure_prefixed self, :#{prefix}, :#{name} }) end.join("\n") end end end BEFORE

Slide 76

Slide 76 text

Applying the Pattern module Alchemist class ModuleBuilder def initialize category @category = category end def build build_module do |category_module| define_inspect_method(category_module) define_unit_methods(category_module) end end private attr_reader :category def build_module(&block) Module.new do def self.define_unit_method(names) names.each do |name| define_method(name.to_sym) { Alchemist.measure self, name.to_sym } end end end.tap &block end def define_inspect_method(category_module) category_module.class_eval %(def self.inspect() "#" end) end def define_unit_methods(category_module) category_module.class_eval category_methods end def library Alchemist.library end def category_methods unit_names.map do |name| %(define_method("#{name}") { Alchemist.measure self, :#{name} }) + "\n" + prefixed_methods(name) end.join("\n") end def unit_names library.unit_names(category) end def prefixes_for(name) if library.si_units.include?(name.to_s) library.unit_prefixes.keys else [] end end def prefixed_methods(name) prefixes_for(name).map do |prefix| %(define_method("#{prefix}#{name}") { Alchemist.measure_prefixed self, :#{prefix}, :#{name} }) end.join("\n") end end end module Alchemist class ModuleBuilder def initialize category @category = category end def build build_module do |category_module| define_inspect_method(category_module) define_unit_methods(category_module) end end private attr_reader :category def build_module(&block) Module.new do def self.define_unit_method(names) names.each do |name| define_method(name.to_sym) { Alchemist.measure self, name.to_sym } end end end.tap &block end def define_inspect_method(category_module) category_module.class_eval %(def self.inspect() "#" end) end def define_unit_methods(category_module) category_module.class_eval category_methods end def library Alchemist.library end def category_methods unit_names.map do |name| %(define_method("#{name}") { Alchemist.measure self, :#{name} }) + "\n" + prefixed_methods(name) end.join("\n") end def unit_names library.unit_names(category) end def prefixes_for(name) if library.si_units.include?(name.to_s) library.unit_prefixes.keys else [] end end def prefixed_methods(name) prefixes_for(name).map do |prefix| %(define_method("#{prefix}#{name}") { Alchemist.measure_prefixed self, :#{prefix}, :#{name} }) end.join("\n") end end end module Alchemist class ModuleBuilder < Module def initialize category define_inspect_method(category) define_unit_methods(category) end def define_unit_method(names) names.each do |name| define_method(name.to_sym) { Alchemist.measure self, name.to_sym } end end private def define_inspect_method(category) define_method :inspect do "#" end end def define_unit_methods(category) unit_names(category).map do |name| define_method name do Alchemist.measure self, name.to_sym end prefixes_for(name).map do |prefix| define_method "#{prefix}#{name}" do Alchemist.measure_prefixed self, prefix.to_sym, name.to_sym end end end end def library Alchemist.library end def unit_names(category) library.unit_names(category) end def prefixes_for(name) if library.si_units.include?(name.to_s) library.unit_prefixes.keys else [] end end end end module Alchemist class ModuleBuilder < Module def initialize category define_inspect_method(category) define_unit_methods(category) end def define_unit_method(names) names.each do |name| define_method(name.to_sym) { Alchemist.measure self, name.to_sym } end end private def define_inspect_method(category) define_method :inspect do "#" end end def define_unit_methods(category) unit_names(category).map do |name| define_method name do Alchemist.measure self, name.to_sym end prefixes_for(name).map do |prefix| define_method "#{prefix}#{name}" do Alchemist.measure_prefixed self, prefix.to_sym, name.to_sym end end end end def library Alchemist.library end def unit_names(category) library.unit_names(category) end def prefixes_for(name) if library.si_units.include?(name.to_s) library.unit_prefixes.keys else [] end end end end BEFORE AFTER

Slide 77

Slide 77 text

Applying the Pattern module Alchemist class ModuleBuilder def initialize category @category = category end def build build_module do |category_module| define_inspect_method(category_module) define_unit_methods(category_module) end end private attr_reader :category def build_module(&block) Module.new do def self.define_unit_method(names) names.each do |name| define_method(name.to_sym) { Alchemist.measure self, name.to_sym } end end end.tap &block end def define_inspect_method(category_module) category_module.class_eval %(def self.inspect() "#" end) end def define_unit_methods(category_module) category_module.class_eval category_methods end def library Alchemist.library end def category_methods unit_names.map do |name| %(define_method("#{name}") { Alchemist.measure self, :#{name} }) + "\n" + prefixed_methods(name) end.join("\n") end def unit_names library.unit_names(category) end def prefixes_for(name) if library.si_units.include?(name.to_s) library.unit_prefixes.keys else [] end end def prefixed_methods(name) prefixes_for(name).map do |prefix| %(define_method("#{prefix}#{name}") { Alchemist.measure_prefixed self, :#{prefix}, :#{name} }) end.join("\n") end end end module Alchemist class ModuleBuilder def initialize category @category = category end def build build_module do |category_module| define_inspect_method(category_module) define_unit_methods(category_module) end end private attr_reader :category def build_module(&block) Module.new do def self.define_unit_method(names) names.each do |name| define_method(name.to_sym) { Alchemist.measure self, name.to_sym } end end end.tap &block end def define_inspect_method(category_module) category_module.class_eval %(def self.inspect() "#" end) end def define_unit_methods(category_module) category_module.class_eval category_methods end def library Alchemist.library end def category_methods unit_names.map do |name| %(define_method("#{name}") { Alchemist.measure self, :#{name} }) + "\n" + prefixed_methods(name) end.join("\n") end def unit_names library.unit_names(category) end def prefixes_for(name) if library.si_units.include?(name.to_s) library.unit_prefixes.keys else [] end end def prefixed_methods(name) prefixes_for(name).map do |prefix| %(define_method("#{prefix}#{name}") { Alchemist.measure_prefixed self, :#{prefix}, :#{name} }) end.join("\n") end end end module Alchemist class ModuleBuilder < Module def initialize category define_inspect_method(category) define_unit_methods(category) end def define_unit_method(names) names.each do |name| define_method(name.to_sym) { Alchemist.measure self, name.to_sym } end end private def define_inspect_method(category) define_method :inspect do "#" end end def define_unit_methods(category) unit_names(category).map do |name| define_method name do Alchemist.measure self, name.to_sym end prefixes_for(name).map do |prefix| define_method "#{prefix}#{name}" do Alchemist.measure_prefixed self, prefix.to_sym, name.to_sym end end end end def library Alchemist.library end def unit_names(category) library.unit_names(category) end def prefixes_for(name) if library.si_units.include?(name.to_s) library.unit_prefixes.keys else [] end end end end module Alchemist class ModuleBuilder < Module def initialize category define_inspect_method(category) define_unit_methods(category) end def define_unit_method(names) names.each do |name| define_method(name.to_sym) { Alchemist.measure self, name.to_sym } end end private def define_inspect_method(category) define_method :inspect do "#" end end def define_unit_methods(category) unit_names(category).map do |name| define_method name do Alchemist.measure self, name.to_sym end prefixes_for(name).map do |prefix| define_method "#{prefix}#{name}" do Alchemist.measure_prefixed self, prefix.to_sym, name.to_sym end end end end def library Alchemist.library end def unit_names(category) library.unit_names(category) end def prefixes_for(name) if library.si_units.include?(name.to_s) library.unit_prefixes.keys else [] end end end end BEFORE AFTER

Slide 78

Slide 78 text

Compact and Versatile class GeoLocation include Dry::Equalizer.new(:latitude, :longitude) attr_reader :latitude, :longitude def initialize(latitude, longitude) @latitude, @longitude = latitude, longitude end end class GeoLocation include Dry::Equalizer.new(:latitude, :longitude) attr_reader :latitude, :longitude def initialize(latitude, longitude) @latitude, @longitude = latitude, longitude end end def initialize(*keys) @keys = keys define_methods freeze end def initialize(*keys) @keys = keys define_methods freeze end def define_methods define_cmp_method define_hash_method define_inspect_method end def define_methods define_cmp_method define_hash_method define_inspect_method end

Slide 79

Slide 79 text

Compact and Versatile point_a = GeoLocation.new(1, 2) point_b = GeoLocation.new(1, 2) point_c = GeoLocation.new(2, 2) point_a.inspect #=> "#" point_a == point_b # => true point_a.hash == point_b.hash # => true point_a.eql?(point_b) # => true point_a.equal?(point_b) # => false point_a == point_c # => false point_a.hash == point_c.hash # => false point_a.eql?(point_c) # => false point_a.equal?(point_c) # => false point_a = GeoLocation.new(1, 2) point_b = GeoLocation.new(1, 2) point_c = GeoLocation.new(2, 2) point_a.inspect #=> "#" point_a == point_b # => true point_a.hash == point_b.hash # => true point_a.eql?(point_b) # => true point_a.equal?(point_b) # => false point_a == point_c # => false point_a.hash == point_c.hash # => false point_a.eql?(point_c) # => false point_a.equal?(point_c) # => false

Slide 80

Slide 80 text

Flexible and Extendable Title Backend # # read(:fr) Storage Strategy ... class Post extend Mobility translates :title, fallbacks: { fr: :en }, default: ‘No Translation’ end I18n.locale = :fr post = Post.new post.title #=> post.title_backend.read(:fr) class Post extend Mobility translates :title, fallbacks: { fr: :en }, default: ‘No Translation’ end I18n.locale = :fr post = Post.new post.title #=> post.title_backend.read(:fr) ● Mobility uses Module Builders to: – Customize translation accessors per-attribute – Add attribute-specific customization to models

Slide 81

Slide 81 text

But umm, is this... okay?

Slide 82

Slide 82 text

Don’t be Afraid! ● A Module Builder is: – Just a subclass – Just configuring an instance – Just defining methods on a module

Slide 83

Slide 83 text

But most of all, a Module Builder is just...

Slide 84

Slide 84 text

But most of all, a Module Builder is just... PLAIN OLD RUBY RUBY

Slide 85

Slide 85 text

Thanks! Thanks!