Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails 'service objects' best practice - class method or instantiate

I am implementing 'service objects' as per a workshop I've been studying, I'm building a reddit API application. I need the object to return something, so I can't just execute everything in the initializer. I have these two options:

Option1: Class needs instantiating

class SubListFromUser

  def user_subscribed_subs(client)
    @client = client
    @subreddits = sort_subs_by_name(user_subs_from_reddit)
  end

  private

  def sort_subs_by_name(subreddits)
    subreddits.sort_by { |sr| sr[:name].downcase }
  end 

  def user_subs_from_reddit
    @client.subscribed_subreddits :limit => 100
  end

end

Called with:

@subreddits = SubListFromUser.new(@client).user_subscribed_subs

Or Option2 is having it as a class method:

class SubListFromUser

  def self.user_subscribed_subs(client)
    sort_subs_by_name(client, user_subs_from_reddit)
  end

  private

  def self.sort_subs_by_name(subreddits)
    subreddits.sort_by { |sr| sr[:name].downcase }
  end 

  def self.user_subs_from_reddit(client)
    client.subscribed_subreddits :limit => 100
  end

end

Called with:

@subreddits = SubListFromUser.user_subscribed_subs(@client)

What is considered 'best practice' in this situation? Is there a reason I shouldn't be using object.new(args).method? I think it gives a cleaner service class but I'm not sure of the technicalities of this approach and if it has disadvantages.

Edit: Or option3 - I'm going about this all wrong and there is a better approach :)

like image 959
RichardAE Avatar asked Jul 16 '14 12:07

RichardAE


2 Answers

In Ruby, I don't find that there's much of a difference.

I find the use of class variables in your "static" version a bit disturbing.

I think the class version might lead to more-creative re-use through subclassing, but that brings its own set of headaches unless things are designed as correctly as possible.

like image 187
Dave Newton Avatar answered Nov 02 '22 07:11

Dave Newton


In many cases you'll need to keep a state for the process lifecycle, such as the client. Instead of having it "travel" through all methods you need it, as an argument, it makes more sense to keep it as a class variable. But for the sake of cleaner syntax, I recommend to combine the two approaches:

class SubListFromUser
  def initialize(client)
    @client = client
  end
  private_class_method :new # only this class can create instances of itself

  def user_subscribed_subs
    @subreddits = sort_subs_by_name(user_subs_from_reddit)
  end

  private

  def sort_subs_by_name(subreddits)
    subreddits.sort_by { |sr| sr[:name].downcase }
  end 

  def user_subs_from_reddit
    @client.subscribed_subreddits :limit => 100
  end


  class << self
    def user_subscribed_subs(client)
      new(client).user_subscribed_subs # create instance of this class and run a process
    end
  end
end

Call as a class method:

@subreddits = SubListFromUser.user_subscribed_subs(@client)
like image 4
elado Avatar answered Nov 02 '22 07:11

elado