Slide 1

Slide 1 text

An Introduction to ClassyEnum and Friends For Indubitably Classy Ruby Developers

Slide 2

Slide 2 text

Classy Indeed. @beerlington beerlington.com Sir Beerlington III

Slide 3

Slide 3 text

Burlington Ruby Conference August 3 - 4 @btvrubyconf burlingtonruby.com

Slide 4

Slide 4 text

ClassyEnum

Slide 5

Slide 5 text

What is an Enum? aka “enumerated type” or “enumeration” Data type consisting of named values: ‘elements’, ‘members’ or ‘enumerators’ Collection of related constants Each value is different, represents one thing

Slide 6

Slide 6 text

Pseudo Examples Deck of cards Chess pieces Color palette Priority levels

Slide 7

Slide 7 text

Why use Enums? Makes code more self-documenting Easy to change values in the future Reduces errors by mistyping values

Slide 8

Slide 8 text

Exploring Enums in Ruby

Slide 9

Slide 9 text

class States NEW_ENGLAND = [ :connecticut, :maine, :massachusetts, :new_hampshire, :rhode_island, :vermont ] end

Slide 10

Slide 10 text

States::NEW_ENGLAND.last => :vermont States::NEW_ENGLAND.first => :connecticut States::NEW_ENGLAND.size => 6

Slide 11

Slide 11 text

Enumerable Similar name, but not an enumerated type Can extend classes to mimic enumerable, or “collection” behavior [1,2,3].first => 1

Slide 12

Slide 12 text

class States extend Enumerable NEW_ENGLAND = [:connecticut, :maine, :massachusetts, :new_hampshire, :rhode_island, :vermont] def self.each NEW_ENGLAND.each {|state| yield state } end end Must yield each successive element!

Slide 13

Slide 13 text

States.first => :connecticut States.count => 6 States.select {|s| s.to_s.start_with? 'm' } => [:maine, :massachusetts]

Slide 14

Slide 14 text

Also similar name, but not an enumerated type Adds convenience methods to iterate over a collection Enumerator [1,2,3].each.class => Enumerator

Slide 15

Slide 15 text

states = States.to_enum => # states.next => :connecticut states.next => :maine ...skip a few... states.next => :vermont states.next StopIteration: iteration reached an end

Slide 16

Slide 16 text

Enums in Active Record Typically used as validation or database lookup tables Need to persist a value that represents a member String or built-in enum Integer

Slide 17

Slide 17 text

class User < ActiveRecord::Base validates_inclusion_of :state, in: States end user = User.new(state: :vermont) user.valid? => true user.state = 'OMG TEXAS' user.valid? # => false

Slide 18

Slide 18 text

Database Lookup Tables Painful to manage data Usually don’t really need to be in the database joins, joins, joins

Slide 19

Slide 19 text

ClassyEnum

Slide 20

Slide 20 text

Classes Object Oriented Programming Polymorphism Composition Factory Patterns

Slide 21

Slide 21 text

Adding Logic class User < ActiveRecord::Base def lives_in_awesome_state? state == 'vermont' end end user = User.new(state: 'maine') user.lives_in_awesome_state? # => false user = User.new(state: 'vermont') user.lives_in_awesome_state? # => true

Slide 22

Slide 22 text

class User < ActiveRecord::Base def lives_in_awesome_state? state == 'vermont' || state == 'maine' end end user = User.new(state: 'maine') user.lives_in_awesome_state? # => true user = User.new(state: 'vermont') user.lives_in_awesome_state? # => true

Slide 23

Slide 23 text

Should a user know if a state is awesome?

Slide 24

Slide 24 text

What if a state knew if it was awesome?

Slide 25

Slide 25 text

What if a state knew if it was awesome? user.state.awesome? # => true

Slide 26

Slide 26 text

class User < ActiveRecord::Base def state State.new(super) end end class State def initialize(name) @name = name end def awesome? @name == 'vermont' || @name == 'maine' end end get value from database

Slide 27

Slide 27 text

What else can be Improved? An individual state doesn’t know whether it’s awesome or not Conditional logic is harder to test and change

Slide 28

Slide 28 text

Polymorphism Objects with the same interface, but different functionality (contract) Expect all objects to behave differently when given the same instructions Circle.draw Square.draw Triangle.draw

Slide 29

Slide 29 text

Polymorphism Inheritance Single Table Inheritance in Active Record

Slide 30

Slide 30 text

class User < ActiveRecord::Base def state super.camelize.constantize.new end end class State def awesome? false end end class Vermont < State def awesome? true end end false by default override in subclasses composition + factory

Slide 31

Slide 31 text

user = User.new(state: 'vermont') user.state.awesome? # => true user.state = 'maine' user.state.awesome? # => false

Slide 32

Slide 32 text

Takeaways Reduce branching (if/else, conditions) Easier to test and change Statistically less bugs Pain to reproduce this logic everywhere you need it

Slide 33

Slide 33 text

Polymorphism + Composition + Simple Factory + Validations

Slide 34

Slide 34 text

Introducing Classy Enum

Slide 35

Slide 35 text

Introducing Classy Enum

Slide 36

Slide 36 text

What Does it Do? Everything I just discussed Wraps your enum values in Ruby classes Isolates behavior and properties Tons of other fun stuff...

Slide 37

Slide 37 text

class State < ClassyEnum::Base end class State::Vermont < State end class User < ActiveRecord::Base classy_enum_attr :state end declaration in AR model enum definitions

Slide 38

Slide 38 text

class State < ClassyEnum::Base def awesome? false end end class State::Vermont < State def awesome? true end end

Slide 39

Slide 39 text

A state still knows if it is awesome. user = User.new(state: 'vermont') user.state.awesome? # => true

Slide 40

Slide 40 text

Active Record Validation alarm = Alarm.new alarm.priority = 'low' alarm.valid? => true alarm.priority = 'lwo' alarm.valid? => false alarm.errors[:priority] => ["is not included in the list"]

Slide 41

Slide 41 text

Classy Enumerables State.count => 6 State.first => connecticut State.max => vermont State.max.text => "Vermont" State.select_options => [["Connecticut", "connecticut"], ["Maine", "maine"], ["Massachusetts", "massachusetts"]...]

Slide 42

Slide 42 text

Internationalization es: classy_enum: priority: low: 'Bajo' medium: 'Medio' high: 'Alto' @alarm.priority = :low @alarm.priority.text # => 'Low' I18n.locale = :es @alarm.priority.text # => 'Bajo'

Slide 43

Slide 43 text

Forms <%= form_for(@alarm) do |f| %>
<%= f.label :priority %>
<%= f.select :priority, Priority.select_options %>
<%= f.submit %>
<% end %>

Slide 44

Slide 44 text

Enum Generator $ rails g classy_enum State connecticut maine massachusetts... create app/enums create app/enums/state.rb class State < ClassyEnum::Base end class State::Connecticut < State end class State::Maine < State end class State::Massachusetts < State end ...

Slide 45

Slide 45 text

Live code!

Slide 46

Slide 46 text

Live code!

Slide 47

Slide 47 text

Alternatives to Classy Enum Non-classy enums State machines

Slide 48

Slide 48 text

Enumerize gem class User < ActiveRecord::Base extend Enumerize enumerize :role, in: [:user, :admin], default: :user end user = User.new user.role.user? # => true simple interface, but benefit of classes is lost

Slide 49

Slide 49 text

State Machines Preferred over static values for states Provides hooks between/around state changes

Slide 50

Slide 50 text

class Vehicle attr_accessor :seatbelt_on, :time_used, :auto_shop_busy state_machine :state, :initial => :parked do after_transition :on => :crash, :do => :tow after_transition :on => :repair, :do => :fix after_transition any => :parked do |vehicle, transition| vehicle.seatbelt_on = false end end end

Slide 51

Slide 51 text

No content

Slide 52

Slide 52 text

No content

Slide 53

Slide 53 text

Conclusion Worldwide Happiness # of projects using Classy Enum