Last active
December 12, 2015 10:29
-
-
Save syou007/4759782 to your computer and use it in GitHub Desktop.
rubyのメソッドをキャッシュするコードです。詳細はブログで紹介してます。
http://ameblo.jp/syou007/entry-11468918541.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 使用例 | |
def hoge | |
# キャッシュしたい処理 | |
end | |
instance_cache :hoge | |
def hoge | |
# 何かの処理 | |
cache_data = instance_cache { | |
# キャッシュしたい処理 | |
} | |
# 何かの処理 | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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