Sharing Net :: IMAP connection between controller actions

I only have one controller and some actions in it to handle various IMAP related functionality. So my problem is that I don't want to create a separate connection for each activity. For example, in an activity, I can do something like (this is not real code):

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

      

Again in a different activity inside the same controller, if I need to do something IMAP related, then I have to create the variable again @imap

.

I am working with IMAP for the first time as in my understanding the method new

in each action will create a different connection to the server and I heard that Google has a connection limit (15) for the number of IMAP connections.

I cannot 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 all other actions, at least actions within the same controller, if possible? If this is not possible, then any other solutions to solve this problem?

And of course, I can cache the data I need from the mailbox, but that doesn't help much, since there are some other actions that won't require data, it will need to do some operations in the mailbox, such as deleting mail. so this will require an instance of the connection.

+3


source to share


2 answers


How about creating a service object (singleton) that wraps you Net::IMAP

. You can stick it in app/services/imap_service.rb

or something like that. For example, what it 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 get access to this singleton for IMAPService.instance

example. IMAPService.instance.inbox(user, password)

... I added a connect_pool in the harness as per our discussion to make sure it is thread safe. The IMAPService does not attr_reader :imap

. However, you can add it so that you can access the connection pool directly in your code if you don't want to include all the required methods here (although I recommend using a service object if possible). Then you can IMAPService.instance.imap.with { |conn| conn.login(user, password) }

and shouldn't rely on methods in IMAPService.

It's worth noting that you don't need to use a mix Singleton

. There is a really good article on Implementing a "lovely" singleton that shows you both ways to do this.

+2


source


If you want the connection to remain open between requests, you cannot store it as an instance variable in your controller, since each request will have its own controller instance.

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

Here's an example:



class ImapService

  attr_accessor :imap

  def initialize
    @imap = Net::IMAP.new("imap.gmail.com", 993, true)
    @imap.login("username@gmail.com", "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 get it again, it will use the old connection.

You must access the imap variable from ImapService.instance.imap

anywhere in the application.

0


source







All Articles