Logo: Relish

  1. Sign in

Project: Ruby-style-guide

metaprogramming

  • Avoid needless metaprogramming.

  • Do not mess around in core classes when writing libraries.
    (Do not monkey-patch them.)

  • The block form of class_eval is preferable to the string-interpolated form.

    • when you use the string-interpolated form, always supply __FILE__ and __LINE__, so that your backtraces make sense:
    class_eval 'def use_relative_model_naming?; true; end', __FILE__, __LINE__
    
    • define_method is preferable to class_eval{ def ... }
  • When using class_eval (or other eval) with string interpolation, add a comment block
    showing its appearance if interpolated (a practice used in Rails code):

    # from activesupport/lib/active_support/core_ext/string/output_safety.rb
    UNSAFE_STRING_METHODS.each do |unsafe_method|
      if 'String'.respond_to?(unsafe_method)
        class_eval <<-EOT, __FILE__, __LINE__ + 1
          def #{unsafe_method}(*args, &block)       # def capitalize(*args, &block)
            to_str.#{unsafe_method}(*args, &block)  #   to_str.capitalize(*args, &block)
          end                                       # end
    
          def #{unsafe_method}!(*args)              # def capitalize!(*args)
            @dirty = true                           #   @dirty = true
            super                                   #   super
          end                                       # end
        EOT
      end
    end
    
  • Avoid using method_missing for metaprogramming because backtraces become messy,
    the behavior is not listed in #methods, and misspelled method calls might silently
    work, e.g. nukes.launch_state = false. Consider using delegation, proxy, or
    define_method instead. If you must use method_missing:

    • Be sure to also define respond_to_missing?
    • Only catch methods with a well-defined prefix, such as find_by_* -- make your code as assertive as possible.
    • Call super at the end of your statement
    • Delegate to assertive, non-magical methods:

      # bad
      def method_missing?(meth, *args, &block)
        if /^find_by_(?<prop>.*)/ =~ meth
          # ... lots of code to do a find_by
        else
          super
        end
      end
      
      # good
      def method_missing?(meth, *args, &block)
        if /^find_by_(?<prop>.*)/ =~ meth
          find_by(prop, *args, &block)
        else
          super
        end
      end
      
      # best of all, though, would to define_method as each findable attribute is declared
      

Last published over 6 years ago by David Kariuki.