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

Introduction to ClassyEnum and Friends

Introduction to ClassyEnum and Friends

Presented at Burlington Ruby's June 2013 meet up

Peter Brown

June 26, 2013
Tweet

More Decks by Peter Brown

Other Decks in Programming

Transcript

  1. 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
  2. Why use Enums? Makes code more self-documenting Easy to change

    values in the future Reduces errors by mistyping values
  3. Enumerable Similar name, but not an enumerated type Can extend

    classes to mimic enumerable, or “collection” behavior [1,2,3].first => 1
  4. 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!
  5. Also similar name, but not an enumerated type Adds convenience

    methods to iterate over a collection Enumerator [1,2,3].each.class => Enumerator
  6. states = States.to_enum => #<Enumerator: States:each> states.next => :connecticut states.next

    => :maine ...skip a few... states.next => :vermont states.next StopIteration: iteration reached an end
  7. 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
  8. 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
  9. Database Lookup Tables Painful to manage data Usually don’t really

    need to be in the database joins, joins, joins
  10. 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
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. Takeaways Reduce branching (if/else, conditions) Easier to test and change

    Statistically less bugs Pain to reproduce this logic everywhere you need it
  17. What Does it Do? Everything I just discussed Wraps your

    enum values in Ruby classes Isolates behavior and properties Tons of other fun stuff...
  18. 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
  19. class State < ClassyEnum::Base def awesome? false end end class

    State::Vermont < State def awesome? true end end
  20. A state still knows if it is awesome. user =

    User.new(state: 'vermont') user.state.awesome? # => true
  21. 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"]
  22. Classy Enumerables State.count => 6 State.first => connecticut State.max =>

    vermont State.max.text => "Vermont" State.select_options => [["Connecticut", "connecticut"], ["Maine", "maine"], ["Massachusetts", "massachusetts"]...]
  23. 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'
  24. Forms <%= form_for(@alarm) do |f| %> <div class="field"> <%= f.label

    :priority %><br> <%= f.select :priority, Priority.select_options %> </div> <div class="actions"> <%= f.submit %> </div> <% end %>
  25. 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 ...
  26. 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
  27. 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