Как мне кэшировать метод с помощью Ruby / Rails? - PullRequest
11 голосов
/ 30 августа 2010

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

def get_listings
  cache(:get_listings!)
end

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

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

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

Ответы [ 7 ]

14 голосов
/ 30 августа 2010

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

def get_listings
  @listings ||= get_listings!
end

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

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

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

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

9 голосов
/ 31 августа 2010

Как предполагает 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.

3 голосов
/ 17 марта 2011

Вы можете использовать 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
1 голос
/ 03 февраля 2017

Был предложен 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

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

1 голос
/ 10 июня 2014

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

gem 'cachethod'

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

class Dog
  cache_method :some_method, expires_in: 1.minutes

  def some_method arg1
    ..
  end
end

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

0 голосов
/ 19 декабря 2018

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

Я использую этот небольшой модуль для переноса из проекта в проект, я считаю его удобным и достаточно расширяемым без добавления дополнительного драгоценного камня.Он использует 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

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

0 голосов
/ 15 февраля 2016

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

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

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

...