require "cached_record/orm/active_record" require "cached_record/orm/data_mapper" module CachedRecord module ORM def self.included(base) base.extend ClassMethods base.send :include, InstanceMethods end module ClassMethods def as_cache(*args) @as_cache = parse_as_cache_options args if args.any? @as_cache ||= {:as_json => {}} end def as_memoized_cache(*args) retain = args.last.delete(:retain) if args.last.is_a?(Hash) as_cache(*args).tap do |options| options[:memoize] = true options[:retain] = retain if retain end end def cache_key(id) "#{name.underscore.gsub("/", ".")}.#{id}" end def cache_root "#{name.underscore.gsub(/^.*\//, "")}".to_sym end def cached(id) Cache.get(self, id) do uncached id end end def uncached(id) raise NotImplementedError, "Cannot fetch uncached `#{self.class}` instances" end def load_cache_json(json) json.symbolize_keys! properties, variables = cache_json_to_properties_and_variables(json) foreign_keys, attributes = properties.partition{|k, v| k.to_s.match /_ids?$/}.collect{|x| Hash[x]} new_cached_instance attributes, foreign_keys, variables end def new_cached_instance(attributes, foreign_keys, variables) id = attributes.delete(:id) || attributes.delete("id") _new_cached_instance_(id, attributes).tap do |instance| instance.id = id if instance.respond_to?(:id=) foreign_keys.each do |key, value| set_cached_association instance, key, value end variables.each do |key, value| instance.instance_variable_set key, value end end end private def _new_cached_instance_(id, attributes) new attributes end def set_cached_association(instance, key, value) raise NotImplementedError, "Cannot set cached association for `#{self}` instances" end def parse_as_cache_options(args) if (symbol = args.first).is_a? Symbol store = symbol end if (hash = args.last).is_a? Hash expire = hash.delete :expire as_json = parse_as_cache_json_options hash end { :store => store, :expire => expire, :as_json => as_json || {} }.reject{|key, value| value.nil?} end def parse_as_cache_json_options(options) options.symbolize_keys! validate_as_cache_json_options options {}.tap do |opts| opts[:only] = symbolize_array(options[:only]) if options[:only] opts[:include] = symbolize_array(options[:include]) if options[:include] opts[:memoize] = parse_memoize_options(options[:memoize]) if options[:memoize] opts[:include_root] = true if options[:include_root] end end def validate_as_cache_json_options(options) options.assert_valid_keys :only, :include, :memoize, :include_root options.slice(:only, :include).each do |key, value| raise ArgumentError unless value.is_a?(Array) end if options[:memoize] && !options[:memoize].is_a?(Enumerable) raise ArgumentError end if options.include?(:include_root) && ![true, false].include?(options[:include_root]) raise ArgumentError end end def symbolize_array(array) array.collect &:to_sym end def parse_memoize_options(options) [options].flatten.inject({}) do |memo, x| hash = x.is_a?(Hash) ? x : {x => :"@#{x}"} memo.merge hash.inject({}){|h, (k, v)| h[k.to_sym] = v.to_sym; h} end end def cache_json_to_properties_and_variables(json) if as_cache[:as_json][:include_root] properties = json.delete cache_root variables = json.inject({}){|h, (k, v)| h[:"@#{k}"] = v; h} [properties, variables] else json.partition{|k, v| !k.to_s.match(/^@/)}.collect{|x| Hash[x]} end end end module InstanceMethods def cache_attributes raise NotImplementedError, "Cannot return cache attributes for `#{self.class}` instances" end def cache_foreign_keys raise NotImplementedError, "Cannot return cache foreign keys for `#{self.class}` instances" end def as_cache_json attributes = {:id => id}.merge cache_attributes variables = (cache_json_options[:memoize] || {}).inject({}) do |hash, (method, variable)| hash[variable] = send method hash end merge_cache_json attributes, variables end def merge_cache_json(attributes, variables) if cache_json_options[:include_root] variables = variables.inject({}){|h, (k, v)| h[k.to_s.gsub(/^@/, "").to_sym] = v; h} {self.class.cache_root => attributes}.merge variables else attributes.merge variables end end def to_cache_json as_cache_json.to_json end def cache self.class.cached id true end def expire Cache.expire self true end private def cache_json_options self.class.as_cache[:as_json] || {} end end end end