Как кэшировать метод с Ruby/Rails?

У меня есть дорогостоящий (отнимающий много времени) внешний запрос на другой веб-сервис, который мне нужно сделать, и я хотел бы его кэшировать. Поэтому я попытался использовать эту idiom, поставив в контроллер приложения следующее:

def get_listings
  cache(:get_listings!)
end

def get_listings!
  return Hpricot.XML(open(xml_feed))
end

Когда я вызываю get_listings! в моем контроллере, все круто, но когда я звоню get_listings, Rails жалуется, что никакого блока не было. И когда я просматриваю этот метод, я вижу, что он действительно ожидает блок, и, кроме того, похоже, что этот метод предназначен только для использования в представлениях? Поэтому я предполагаю, что, хотя не было указано, что пример - это просто псевдокод.

Итак, мой вопрос: как я кешу что-то вроде этого? Я пробовал разные способы, но не мог понять. Спасибо!

Ответ 1

Как предполагает nruth, встроенное в кеш хранилище Rails, вероятно, то, что вам нужно.

Пытаться:

def get_listings
  Rails.cache.fetch(:listings) { get_listings! }
end

def get_listings!
  Hpricot.XML(open(xml_feed))
end

fetch() извлекает кэшированное значение для указанного ключа или записывает результат блока в кеш, если он не существует.

По умолчанию кеш Rails использует хранилище файлов, но в производственной среде memcached является предпочтительным вариантом.

См. Раздел 2 http://guides.rubyonrails.org/caching_with_rails.html для получения более подробной информации.

Ответ 2

Подход в коде может выглядеть примерно так:

def get_listings
  @listings ||= get_listings!
end

def get_listings!
  Hpricot.XML(open(xml_feed))
end

который будет кэшировать результат для каждого запроса (новый экземпляр контроллера для каждого запроса), хотя вы можете посмотреть на помощников "memoize" как параметр API.

Если вы хотите обмениваться запросами , не сохраняйте данные на объектах класса, так как ваше приложение не будет поточно-ориентированным, если вы не разбираетесь в параллельном программировании и не убедитесь, что потоки не мешают друг другу получать доступ к данным для общего доступа. переменная.

"Рельсовым способом" кеширования запросов является хранилище Rails.cache. Memcached часто используется, но вы можете найти файлы или хранилища памяти, соответствующие вашим потребностям. Это на самом деле зависит от того, как вы развертываете, и хотите ли вы отдавать приоритет попаданиям в кеш, времени отклика, памяти (RAM) или использовать размещенное решение, например, дополнение к heroku.

Ответ 3

Вы можете использовать cache_method gem:

gem install cache_method
require 'cache_method'

В вашем коде:

def get_listings
  Hpricot.XML(open(xml_feed))
end
cache_method :get_listings

Вы могли заметить, что я избавился от get_listings!. Если вам нужен способ обновить данные вручную, я предлагаю:

def refresh
  clear_method_cache :get_listings
end

Здесь еще один лакомый кусочек:

def get_listings
  Hpricot.XML(open(xml_feed))
end
cache_method :get_listings, (60*60) # automatically expire cache after an hour

Ответ 4

Вы также можете использовать кеш-кед (https://github.com/reneklacan/cachethod)

gem 'cachethod'

Тогда смертельно просто кэшировать результат метода

class Dog
  cache_method :some_method, expires_in: 1.minutes

  def some_method arg1
    ..
  end
end

Он также поддерживает кэширование уровня аргументов

Ответ 5

Было предложено cache_method драгоценный камень, хотя он довольно тяжелый. Если вам нужно вызвать метод без аргументов, решение очень просто:

Object.class_eval do

  def self.cache_method(method_name)
    original_method_name = "_original_#{method_name}"
    alias_method original_method_name, method_name
    define_method method_name do
      @cache ||= {}
      @cache[method_name] = send original_method_name unless @cache.key?(method_name)
      @cache[method_name]
    end
  end

end

то вы можете использовать его в любом классе:

def get_listings
  Hpricot.XML(open(xml_feed))
end
cache_method :get_listings

Примечание. Это также будет кэшировать нуль, что является единственной причиной для его использования вместо @cached_value ||=

Ответ 6

Другие ответы превосходны, но если вам нужен простой ручной показ, вы можете это сделать. Определите метод, подобный приведенному ниже в своем классе...

def use_cache_if_available(method_name,&hard_way)
 @cached_retvals ||= {}  # or initialize in constructor
 return @cached_retvals[method_name] if @cached_retvals.has_key?(method_name)
 @cached_retvals[method_name] = hard_way.call
end

Затем для каждого метода, который вы хотите кэшировать, вы можете поместить оболочку метода в нечто подобное...

def some_expensive_method(arg1, arg2, arg3)
  use_cache_if_available(__method__) {
    calculate_it_the_hard_way_here
  }
end

Одна вещь, которая делает это лучше, чем самый простой метод, указанный выше, заключается в том, что он будет кэшировать нуль. У этого есть удобство, что это не требует создания повторяющихся методов. Вероятно, подход к драгоценным камням более чист.

Ответ 7

Поздно на вечеринку, но в случае, если кто-то прибывает сюда в поисках.

Я использую для переноса этого маленького модуля из проекта в проект, я нахожу его удобным и достаточно расширяемым без добавления дополнительного драгоценного камня. Он использует бэкэнд Rails.cache, поэтому, пожалуйста, используйте его, только если он у вас есть.

# lib/active_record/cache_method.rb
module ActiveRecord
  module CacheMethod
    extend ActiveSupport::Concern

    module ClassMethods
      # To be used with a block
      def cache_method(args = {})
        @caller = caller
        caller_method_name = args.fetch(:method_name)     { @caller[0][/'.*'/][1..-2] }
        expires_in         = args.fetch(:expires_in)      { 24.hours }
        cache_key          = args.fetch(:cache_key)       { "#{self.name.underscore}/methods/#{caller_method_name}" }

        Rails.cache.fetch(cache_key, expires_in: expires_in) do
          yield
        end
      end
    end

    # To be used with a block
    def cache_method(args = {})
      @caller = caller
      caller_method_name = args.fetch(:method_name) { @caller[0][/'.*'/][1..-2] }
      expires_in         = args.fetch(:expires_in)  { 24.hours }
      cache_key          = args.fetch(:cache_key)   { "#{self.class.name.underscore}-#{id}-#{updated_at.to_i}/methods/#{caller_method_name}" }

      Rails.cache.fetch(cache_key, expires_in: expires_in) do
        yield
      end
    end
  end
end

Затем в инициализаторе:

# config/initializers/active_record.rb
require 'active_record/cache_method'
ActiveRecord::Base.send :include, ActiveRecord::CacheMethod

И тогда в модели:

# app/models/user.rb
class User < AR 
  def self.my_slow_class_method
    cache_method do 
      # some slow things here
    end
  end

  def this_is_also_slow(var)
    custom_key_depending_on_var = ...
    cache_method(key_name: custom_key_depending_on_var, expires_in: 10.seconds) do 
      # other slow things depending on var
    end
  end
end

На данный момент он работает только с моделями, но может быть легко обобщен.