Search code examples
ruby-on-railsrubyimap

Share a Net::IMAP connection between controller actions


I have only one controller and some actions in it to handle different functionalities related to IMAP. So my problem is I don't want to create a separate connection for every action. For example in an action I can do something like(it is not the actual code):

def index
 @imap = Net::IMAP.new(server, 993, true)
 @imap.login(user, password)
 @imap.select("INBOX")
end

Again in another action inside the same controller, if I need to do something related to IMAP then I will have to create the @imap variable again.

I am working with IMAP first time so as per my understanding new method in each action will create another connection to the server and I have heard google has connection limit (15) for the number of IMAP connections.

I can not serialize this connection object or store it in any other service like Redis or Memcached or cache it, So how can I create this connection once and use it all other actions, at least actions inside the same controller if possible? If not possible then any other solutions to handle this problem?

And of course I can cache the data I need from the mailbox but that can't help much since there are some other actions which won't need the data, it will need to do so some operations in the mailbox like deleting mails, so that will need the connection instance.


Solution

  • How about you create a service object (singleton) that wraps you Net::IMAP. You can stick it in app/services/imap_service.rb or something like that. For an example on what that would look like:

    require 'singleton' # This is part of the standard library
    require 'connection_pool' # https://github.com/mperham/connection_pool
    
    class IMAPService
      include Singleton
    
      def initialize
        @imap = ConnectionPool.new(size: 15) { Net::IMAP.new(server, 993, true) }
      end
    
      def inbox(user, password)
        @imap.with do |conn|
          conn.login(user, password)
          conn.select("INBOX")
        end
      end
    end
    

    You access this singleton like IMAPService.instance e.g. IMAPService.instance.inbox(user, password). I added in the connect_pool gem as per our discussion to make sure this is thread safe. There is no attr_reader :imap on IMAPService. However, you can add one so that you can directly access the connection pool in your code if you don't want to include all of the necessary methods here (although I recommend using the service object if possible). Then you can do IMAPService.instance.imap.with { |conn| conn.login(user, password) } and don't need to rely on methods in IMAPService.

    It's worth noting that you don't have to use the Singleton mixin. There is a really good article on Implementing "the lovely" Singleton which will show you both ways to do it.