Prefer instance methods over class methods

Why do they make dependency injection harder? ๐Ÿ”—

Real Example - searching in Agent Finder ๐Ÿ”—

Bad - using class methods ๐Ÿ”—

# Search implementations
class PostgresSearch
  def self.call(query:)
    # ... do the search here ...
  end
end

class ElasticSearch
  def self.call(query:)
    # ... do the search here ...
  end
end

# Generic location search code
class LocationSearch
  def self.call(query:, search:)
    # ... snip ...
    search.call(query: query)
  end
end

# Controller
class SearchController < ApplicationController
  def show
    LocationSearch.
      call(query: params[:query], search: Rails.configuration.search)
  end
end

Rails.application.configure do
  # What if we need some config to be injected into these different implementations?
  # For example, host, username, password. Or maybe searching config such as exact match.
  # This would now be *impossible*.
  config.search = ElasticSearch # old
  config.search = PostgresSearch # new
end

Good - using instance methods ๐Ÿ”—

# Search implementations
class PostgresSearch
  def initialize(host:, username:) # Inject config, options or other instances
    @username = username
    @host = host
  end

  def call(query:)
    # ... do the search here ...
  end
end

class ElasticSearch
  def initialize(port: 1113)
    @port = port
  end

  def call(query:)
    # ... do the search here ...
  end
end

# Generic location search code
class LocationSearch
  def initialize(search:) # We can now dependency inject into the instance
    @search = search
  end

  def call(query:) # One less parameter then before
    # ... snip ...
    @search.call(query: query)
  end
end

# Controller
class SearchController < ApplicationController
  def show
    LocationSearch.
      new(search: Rails.configuration.search). # Inject configuration into instance
      call(query: params[:query])
  end
end

Rails.application.configure do
  # We can now inject options into the searchers at the top level. Yet more flexibility!
  config.search = ElasticSearch.new(port: 3445)                           # old
  config.search = PostgresSearch.new(host: "127.0.0.1", username: "root") # new
end