I am using Rails 4 w/ the impressionist and resque gem.
I am using impressionist to log unique session hits on my article show page. Due to performance issues and no need to display hits to users (it is for admins only), I would like to move logging impressions off into the background.
Normally I would log an impression using impressionist(@article, unique: [:session_hash])
but to move it off into the bg via resque I am now doing something like this...
articles_controller:
def show
.
.
.
Resque.enqueue(ImpressionLogger, @article.id)
end
app/workers/impression_logger.rb:
class ImpressionLogger
@queue = :impression_queue
def self.perform(article_id)
article = Article.find(article_id)
impressionist(article, unique: [:session_hash])
end
end
When I set it up like this, when resque tries to process the job, it is returning undefined method "impressionist" for ImpressionLogger:Class
. What do you guys think the best way to go about this is? I am not sure how to include impressionist methods inside of my resque worker.
The issue
Your problem stems from the fact that it looks like Impressionist works on the controller level due to including a module with the impressionist
method in an engine initializer on any instances of ActionController:
https://github.com/charlotte-ruby/impressionist/blob/master/lib/impressionist/engine.rb#L11
You're trying to call the impressionist method from a regular class being invoked in a Resque job, so it's not going to have that method defined.
Solution
It's kind of gross, but if you really want to use impressionist, we can delve into this... Looking at the actual implementation of the impressionist method found here, we see the following:
def impressionist(obj,message=nil,opts={})
if should_count_impression?(opts)
if obj.respond_to?("impressionable?")
if unique_instance?(obj, opts[:unique])
obj.impressions.create(associative_create_statement({:message => message}))
end
else
# we could create an impression anyway. for classes, too. why not?
raise "#{obj.class.to_s} is not impressionable!"
end
end
end
Assuming that you'd be calling something like this manually (as you want to from a resque job) the key are these three lines:
if unique_instance?(obj, opts[:unique])
obj.impressions.create(associative_create_statement({:message => message}))
end
The if wrapper only seems to be important if you want to implement this functionality. Which it looks like you do. The call to associative_create_statement
seems to be pulling parameters based off of the controller name as well as parameters passed from Rack such as the useragent string and ip address (here). So, you'll have to resolve these values prior to invoking the Resque job.
What I would suggest at this point is implementing a Resque class that takes in two parameters, an article_id and the impression parameters that you want. The resque class would then just directly create the impression on the impressionable object. Your Resque class would become:
class ImpressionLogger
@queue = :impression_queue
def self.perform(article_id, impression_params = {})
article = Article.find(article_id)
article.impressions.create(impression_params)
end
end
And your controller method would look something like this:
def show
.
.
.
Resque.enqueue(ImpressionLogger, @article.id, associative_create_statement({message: nil})) if unique_instance?(@article, [:session_hash])
end
Disclaimer
There's a fairly big disclaimer that comes with doing it this way though... the method associative_create_statement
is marked protected and unique_instance?
is marked private... so neither of these is part of the impressionist gem's public API, so this code might break between versions of the gem.