ruby-on-railsrubyruby-on-rails-4resqueimpressionist

Rails 4 - Impressionist Inside of a Resque Background Job


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.


Solution

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