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
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.
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.