skip to Main Content

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.

2

Answers


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

    Login or Signup to reply.
  2. If you want the connection to stay open between requests you can not store it as an instance variable in your controller since each request will have its own instance of the controller.

    One way to store the connection is to use a singleton.

    Here is an example:

    class ImapService
    
      attr_accessor :imap
    
      def initialize
        @imap = Net::IMAP.new("imap.gmail.com", 993, true)
        @imap.login("[email protected]", "password")
        @imap.select("INBOX")
      end
    
      @@instance = ImapService.new
      private_class_method :new
    
      def self.instance
        return @@instance
      end
    end
    

    This will open the connection the first time you access it, and if you access it again, it will use the old connection.

    You would access the imap variable with ImapService.instance.imap anywhere in your application.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search