4

I'm working on a rails application, and I've been pulling functionality out of my rails code and into pure ruby classes in lib/. I've found myself often writing classes like this:

 class MailchimpIntegration   
  def subscribe_email(email, fname, lname)
    Gibbon.list_subscribe(:id => NEWSLETTER_LIST_ID, :email_address => email,
      :merge_vars => {'fname' => fname, 'lname' => lname },
      :email_type => "html",  :double_optin => false, :send_welcome => false)
  end

  def unsubscribe_email(email)
    Gibbon.list_unsubscribe(:id => NEWSLETTER_LIST_ID, :email_address => email)
  end

  def change_details(old_email, email, fname, lname)
    Gibbon.list_update_member(:id => NEWSLETTER_LIST_ID, :email_address => old_email,
      :merge_vars => {'email' => email, 'fname' => fname, 'lname' => lname })
  end

  def get_email_info(email)
    Gibbon.list_member_info(:id => NEWSLETTER_LIST_ID, :email_address => [email])["data"].first
  end
end

My question is: Should I change these methods to be class methods?

It seems reasonable to do, as I'll probably end up calling these by just newing up a MailchimpIntegration class each time. However, I generally prefer to have instance methods as they can be more easily stubbed etc, although this seems to be less of an issue in ruby.

I have several classes like this in my system, so I'd be keen to see what people think about this.

1

2 Answers 2

3

Another approach you can take is to use mixins.

    module InteractsWithMailChimp
       def subscribe_email(email, fname, lname)
          Gibbon.list_subscribe(:id => NEWSLETTER_LIST_ID, :email_address => email, 
          :merge_vars => {'fname' => fname, 'lname' => lname }, 
          :email_type => "html",  :double_optin => false, :send_welcome => false)
       end
    end

    Class User
        include InteractsWithMailChimp
    end

This approach would give you the benefit of having/using instance variables from the context of the user within the module. The module can be tested in isolation by extending it within any test case. Within the module you can separate the responsibilities for example you can consider that the subscribe/unsubcribe part is a responsibility on its own so you would have:

    module InteractsWithMailChimp
       class HandlesSubscriptions
           def subscribe_email(email, fname, lname)
              Gibbon.list_subscribe(:id => NEWSLETTER_LIST_ID, :email_address => email, 
              :merge_vars => {'fname' => fname, 'lname' => lname }, 
              :email_type => "html",  :double_optin => false, :send_welcome => false)
           end
       end
    end

    Class User
        include InteractsWithMailChimp::HandlesSubscriptions
    end

Within classes that are part of the module if you consider that you would not need any external data there is no problem of making the methods class ones. I like the mixin approach more because it's simple to reuse, it does not need to repeat yourself with the object instantiation and I can name my modules and classes within modules referring exactly to the responsibility they have. I would still keep these module separate in /lib of course. For more really nice tips and tricks on how to organize your rails code and have your test run fast you can check out fast rails tests. Hope it helps.

0

I'm putting this in as an answer, since I could be wrong, but I think I have a good reason for going with instance methods as opposed to class methods in this case.

The mailchimp class above is called from the User class when the user is saved, email edited, etc, realized that I need to make sure I don't call the mailchimp API in test, or all my tests will be super slow. (I stub out the mailchimp stuff in the tests dealing with that, but I don't for the other tests which just deal with user).

With class methods, this seems like it would be a pain since I can't easily stub out all the methods, but with MailchimpIntegration objects, I can just put:

  def mailchimp
    if Rails.env.production?
      MailchimpIntegration.new
    else
      NullObject.new
    end
  end

In my user class, and stub that out to return my stub object with I'm testing the integration.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.