loading...

Ruby quick tip: ABC, Always Be Constantizing

thepeoplesbourgeois profile image Josh ・2 min read

Ruby has a reputation as one of the most fast and loose languages in modern programming, but in reality, it has a number of subtle features that help ensure the intent of your code is fairly clear.

For example, if you try to define a class like this...

# ./puppy.rb
class puppy
end

The interpreter will tell you

SyntaxError (./puppy.rb:2: class/module name must be CONSTANT)

And you will have no puppy to speak of :[

Constants have a lot of great uses, but their biggest strength is in helping you organize your code and provide context around what a literal is used for.

class Action < ApplicationRecord
  scope :accepted, -> { where(type: "accept") }
  scope :rejected, -> { where(type: "reject") }
  scope :requested_changes, -> { where(type: "changes_requested") }

  # ...
end

These scopes contain string literals that likely aren't going to change, but suppose a major rewrite of this app's business logic occurred, and they suddenly did. If a developer misses one of the invocations of that literal – say, in another file like user.rb, you might not find out about it until the exception surfaces in your runtime.

You can prevent that from happening by moving these string literals to constants, and then referencing those in place of the literals themselves:

class Action < ApplicationRecord
  module Type
    ACCEPT = "accept"
    REJECT = "reject"
    REQUEST_CHANGES = "changes_requested"
    MAKE_BETTER = "improvements_required"
    RUBY_PRETTIER = "improve_readability"

    ALL = [ACCEPT, REJECT, REQUEST_CHANGES, MAKE_BETTER, RUBY_PRETTIER]
  end

  scope :accepted, -> { where(type: Type::ACCEPT) }
  scope :rejected, -> { where(type: Type::REJECT) }
  scope :requested_changes, -> { where(type: Type::REQUEST_CHANGES) }
end

You can even declare these constants within the array literal ALL, and have access to the individual strings and collection, all from one terse declaration.

class Action < ApplicationRecord
  module Type
    ALL = [
      ACCEPT = "accept",
      REJECT = "reject",
      REQUEST_CHANGES = "changes_requested",
      MAKE_BETTER = "improvements_required",
      RUBY_PRETTIER = "improve_readability"  
    ]
  end

  # ...
end

In your terminal:

 2019-03-03 15:33:33 ⌚ ruby 2.6.3p62 Newtons-Mac in ~/workspace/my_great_app
±  |master ✓| → rails console
Running via Spring preloader in process 90221
Loading development environment (Rails 5.2.3)
irb(main):001:0> Action::Type::ACCEPT
=> "accept"
irb(main):002:0> Action::Type::ALL
=> ["accept", "reject", "changes_requested", "improvements_required", "improve_readability"]

After that refactor, you'll also get tab completion in your IDE of choice. Bonus. 👍

Now go forth, and dev greatly ✨🐶✨

Discussion

pic
Editor guide