Ruby Patterns

I’ve been using Ruby professionally for close to three years now. I’ve recently gotten more curious into what different patterns are called and how they are used in other programming languages as well as what typical programming patterns can’t be used in Ruby.

When I create a new service object in my Rails code, with only one purpose, I tend to still write it as an instance object, so that the garbage collector can pick it up.

Let say we have this code:

class Mailer
  attr_reader :type

  def initialize(type)
    @type = type
  end

  def send_mail!
    ExternalMailClient.send_mail(type)
  end
end

to call this, we’d have to do:

Mailer.new(:my_type).send_mail!

When the external call in #send_mail! is done, the new Mailer instance will be marked for deletion and picked up by the garbage collector. But that new followed by send_mail! thorns in my eye. I tend to do this instead:

class Mailer
  class << self
    def send_mail!(type)
      new(type).send_mail!
    end
  end

  attr_reader :type

  def initialize(type)
    @type = type
  end

  def send_mail!
    ExternalMailClient.send_mail(type)
  end
end

Now I can instead do:

Mailer.send_mail!(:my_type)

and I don’t have to worry about the new instance or the fact that it is indeed creating a new instance of the Mailer class underneath the hood.

You could even go as far as adding:

private :new

to the class definition, so no one can call it without using your #send_mail! method.


My question is simple: What is this pattern called?

2 Likes

I’m not sure that the pattern is called, but it’s what I would to do too :slight_smile:

I’m no expert on terminology. But I’m not sure this has a pattern name yet.

I try to use a module for single function use cases. So personally I’d probably write it more like:

module Mailer
  class << self
    def send_mail!(type)
      ExternalMailClient.send_mail(type)
    end
  end
end

There is only one Mailer Object that stays around and the parameter is the only garbage collected item. Of course this looks like it’s just aliasing ExternalMailClient to Mailer. In which case this may work.

Mailer = ExternalMailClient

And instead of the bang method I’d just use the standard method :send_mail.

Yeah, might not have been the best example.

The thing that Mailer is solving here, is being an instance of something, with an internal state, however it is created and used for a single purpose and then thrown away.

Yeah I know what you mean about mailer being an instance with state. I don’t like how it’s implemented. In my mind an email shouldn’t know about sending. I think a Mail object should hold the state and a Mailer be purely functional.

No, what I meant was that a Mailer might not have been the best example, because I feel like you here :wink:

The above mailer example I just wrote for this topic.

Shall we call it “The Garbage Collector Pattern”? :wink: Or “Encapsulated Garbage Collection Friendly Pattern” :slight_smile:

1 Like

I have a new pattern I’m using! I’m translating a Python library and their structs are nothing like our structs. But everything in Ruby is an Object so I’m cleanly inheriting with keyward args and dynamically define the instance variables.

Here’s a snippet of the code

module NumberTypes
  class NumFlags 
    @@attrs = [:bytewidth, :min_val, :max_val, :rb_type, :name, :packer_type]
    attr_accessor *@@attrs
    def initialize **opts
      @value = 0 # YES/NO/I DON'T KNOW MAYBE NIL ;-)
      @@attrs.each do |a|
        instance_variable_set "@#{a}", opts.fetch(a) {nil}
      end
    end

    def self.inherited base
      def base.rb_type value
        new.instance_exec {@value = value; self}
      end
    end

    def coerce other
      [other, @value]
    end
  end

  module ::Boolean; end
  FalseClass.prepend Boolean
  TrueClass. prepend Boolean

  class BoolFlags < NumFlags
    def initialize **opts
      super( {
        bytewidth:   1,
        min_val:     false,
        max_val:     true,
        rb_type:     Boolean,
        name:        "bool",
        packer_type: nil
      }.
      update opts)
    end
  end

  class Uint8Flags < NumFlags
    def initialize **opts
      super( {
        bytewidth:   1,
        min_val:     0,
        max_val:     (2**8) - 1,
        rb_type:     Integer,
        name:        "uint8",
        packer_type: nil
      }.
      update opts)
    end
  end

  class Uint16Flags < NumFlags
    def initialize **opts
      super( {
        bytewidth:   2,
        min_val:     0,
        max_val:     (2**16) - 1,
        rb_type:     Integer,
        name:        "uint16",
        packer_type: nil
      }.
      update opts)
    end
  end
end

I currently have the packer_type as nil because I haven’t figured out what behavior I need to imitate there. But I thought you all might like my design pattern for inheritance so I thought I’d share it here.

The way new instances were created in Python structs with py_type is pretty weird. I mimicked the behavior. Also why coerce was needed.

And now I’ve thrown the code away. :wink: Changing everything to Structs as that is what I need to model. Sometimes being brilliant isn’t a good idea.

1 Like