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.
source to share
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.
source to share
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.
source to share