Skip to content

Instantly share code, notes, and snippets.

@syou007
Last active December 12, 2015 10:29
Show Gist options
  • Save syou007/4759782 to your computer and use it in GitHub Desktop.
Save syou007/4759782 to your computer and use it in GitHub Desktop.
rubyのメソッドをキャッシュするコードです。詳細はブログで紹介してます。 http://ameblo.jp/syou007/entry-11468918541.html
# 使用例
def hoge
# キャッシュしたい処理
end
instance_cache :hoge
def hoge
# 何かの処理
cache_data = instance_cache {
# キャッシュしたい処理
}
# 何かの処理
end
module TownSoft
# モデルに対してのキャッシュの仕組みを提供します。
module Cache
# mix-inを行う際にクラスメソッドを追加する。
def self.included(base)
base.class_eval do
# オブジェクトキャッシュを一切行いたくない場合
attr_accessor :instance_cache_not_use
# プリミティブ型だけのキャッシュを行う場合
attr_accessor :instance_cache_only_primitive
end
# クラスメソッドを追加します。
base.extend(ClassMethods)
end
module ClassMethods
# メソッド自体をキャッシュする場合に使用します。
# インスタンスにキャッシュしたいメソッドとその際に使用する引数を定義してください。
# ※この処理はメソッド定義をした後に行う事。
#
# method:
# メソッド名を指定します。
#
# arg_string:
# アーギメントの引数をそのまま設定してください。
# 「"」部分は「\"」で定義すること
#
# cache_arg:
# キャッシュさせる定数を設定してください。
# 引数が元となってますが、インスタンス変数を文字列で指定してもキャッシュ値として認められます。
#
# ex)
# def method(arg1, arg2 = {}, arg3 = "a")
# ...
# end
# instance_cache :method, "arg1, arg2 = {}, arg3 = \"a\"", :arg1
#
def instance_cache(method, arg_string = "", *cache_arg)
# 指定のメソッドのaliasを生成する。
method_org = "#{method}_cache_org"
# 最後に?が付いている場合はオリジナルメソッドの最後に付与させる。
method_org = method.to_s.sub("?", "") + "_cache_org?" if method.to_s.index("?")
class_eval "alias #{method_org} #{method}"
# キャッシュする引数とif文を生成します。
cache_object_source = "@instance_cache"
if_cache_object_source = "#{cache_object_source}"
cache_object_create_source = ""
cache_arg ||= []
cache_arg.unshift("\"#{method}\"")
cache_arg.each { |arg|
# 最後はキーの存在確認にします。
if_cache_object_source += " && #{cache_object_source}.has_key?(#{arg})"
cache_object_create_source += "\n#{cache_object_source} ||= {}"
cache_object_source += "[#{arg}]"
}
# 呼び出し時の引数を生成します。
call_arg_string = ""
arg_string.split(",").each { |str|
call_arg_string += "," unless call_arg_string.blank?
call_arg_string += str.split("=")[0]
}
# メソッドの中身を書き替えます。
# 0行目. 開発環境ではキャッシュの回数をチェックします。
# 1行目. キャッシュ自体を使用しない場合は元のメソッドを呼び出して処理を返却します。
# 2行目. 既にキャッシュがある場合はキャッシュを返却します。
# 3行目. 元の処理を呼び出します。
# 4行目. プリミティブ型だけのキャッシュを行う場合はプリミティブ型以外の値を返却する。
# 5行目. 値をキャッシュします。
# 6行目. 処理結果を返します。
#
# キャッシュをクリアするメソッドを作成します。
# ※?が付くメソッドは?を排除して作成してます。
#
class_eval %-
def #{method}(#{arg_string})
return #{method_org}(#{call_arg_string}) if self.instance_cache_not_use
return #{cache_object_source} if #{if_cache_object_source}
ret_value = #{method_org}(#{call_arg_string})
return ret_value if self.instance_cache_only_primitive && !(ret_value.class == TrueClass ||
ret_value.class == FalseClass || ret_value.class == NilClass || ret_value.class == Fixnum || ret_value.class == String)
#{cache_object_create_source}
#{cache_object_source} = ret_value
return ret_value
end
def #{method.to_s.sub("?", "")}_cache_clear
@instance_cache.delete("#{method}")
end
-
end
end
# インスタンス内部にキャッシュしているデータを全てクリアーします。
def instance_cache_clear
@instance_cache = nil
end
private
# メソッドの一部をキャッシュさせたい場合に使用します。
# このインスタンス内部でキャッシュをさせたい箇所を指定します。
# 処理内容はクラスメソッドと同様です。なので、クラスメソッドの処理行数をコメントに記載しています。
#
# ex)
# instance_cache(__method__, arg1, arg,2) {
# ...
# }
# ※注意・・・最後の処理結果をキャッシュに詰めるので、returnは行わないこと。
# ※__method__部分は固定にすること(メソッド名でキャッシュを作るため。)
def instance_cache(*cache_arg)
raise ArgumentError, "Missing block" unless block_given?
# 1行目. キャッシュ自体を使用しない場合は元のメソッドを呼び出して処理を返却します。
return yield if self.instance_cache_not_use
# 2行目. 既にキャッシュがある場合はキャッシュを返却します。
cache_data = nil
cache_arg.each { |arg|
cache_data ||= @instance_cache
# キーを持っているかチェックします。
unless cache_data.has_key?(arg)
# キャッシュデータを持っていないので返却
cache_data = nil
break
end
# キャッシュデータを更新します。
cache_data = cache_data[arg]
} if @instance_cache
# キャッシュが存在している場合はキャッシュデータを返却します。
return cache_data if cache_data
# 3行目. 元の処理を呼び出します。
ret_value = yield
# 4行目. プリミティブ型だけのキャッシュを行う場合はプリミティブ型以外の値を返却する。
return ret_value if self.instance_cache_only_primitive && !(ret_value.class == TrueClass ||
ret_value.class == FalseClass || ret_value.class == NilClass || ret_value.class == Fixnum || ret_value.class == String)
# 5行目. 値をキャッシュします。
@instance_cache ||= {}
key = nil
cache_arg.each_with_index { |arg, index|
cache_data ||= @instance_cache
# キャッシュデータを更新します。
cache_data[arg] ||= {}
if index + 1 < cache_arg.size
# 次のループに備えます。
cache_data = cache_data[arg]
else
# キーを保存します。
key = arg
end
}
cache_data[key] = ret_value
# 6行目. 処理結果を返します。
return ret_value
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment