lib/gitmodel/persistable.rb in gitmodel-0.0.4 vs lib/gitmodel/persistable.rb in gitmodel-0.0.5

- old
+ new

@@ -130,31 +130,31 @@ end private - def load(dir) + def load(dir, branch) _run_find_callbacks do # remove dangerous ".." # todo find a better way to ensure path is safe dir.gsub!(/\.\./, '') - raise GitModel::RecordNotFound if GitModel.current_tree.nil? + raise GitModel::RecordNotFound if GitModel.current_tree(branch).nil? self.id = File.basename(dir) @new_record = false GitModel.logger.debug "Loading #{self.class.name} with id: #{id}" # load the attributes - object = GitModel.current_tree / File.join(dir, 'attributes.json') + object = GitModel.current_tree(branch) / File.join(dir, 'attributes.json') raise GitModel::RecordNotFound if object.nil? self.attributes = Yajl::Parser.parse(object.data) # load all other non-hidden files in the dir as blobs - blobs = (GitModel.current_tree / dir).blobs.reject{|b| b.name[0] == '.' || b.name == 'attributes.json'} + blobs = (GitModel.current_tree(branch) / dir).blobs.reject{|b| b.name[0] == '.' || b.name == 'attributes.json'} blobs.each do |b| self.blobs[b.name] = b.data end end end @@ -179,99 +179,119 @@ def #{name}; blobs[:#{name}]; end def #{name}=(value); blobs[:#{name}] = value; end EOF end - def find(id) + def find(id, branch = GitModel.default_branch) GitModel.logger.debug "Finding #{name} with id: #{id}" - o = new - dir = File.join(db_subdir, id) - o.send :load, dir - return o + result = GitModel.cache(branch, "#{db_subdir}-find-#{id}") do + o = new + dir = File.join(db_subdir, id) + o.send :load, dir, branch + o + end + return result end - def exists?(id) + def exists?(id, branch = GitModel.default_branch) GitModel.logger.debug "Checking existence of #{name} with id: #{id}" - GitModel.repo.commits.any? && !(GitModel.current_tree / File.join(db_subdir, id, 'attributes.json')).nil? + result = GitModel.cache(branch, "#{db_subdir}-exists-#{id}") do + GitModel.repo.commits.any? && !(GitModel.current_tree(branch) / File.join(db_subdir, id, 'attributes.json')).nil? + end + return result end + # TODO document conditions + # :branch + # :cache_key + # :order_by + # :order + # any model attribute def find_all(conditions = {}) + branch = conditions.delete(:branch) || GitModel.default_branch # TODO Refactor this spaghetti GitModel.logger.debug "Finding all #{name.pluralize} with conditions: #{conditions.inspect}" - return [] unless GitModel.current_tree + cache_key = "#{db_subdir}-find_all-#{format_conditions_hash_for_cache_key(conditions)}" + cached_results = GitModel.cache(branch, cache_key) do + current_tree = GitModel.current_tree(branch) + unless current_tree + [] + else + order = conditions.delete(:order) || :asc + order_by = conditions.delete(:order_by) || :id + limit = conditions.delete(:limit) - order = conditions.delete(:order) || :asc - order_by = conditions.delete(:order_by) || :id - limit = conditions.delete(:limit) - - matching_ids = [] - if conditions.empty? # load all objects - trees = (GitModel.current_tree / db_subdir).trees - trees.each do |t| - matching_ids << t.name if t.blobs.any? - end - else # only load objects that match conditions - matching_ids_for_condition = {} - conditions.each do |k,v| - matching_ids_for_condition[k] = [] - if k == :id # id isn't indexed - if v.is_a?(Proc) - trees = (GitModel.current_tree / db_subdir).trees - trees.each do |t| - matching_ids_for_condition[k] << t.name if t.blobs.any? && v.call(t.name) - end - else - # an unlikely use case but supporting it for completeness - matching_ids_for_condition[k] << v if (GitModel.current_tree / db_subdir / v) + matching_ids = [] + if conditions.empty? # load all objects + trees = (current_tree / db_subdir).trees + trees.each do |t| + matching_ids << t.name if t.blobs.any? end - else - raise GitModel::IndexRequired unless index.generated? - attr_index = index.attr_index(k) - if v.is_a?(Proc) - attr_index.each do |value, ids| - matching_ids_for_condition[k] += ids.to_a if v.call(value) + else # only load objects that match conditions + matching_ids_for_condition = {} + conditions.each do |k,v| + matching_ids_for_condition[k] = [] + if k == :id # id isn't indexed + if v.is_a?(Proc) + trees = (current_tree / db_subdir).trees + trees.each do |t| + matching_ids_for_condition[k] << t.name if t.blobs.any? && v.call(t.name) + end + else + # an unlikely use case but supporting it for completeness + matching_ids_for_condition[k] << v if (current_tree / db_subdir / v) + end + else + raise GitModel::IndexRequired unless index.generated? + attr_index = index.attr_index(k) + if v.is_a?(Proc) + attr_index.each do |value, ids| + matching_ids_for_condition[k] += ids.to_a if v.call(value) + end + else + matching_ids_for_condition[k] += attr_index[v].to_a + end end - else - matching_ids_for_condition[k] += attr_index[v].to_a end + matching_ids += matching_ids_for_condition.values.inject{|memo, obj| memo & obj} end - end - matching_ids += matching_ids_for_condition.values.inject{|memo, obj| memo & obj} - end - results = nil - if order_by != :id - GitModel.logger.warn "Ordering by an attribute other than id requires loading all matching objects before applying limit, this will be slow" if limit - results = matching_ids.map{|k| find(k)} + results = nil + if order_by != :id + GitModel.logger.warn "Ordering by an attribute other than id requires loading all matching objects before applying limit, this will be slow" if limit + results = matching_ids.map{|k| find(k)} - if order == :asc - results = results.sort{|a,b| a.send(order_by) <=> b.send(order_by)} - elsif order == :desc - results = results.sort{|b,a| a.send(order_by) <=> b.send(order_by)} - else - raise GitModel::InvalidParams("invalid order: '#{order}'") - end + if order == :asc + results = results.sort{|a,b| a.send(order_by) <=> b.send(order_by)} + elsif order == :desc + results = results.sort{|b,a| a.send(order_by) <=> b.send(order_by)} + else + raise GitModel::InvalidParams("invalid order: '#{order}'") + end - if limit - results = results[0, limit] - end - else - if order == :asc - matching_ids = matching_ids.sort{|a,b| a <=> b} - elsif order == :desc - matching_ids = matching_ids.sort{|b,a| a <=> b} - else - raise GitModel::InvalidParams("invalid order: '#{order}'") - end - if limit + if limit + results = results[0, limit] + end + else + if order == :asc + matching_ids = matching_ids.sort{|a,b| a <=> b} + elsif order == :desc + matching_ids = matching_ids.sort{|b,a| a <=> b} + else + raise GitModel::InvalidParams("invalid order: '#{order}'") + end + if limit - matching_ids = matching_ids[0, limit] - end - results = matching_ids.map{|k| find(k)} - end + matching_ids = matching_ids[0, limit] + end + results = matching_ids.map{|k| find(k)} + end - return results + results + end + end # cached block + return cached_results end def all_values_for_attr(attr) attr_index = index.attr_index(attr.to_s) values = attr_index ? attr_index.keys : [] @@ -300,31 +320,33 @@ def delete(id, options = {}) GitModel.logger.debug "Deleting #{name} with id: #{id}" path = File.join(db_subdir, id) transaction = options.delete(:transaction) || GitModel::Transaction.new(options) result = transaction.execute do |t| - delete_tree(path, t.index, options) + branch = t.branch || options[:branch] || GitModel.default_branch + delete_tree(path, t.index, branch, options) end end def delete_all(options = {}) GitModel.logger.debug "Deleting all #{name.pluralize}" transaction = options.delete(:transaction) || GitModel::Transaction.new(options) result = transaction.execute do |t| - delete_tree(db_subdir, t.index, options) + branch = t.branch || options[:branch] || GitModel.default_branch + delete_tree(db_subdir, t.index, branch, options) end end - def index! - index.generate! - index.save + def index!(branch) + index.generate!(branch) + index.save(:branch => branch) end private - def delete_tree(path, index, options = {}) + def delete_tree(path, index, branch, options = {}) # This leaves a bunch of empty sub-trees, there must be a way to just # replace the tree to be deleted with an empty tree that doesn't even # reference the sub-trees. current = index.tree path.split('/').each do |dir| @@ -343,9 +365,28 @@ tree.trees.each do |t| hash[t.name] = {} build_tree_hash(hash[t.name], t) end return hash + end + + def format_conditions_hash_for_cache_key(hash) + # allow setting an explicit cache key, mostly because Proc.hash is + # usually different even with the same code and same parameters + cache_key = hash.delete(:cache_key) + + unless cache_key + cache_key = "" + hash.inject('') do |s,kv| + key = kv[0] + val = kv[1] + if val.is_a?(Proc) + val = "proc-#{val.hash}" + end + cache_key += "#{key}:#{val};" + end + end + cache_key end end # module ClassMethods end # module Persistable