From d7d457cd138885b53d2ef8f1d7ca0db5eb4e5163 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 18:13:44 +0000 Subject: [PATCH 1/4] Initial plan From 31246ae02d11fdf8c60e17555913d2d5ffb09dc5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 18:20:11 +0000 Subject: [PATCH 2/4] Add subdirectory delimiter feature with tests and documentation Co-authored-by: nhorton <204146+nhorton@users.noreply.github.com> --- README.md | 32 +++++ .../cache/source_control_cache_store.rb | 109 ++++++++++++++-- spec/source_control_cache_store_spec.rb | 117 ++++++++++++++++++ 3 files changed, 249 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 04b12d5..db9ff21 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,38 @@ cache.delete("my_key") cache.clear ``` +### Subdirectory Delimiter + +You can optionally configure a `subdirectory_delimiter` to organize cache entries into nested subdirectories based on key segments: + +```ruby +cache = ActiveSupport::Cache::SourceControlCacheStore.new( + cache_path: "/path/to/cache/directory", + subdirectory_delimiter: "---" +) + +# With delimiter "---", key "foo---bar---boo-ba" creates: +# /path/to/cache/directory/ +# hash(foo)/ +# _key_chunk (contains "foo") +# hash(bar)/ +# _key_chunk (contains "bar") +# hash(boo-ba)/ +# _key_chunk (contains "boo-ba") +# value (contains the cached value) + +cache.write("foo---bar---boo-ba", "27") +value = cache.read("foo---bar---boo-ba") # => "27" +``` + +When a delimiter is configured: +- The cache key is split by the delimiter into segments +- Each segment creates a subdirectory named `hash(segment)` using SHA256 +- Each subdirectory contains a `_key_chunk` file with the original segment text +- The cached value is stored in a `value` file in the final subdirectory + +This feature is useful for organizing cache entries hierarchically when keys have a natural structure. + ## Key Features ### Hashed Keys diff --git a/lib/active_support/cache/source_control_cache_store.rb b/lib/active_support/cache/source_control_cache_store.rb index 62421d1..6a252cb 100644 --- a/lib/active_support/cache/source_control_cache_store.rb +++ b/lib/active_support/cache/source_control_cache_store.rb @@ -19,23 +19,29 @@ module Cache # Example usage: # config.cache_store = :source_control_cache_store, cache_path: "tmp/cache" class SourceControlCacheStore < Store - attr_reader :cache_path + attr_reader :cache_path, :subdirectory_delimiter # Initialize a new SourceControlCacheStore # # @param cache_path [String] The directory where cache files will be stored + # @param subdirectory_delimiter [String, nil] Optional delimiter to split keys into subdirectories # @param options [Hash] Additional options (currently unused) - def initialize(cache_path:, **options) + def initialize(cache_path:, subdirectory_delimiter: nil, **options) super(options) @cache_path = cache_path + @subdirectory_delimiter = subdirectory_delimiter FileUtils.mkdir_p(@cache_path) end # Clear all cache entries def clear(options = nil) if File.directory?(@cache_path) - Dir.glob(File.join(@cache_path, "*")).each do |file| - File.delete(file) if File.file?(file) + Dir.glob(File.join(@cache_path, "*")).each do |path| + if File.file?(path) + File.delete(path) + elsif File.directory?(path) + FileUtils.rm_rf(path) + end end end true @@ -49,8 +55,7 @@ def clear(options = nil) # @param options [Hash] Options (unused) # @return [Object, nil] The cached value or nil if not found def read_entry(key, **options) - hash = hash_key(key) - value_file = value_path(hash) + value_file = value_path_for_key(key) return nil unless File.exist?(value_file) @@ -74,6 +79,18 @@ def read_entry(key, **options) # @param options [Hash] Options (expiration is ignored) # @return [Boolean] Returns true on success, false on failure def write_entry(key, entry, **options) + if @subdirectory_delimiter + write_entry_with_subdirectories(key, entry, **options) + else + write_entry_simple(key, entry, **options) + end + rescue StandardError + # Return false if write fails (permissions, disk space, etc.) + false + end + + # Write entry using simple hash-based file structure + def write_entry_simple(key, entry, **options) hash = hash_key(key) # Write the key file @@ -83,9 +100,27 @@ def write_entry(key, entry, **options) File.write(value_path(hash), serialize_entry(entry, **options)) true - rescue StandardError - # Return false if write fails (permissions, disk space, etc.) - false + end + + # Write entry using subdirectory structure + def write_entry_with_subdirectories(key, entry, **options) + chunks = key.to_s.split(@subdirectory_delimiter) + current_dir = @cache_path + + # Create subdirectories for each chunk + chunks.each_with_index do |chunk, index| + chunk_hash = hash_chunk(chunk) + current_dir = File.join(current_dir, chunk_hash) + FileUtils.mkdir_p(current_dir) + + # Write _key_chunk file + File.write(File.join(current_dir, "_key_chunk"), chunk) + end + + # Write the value file in the final directory + File.write(File.join(current_dir, "value"), serialize_entry(entry, **options)) + + true end # Delete an entry from the cache @@ -94,6 +129,15 @@ def write_entry(key, entry, **options) # @param options [Hash] Options (unused) # @return [Boolean] Returns true if any file was deleted def delete_entry(key, **options) + if @subdirectory_delimiter + delete_entry_with_subdirectories(key, **options) + else + delete_entry_simple(key, **options) + end + end + + # Delete entry using simple hash-based file structure + def delete_entry_simple(key, **options) hash = hash_key(key) key_file = key_path(hash) value_file = value_path(hash) @@ -115,6 +159,25 @@ def delete_entry(key, **options) deleted end + # Delete entry using subdirectory structure + def delete_entry_with_subdirectories(key, **options) + value_file = value_path_for_key(key) + + return false unless File.exist?(value_file) + + # Delete the entire directory tree for this key + chunks = key.to_s.split(@subdirectory_delimiter) + first_chunk_hash = hash_chunk(chunks[0]) + dir_to_delete = File.join(@cache_path, first_chunk_hash) + + begin + FileUtils.rm_rf(dir_to_delete) if File.exist?(dir_to_delete) + true + rescue StandardError + false + end + end + # Generate a hash for the given key # # @param key [String] The cache key @@ -123,6 +186,14 @@ def hash_key(key) ::Digest::SHA256.hexdigest(key.to_s) end + # Generate a hash for a key chunk + # + # @param chunk [String] A chunk of the cache key + # @return [String] The SHA256 hash of the chunk + def hash_chunk(chunk) + ::Digest::SHA256.hexdigest(chunk.to_s) + end + # Get the path for the key file # # @param hash [String] The hash of the key @@ -138,6 +209,26 @@ def key_path(hash) def value_path(hash) File.join(@cache_path, "#{hash}.value") end + + # Get the value file path for a given key + # + # @param key [String] The cache key + # @return [String] The full path to the value file + def value_path_for_key(key) + if @subdirectory_delimiter + chunks = key.to_s.split(@subdirectory_delimiter) + current_dir = @cache_path + + chunks.each do |chunk| + chunk_hash = hash_chunk(chunk) + current_dir = File.join(current_dir, chunk_hash) + end + + File.join(current_dir, "value") + else + value_path(hash_key(key)) + end + end end end end diff --git a/spec/source_control_cache_store_spec.rb b/spec/source_control_cache_store_spec.rb index 7b8f1ed..1b4221b 100644 --- a/spec/source_control_cache_store_spec.rb +++ b/spec/source_control_cache_store_spec.rb @@ -198,4 +198,121 @@ expect(store.read("false_key")).to eq(false) end end + + describe "subdirectory_delimiter feature" do + let(:cache_path_with_delimiter) { Dir.mktmpdir } + let(:store_with_delimiter) { described_class.new(cache_path: cache_path_with_delimiter, subdirectory_delimiter: "---") } + + after do + FileUtils.rm_rf(cache_path_with_delimiter) if File.exist?(cache_path_with_delimiter) + end + + it "stores subdirectory_delimiter parameter" do + expect(store_with_delimiter.subdirectory_delimiter).to eq("---") + end + + it "creates nested directories for split keys" do + store_with_delimiter.write("foo---bar---boo-ba", "27") + + # Calculate expected hashes + foo_hash = Digest::SHA256.hexdigest("foo") + bar_hash = Digest::SHA256.hexdigest("bar") + boo_ba_hash = Digest::SHA256.hexdigest("boo-ba") + + # Check that directories exist + expect(File.directory?(File.join(cache_path_with_delimiter, foo_hash))).to be true + expect(File.directory?(File.join(cache_path_with_delimiter, foo_hash, bar_hash))).to be true + expect(File.directory?(File.join(cache_path_with_delimiter, foo_hash, bar_hash, boo_ba_hash))).to be true + end + + it "creates _key_chunk files with correct content" do + store_with_delimiter.write("foo---bar---boo-ba", "27") + + foo_hash = Digest::SHA256.hexdigest("foo") + bar_hash = Digest::SHA256.hexdigest("bar") + boo_ba_hash = Digest::SHA256.hexdigest("boo-ba") + + # Check _key_chunk files + foo_chunk_file = File.join(cache_path_with_delimiter, foo_hash, "_key_chunk") + bar_chunk_file = File.join(cache_path_with_delimiter, foo_hash, bar_hash, "_key_chunk") + boo_ba_chunk_file = File.join(cache_path_with_delimiter, foo_hash, bar_hash, boo_ba_hash, "_key_chunk") + + expect(File.read(foo_chunk_file)).to eq("foo") + expect(File.read(bar_chunk_file)).to eq("bar") + expect(File.read(boo_ba_chunk_file)).to eq("boo-ba") + end + + it "stores value in the final directory" do + store_with_delimiter.write("foo---bar---boo-ba", "27") + + foo_hash = Digest::SHA256.hexdigest("foo") + bar_hash = Digest::SHA256.hexdigest("bar") + boo_ba_hash = Digest::SHA256.hexdigest("boo-ba") + + value_file = File.join(cache_path_with_delimiter, foo_hash, bar_hash, boo_ba_hash, "value") + + expect(File.exist?(value_file)).to be true + expect(store_with_delimiter.read("foo---bar---boo-ba")).to eq("27") + end + + it "reads values correctly from subdirectory structure" do + store_with_delimiter.write("alpha---beta", "test_value") + expect(store_with_delimiter.read("alpha---beta")).to eq("test_value") + end + + it "handles single chunk keys (no delimiter present)" do + store_with_delimiter.write("single_key", "single_value") + + single_hash = Digest::SHA256.hexdigest("single_key") + value_file = File.join(cache_path_with_delimiter, single_hash, "value") + + expect(File.exist?(value_file)).to be true + expect(store_with_delimiter.read("single_key")).to eq("single_value") + end + + it "deletes entries in subdirectory structure" do + store_with_delimiter.write("foo---bar---baz", "value") + expect(store_with_delimiter.read("foo---bar---baz")).to eq("value") + + result = store_with_delimiter.delete("foo---bar---baz") + expect(result).to be_truthy + expect(store_with_delimiter.read("foo---bar---baz")).to be_nil + end + + it "clears all entries including subdirectories" do + store_with_delimiter.write("key1---sub1", "value1") + store_with_delimiter.write("key2---sub2", "value2") + store_with_delimiter.write("key3---sub3---sub4", "value3") + + expect(Dir.glob(File.join(cache_path_with_delimiter, "*")).length).to be > 0 + + store_with_delimiter.clear + + expect(Dir.glob(File.join(cache_path_with_delimiter, "*")).length).to eq(0) + end + + it "uses fetch correctly with subdirectory structure" do + result = store_with_delimiter.fetch("new---key") { "computed" } + expect(result).to eq("computed") + expect(store_with_delimiter.read("new---key")).to eq("computed") + end + + it "overwrites existing values in subdirectory structure" do + store_with_delimiter.write("key---sub", "value1") + store_with_delimiter.write("key---sub", "value2") + expect(store_with_delimiter.read("key---sub")).to eq("value2") + end + + it "handles complex objects in subdirectory structure" do + complex_object = { name: "Test", data: [1, 2, 3] } + store_with_delimiter.write("obj---data", complex_object) + expect(store_with_delimiter.read("obj---data")).to eq(complex_object) + end + + it "handles many levels of nesting" do + key = "a---b---c---d---e---f" + store_with_delimiter.write(key, "deep_value") + expect(store_with_delimiter.read(key)).to eq("deep_value") + end + end end From e8384a227245042d235d4142544fb8083b3796ed Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 18:24:03 +0000 Subject: [PATCH 3/4] Replace SHA256 with ActiveSupport::Digest and fix deletion logic Co-authored-by: nhorton <204146+nhorton@users.noreply.github.com> --- README.md | 6 +-- .../cache/source_control_cache_store.rb | 23 +++++---- spec/source_control_cache_store_spec.rb | 47 +++++++++++++------ 3 files changed, 49 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index db9ff21..a20dc9d 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ This cache store is designed to be committed to version control, making it ideal ## Features - **File-based storage**: Each cache entry is stored as separate `.key` and `.value` files -- **Hashed filenames**: Uses SHA256 hashing for keys to create consistent, filesystem-safe filenames +- **Hashed filenames**: Uses ActiveSupport::Digest for keys to create consistent, filesystem-safe filenames - **No expiration**: Cache entries do NOT honor expiration parameters - they persist until explicitly deleted - **Rails 7.1+ compatible**: Implements the ActiveSupport::Cache::Store interface @@ -94,7 +94,7 @@ value = cache.read("foo---bar---boo-ba") # => "27" When a delimiter is configured: - The cache key is split by the delimiter into segments -- Each segment creates a subdirectory named `hash(segment)` using SHA256 +- Each segment creates a subdirectory named `hash(segment)` using ActiveSupport::Digest - Each subdirectory contains a `_key_chunk` file with the original segment text - The cached value is stored in a `value` file in the final subdirectory @@ -104,7 +104,7 @@ This feature is useful for organizing cache entries hierarchically when keys hav ### Hashed Keys -Keys are hashed using SHA256 to create filesystem-safe filenames. The original key is preserved in the `.key` file, while the hash is used for the filename: +Keys are hashed using ActiveSupport::Digest to create filesystem-safe filenames. The original key is preserved in the `.key` file, while the hash is used for the filename: ```ruby cache.write("user:123:profile", { name: "John" }) diff --git a/lib/active_support/cache/source_control_cache_store.rb b/lib/active_support/cache/source_control_cache_store.rb index 6a252cb..17c5494 100644 --- a/lib/active_support/cache/source_control_cache_store.rb +++ b/lib/active_support/cache/source_control_cache_store.rb @@ -4,7 +4,6 @@ require "active_support/cache" require "active_support/notifications" require "active_support/core_ext/object/json" -require "digest" require "fileutils" module ActiveSupport @@ -165,13 +164,19 @@ def delete_entry_with_subdirectories(key, **options) return false unless File.exist?(value_file) - # Delete the entire directory tree for this key + # Delete only the deepest directory containing this specific entry chunks = key.to_s.split(@subdirectory_delimiter) - first_chunk_hash = hash_chunk(chunks[0]) - dir_to_delete = File.join(@cache_path, first_chunk_hash) + + # Build the full path to the final directory + current_dir = @cache_path + chunks.each do |chunk| + chunk_hash = hash_chunk(chunk) + current_dir = File.join(current_dir, chunk_hash) + end begin - FileUtils.rm_rf(dir_to_delete) if File.exist?(dir_to_delete) + # Delete the final directory (containing _key_chunk and value) + FileUtils.rm_rf(current_dir) if File.exist?(current_dir) true rescue StandardError false @@ -181,17 +186,17 @@ def delete_entry_with_subdirectories(key, **options) # Generate a hash for the given key # # @param key [String] The cache key - # @return [String] The SHA256 hash of the key + # @return [String] The hash of the key def hash_key(key) - ::Digest::SHA256.hexdigest(key.to_s) + ::ActiveSupport::Digest.hexdigest(key.to_s) end # Generate a hash for a key chunk # # @param chunk [String] A chunk of the cache key - # @return [String] The SHA256 hash of the chunk + # @return [String] The hash of the chunk def hash_chunk(chunk) - ::Digest::SHA256.hexdigest(chunk.to_s) + ::ActiveSupport::Digest.hexdigest(chunk.to_s) end # Get the path for the key file diff --git a/spec/source_control_cache_store_spec.rb b/spec/source_control_cache_store_spec.rb index 1b4221b..37aec00 100644 --- a/spec/source_control_cache_store_spec.rb +++ b/spec/source_control_cache_store_spec.rb @@ -45,7 +45,7 @@ store.write("my_key", "my_value") # Calculate the expected hash - hash = Digest::SHA256.hexdigest("my_key") + hash = ActiveSupport::Digest.hexdigest("my_key") key_file = File.join(cache_path, "#{hash}.key") value_file = File.join(cache_path, "#{hash}.value") @@ -57,7 +57,7 @@ original_key = "my_special_key" store.write(original_key, "value") - hash = Digest::SHA256.hexdigest(original_key) + hash = ActiveSupport::Digest.hexdigest(original_key) key_file = File.join(cache_path, "#{hash}.key") expect(File.read(key_file)).to eq(original_key) @@ -82,7 +82,7 @@ it "removes both .key and .value files" do store.write("key", "value") - hash = Digest::SHA256.hexdigest("key") + hash = ActiveSupport::Digest.hexdigest("key") key_file = File.join(cache_path, "#{hash}.key") value_file = File.join(cache_path, "#{hash}.value") @@ -149,9 +149,9 @@ end describe "key hashing" do - it "uses SHA256 for hashing keys" do + it "uses ActiveSupport::Digest for hashing keys" do key = "test_key" - expected_hash = Digest::SHA256.hexdigest(key) + expected_hash = ActiveSupport::Digest.hexdigest(key) store.write(key, "value") @@ -215,9 +215,9 @@ store_with_delimiter.write("foo---bar---boo-ba", "27") # Calculate expected hashes - foo_hash = Digest::SHA256.hexdigest("foo") - bar_hash = Digest::SHA256.hexdigest("bar") - boo_ba_hash = Digest::SHA256.hexdigest("boo-ba") + foo_hash = ActiveSupport::Digest.hexdigest("foo") + bar_hash = ActiveSupport::Digest.hexdigest("bar") + boo_ba_hash = ActiveSupport::Digest.hexdigest("boo-ba") # Check that directories exist expect(File.directory?(File.join(cache_path_with_delimiter, foo_hash))).to be true @@ -228,9 +228,9 @@ it "creates _key_chunk files with correct content" do store_with_delimiter.write("foo---bar---boo-ba", "27") - foo_hash = Digest::SHA256.hexdigest("foo") - bar_hash = Digest::SHA256.hexdigest("bar") - boo_ba_hash = Digest::SHA256.hexdigest("boo-ba") + foo_hash = ActiveSupport::Digest.hexdigest("foo") + bar_hash = ActiveSupport::Digest.hexdigest("bar") + boo_ba_hash = ActiveSupport::Digest.hexdigest("boo-ba") # Check _key_chunk files foo_chunk_file = File.join(cache_path_with_delimiter, foo_hash, "_key_chunk") @@ -245,9 +245,9 @@ it "stores value in the final directory" do store_with_delimiter.write("foo---bar---boo-ba", "27") - foo_hash = Digest::SHA256.hexdigest("foo") - bar_hash = Digest::SHA256.hexdigest("bar") - boo_ba_hash = Digest::SHA256.hexdigest("boo-ba") + foo_hash = ActiveSupport::Digest.hexdigest("foo") + bar_hash = ActiveSupport::Digest.hexdigest("bar") + boo_ba_hash = ActiveSupport::Digest.hexdigest("boo-ba") value_file = File.join(cache_path_with_delimiter, foo_hash, bar_hash, boo_ba_hash, "value") @@ -263,7 +263,7 @@ it "handles single chunk keys (no delimiter present)" do store_with_delimiter.write("single_key", "single_value") - single_hash = Digest::SHA256.hexdigest("single_key") + single_hash = ActiveSupport::Digest.hexdigest("single_key") value_file = File.join(cache_path_with_delimiter, single_hash, "value") expect(File.exist?(value_file)).to be true @@ -314,5 +314,22 @@ store_with_delimiter.write(key, "deep_value") expect(store_with_delimiter.read(key)).to eq("deep_value") end + + it "deletes only the specific entry without affecting others with common prefixes" do + # Write two keys that share the first chunk + store_with_delimiter.write("foo---bar", "value1") + store_with_delimiter.write("foo---baz", "value2") + + # Verify both exist + expect(store_with_delimiter.read("foo---bar")).to eq("value1") + expect(store_with_delimiter.read("foo---baz")).to eq("value2") + + # Delete the first one + store_with_delimiter.delete("foo---bar") + + # Verify only the deleted one is gone + expect(store_with_delimiter.read("foo---bar")).to be_nil + expect(store_with_delimiter.read("foo---baz")).to eq("value2") + end end end From cb09fd577381f6d692d834498918af0fa0981d79 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 18:26:37 +0000 Subject: [PATCH 4/4] Add docstrings and refactor to reduce code duplication Co-authored-by: nhorton <204146+nhorton@users.noreply.github.com> --- .../cache/source_control_cache_store.rb | 51 ++++++++++++------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/lib/active_support/cache/source_control_cache_store.rb b/lib/active_support/cache/source_control_cache_store.rb index 17c5494..1604d92 100644 --- a/lib/active_support/cache/source_control_cache_store.rb +++ b/lib/active_support/cache/source_control_cache_store.rb @@ -89,6 +89,11 @@ def write_entry(key, entry, **options) end # Write entry using simple hash-based file structure + # + # @param key [String] The cache key + # @param entry [ActiveSupport::Cache::Entry] The cache entry + # @param options [Hash] Options (expiration is ignored) + # @return [Boolean] Returns true on success def write_entry_simple(key, entry, **options) hash = hash_key(key) @@ -102,12 +107,17 @@ def write_entry_simple(key, entry, **options) end # Write entry using subdirectory structure + # + # @param key [String] The cache key + # @param entry [ActiveSupport::Cache::Entry] The cache entry + # @param options [Hash] Options (expiration is ignored) + # @return [Boolean] Returns true on success def write_entry_with_subdirectories(key, entry, **options) chunks = key.to_s.split(@subdirectory_delimiter) current_dir = @cache_path # Create subdirectories for each chunk - chunks.each_with_index do |chunk, index| + chunks.each do |chunk| chunk_hash = hash_chunk(chunk) current_dir = File.join(current_dir, chunk_hash) FileUtils.mkdir_p(current_dir) @@ -159,20 +169,17 @@ def delete_entry_simple(key, **options) end # Delete entry using subdirectory structure + # + # @param key [String] The cache key + # @param options [Hash] Options (unused) + # @return [Boolean] Returns true if the entry was deleted def delete_entry_with_subdirectories(key, **options) value_file = value_path_for_key(key) return false unless File.exist?(value_file) # Delete only the deepest directory containing this specific entry - chunks = key.to_s.split(@subdirectory_delimiter) - - # Build the full path to the final directory - current_dir = @cache_path - chunks.each do |chunk| - chunk_hash = hash_chunk(chunk) - current_dir = File.join(current_dir, chunk_hash) - end + current_dir = subdirectory_path_for_key(key) begin # Delete the final directory (containing _key_chunk and value) @@ -221,19 +228,27 @@ def value_path(hash) # @return [String] The full path to the value file def value_path_for_key(key) if @subdirectory_delimiter - chunks = key.to_s.split(@subdirectory_delimiter) - current_dir = @cache_path - - chunks.each do |chunk| - chunk_hash = hash_chunk(chunk) - current_dir = File.join(current_dir, chunk_hash) - end - - File.join(current_dir, "value") + File.join(subdirectory_path_for_key(key), "value") else value_path(hash_key(key)) end end + + # Get the subdirectory path for a given key + # + # @param key [String] The cache key + # @return [String] The full path to the subdirectory for this key + def subdirectory_path_for_key(key) + chunks = key.to_s.split(@subdirectory_delimiter) + current_dir = @cache_path + + chunks.each do |chunk| + chunk_hash = hash_chunk(chunk) + current_dir = File.join(current_dir, chunk_hash) + end + + current_dir + end end end end