Classes & Modules
Use a consistent structure in your class definitions. [link]
class Person # extend and include go first extend SomeModule include AnotherModule # inner classes CustomErrorKlass = Class.new(StandardError) # constants are next SOME_CONSTANT = 20 # afterwards we have attribute macros attr_reader :name # followed by other macros (if any) validates :name # public class methods are next in line def self.some_method end # initialization goes between class methods and other instance methods def initialize end # followed by other public instance methods def some_method end # protected and private methods are grouped near the end protected def some_protected_method end private def some_private_method end endDon't nest multi line classes within classes. Try to have such nested classes each in their own file in a folder named like the containing class. [link]
# bad # foo.rb class Foo class Bar # 30 methods inside end class Car # 20 methods inside end # 30 methods inside end # good # foo.rb class Foo # 30 methods inside end # foo/bar.rb class Foo class Bar # 30 methods inside end end # foo/car.rb class Foo class Car # 20 methods inside end endPrefer modules to classes with only class methods. Classes should be used only when it makes sense to create instances out of them. [link]
# bad class SomeClass def self.some_method # body omitted end def self.some_other_method end end # good module SomeModule module_function def some_method # body omitted end def some_other_method end endFavor the use of
module_functionoverextend selfwhen you want to turn a module's instance methods into class methods. [link]# bad module Utilities extend self def parse_something(string) # do stuff here end def other_utility_method(number, string) # do some more stuff end end # good module Utilities module_function def parse_something(string) # do stuff here end def other_utility_method(number, string) # do some more stuff end endWhen designing class hierarchies make sure that they conform to the Liskov Substitution Principle. [link]
Always supply a proper
to_smethod for classes that represent domain objects. [link]class Person attr_reader :first_name, :last_name def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end def to_s "#{@first_name} #{@last_name}" end endUse the
attrfamily of functions to define trivial accessors or mutators. [link]# bad class Person def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end def first_name @first_name end def last_name @last_name end end # good class Person attr_reader :first_name, :last_name def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end endAvoid the use of
attr(now undocumented). Useattr_readerandattr_accessorinstead. [link]# bad: creates a single attribute accessor attr :something, true # behaves as attr_accessor attr :one, :two, :three # behaves as attr_reader # good attr_accessor :something attr_reader :one, :two, :threeConsider using
Struct.new, which defines the trivial accessors, constructor and comparison operators for you. (Just be aware thatStructadds several convenience methods that you may not want in some cases. An example is that you would prefer to know that some data class is immutable and will not change, so the setters thatStructautomatically creates for all fields would be inappropriate.) [link]# good class Person attr_accessor :first_name, :last_name def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end end # good Person = Struct.new(:first_name, :last_name) `Don't extend an instance initialized by
Struct.new. Extending it introduces a superfluous class level and may also introduce weird errors if the file is required multiple times. [link]# bad class Person < Struct.new(:first_name, :last_name) # any other methods... end # good Person = Struct.new(:first_name, :last_name) do # any other methods... end `Consider adding factory methods to provide additional sensible ways to create instances of a particular class. [link]
class Person def self.create(options_hash) # body omitted end endPrefer duck-typing over inheritance. [link]
# bad class Animal # abstract method def speak end end # extend superclass class Duck < Animal def speak puts "Quack! Quack" end end # extend superclass class Dog < Animal def speak puts "Bau! Bau!" end end # good class Duck def speak puts "Quack! Quack" end end class Dog def speak puts "Bau! Bau!" end endAvoid the usage of class (
@@) variables due to their "nasty" behavior in inheritance. [link]class Parent @@class_var = "parent" def self.print_class_var puts @@class_var end end class Child < Parent @@class_var = "child" end Parent.print_class_var # => will print "child"As you can see all the classes in a class hierarchy actually share one class variable. Class instance variables should usually be preferred over class variables.
Assign proper visibility levels to methods (
private,protected) in accordance with their intended usage. Don't go off leaving everythingpublic(which is the default). [link]Indent the
public,protected, andprivatemethods as much as the method definitions they apply to. Leave one blank line above the visibility modifier and one blank line below in order to emphasize that it applies to all methods below it. [link]class SomeClass def public_method # ... end private def private_method # ... end def another_private_method # ... end endUse
def self.methodto define class methods. This makes the code easier to refactor since the class name is not repeated. [link]class TestClass # bad def TestClass.some_method # body omitted end # bad: It's easy to miss that later methods, like second_method_etc(), are # under this new scope. Indention may be your only clue if `class << self` # is off screen. class << self def first_method # body omitted end def second_method_etc # body omitted end end # good def self.some_other_method # body omitted end endPrefer
alias_methodwhen aliasing methods. The alternative,alias, is a keyword with unusual syntax. [link]class Westerner def first_name @names.first end alias_method :given_name, :first_name endBe aware of how Ruby handles aliases: an alias is actually just a copy of the method that was made at the time the alias was defined; it is not dispatched dynamically. This matters in cases like inheritance:
class Fugitive < Westerner def first_name "Nobody" end endIn this example,
Fugitive#given_namewould still call the originalWesterner#first_namemethod, notFugitive#first_name. To override the behavior ofFugitive#given_nameas well, you'd have to redefine it in the derived class.class Fugitive < Westerner def first_name "Nobody" end alias_method :given_name, :first_name endWhen creating new nested modules or classes, avoid
::. Only use::when referring to modules or classes in other files (see: double colons). When loading the file, if the left side of the::isn't defined, Ruby will choke. [link]# bad class ParentModule::NaughtyInnerChildClass; end # good module ParentModule class WellBehavedChildClass; end end