Logging leads when interactions happen for a contact I have been trying to get as close to SRP in my code as possible. I have a situation where I need to log leads when interactions happen for a contact. A lead may be created or updated. There are three different states that I would like to set leads to.


Active - new interested interactions causes active lead to be created
Converted - a existing lead is updated to converted if the product was purchased - this is managed with conversion flag
Stale - a existing lead is updated to stale if the contact is not interested




class LeadGeneration

  def initialize contact_id, options={}
    @lead = Lead.find_or_initialize_by(contact_id: contact_id)

    options.reverse_merge!(conversion: false, interested:true)
    @conversion = options[:conversion]
    @interested = options[:interested]
  end

  def call
    if conversion?
      convert_lead 
    else
      process_lead
    end
  end

  private 

  def lead
    @lead
  end

  def conversion?
    @conversion
  end

  def interested?
    @interested
  end

  def convert_lead
    lead.converted! unless lead.new_record?  # new leads cannot be saved as converted
  end

  def process_lead
    if interested?
      activate_lead
    else
      stale_lead
    end
  end

  def activate_lead
    lead.active!
  end

  def stale_lead
    lead.stale! unless lead.new_record? # new leads cannot be saved as stale
  end
end


Right now I feel like I should be splitting up converting a lead and managing stale/active states.  I'm not really sure what to do and tests are a little confusing right now.
I'm not sure how you intend to use this class, but the call method is very fishy. This method seems to do one of three things: convert_lead, activate_lead or stale_lead. The choice of which of them should actually be executed is made in the options argument during object construction.

This is not very easy to understand from reading the code, and I can't really see the reasoning behind it. Is it an implementation of a Command pattern? that is not very ruby-like.

Since all this class actually does is quite basic delegation (@lead.converted!, @lead.stale! or @lead.active!) I would consider foregoing this class altogether, and simply doing the action. It is unnecessarily long and convoluted, and hides more than explains what it does.



From your comment below I understand that LeadGeneration should be a Service Object. It is true that service objects may have a single method called call, as is suggested in this blog post:


  Service objects are responsible for retrieving and/or manipulating
  data — essentially any work that needs to be done that you might have
  put in a controller or model object before. They typically only have
  one method, which I like to define as “#call” (they're usually named
  something like PictureCreationService, so naming the method #create
  would seem redundant).


But note, though, the reason a single method call is allowable - it is apparent from the class name exactly what the method should do.
If your service object was called LeadConversionService, then a call method would be good enough.

Being a service object, it is even more important than usual in ruby for the class to be small and transparent.

If you want to go to the extreme SRP suggested in the quote above, you'd have three classes, each with #call and #allowed? methods:

class LeadConversionService
  def initialize(lead)
    @lead = lead
  end

  def call
    @lead.converted!
  end

  def allowed?
    !@lead.new_record?
  end
end


OR, you could be a little less extreme, and decide the LeadGeneration (or maybe LeadLifeCycleService?) is a good enough scope, and let it have three method:

class LeadLifeCycleService
  def initialize(lead)
    @lead = lead
  end

  def convert!
    raise LifeCycleException if @lead.new_record?
    @lead.convert!
  end

  def activate!
    @lead.active!
  end

  def stale!
    raise LifeCycleException if @lead.new_record?
    @lead.stale!
  end
end


Writing this solution makes it obvious again, that this service doesn't do much, since it only delegates (so it doesn't even save code in your model...). All it does is validates pre-conditions to your actions, which may be better handled by a state-machine, as @Flambino suggested.