Skip to content

Commit

Permalink
Add fetch_multi_by_* support for cache_index with a single field
Browse files Browse the repository at this point in the history
  • Loading branch information
dylanahsmith authored and Matt Scriven committed Mar 19, 2019
1 parent 32aac1a commit 5d2c5ef
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 1 deletion.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ end
# If the object isn't in the cache it is pulled from the db and stored in the cache.
product = Product.fetch_by_handle(handle)

# Fetch multiple products by providing an array of index values.
products = Product.fetch_multi_by_handle(handles)

products = Product.fetch_by_vendor_and_product_type(vendor, product_type)
```

Expand Down
64 changes: 64 additions & 0 deletions lib/identity_cache/configuration_dsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,16 @@ def fetch_by_#{field_list}(#{arg_list}, includes: nil)
end
CODE
end

if fields.length == 1
self.instance_eval(ruby = <<-CODE, __FILE__, __LINE__ + 1)
def fetch_multi_by_#{field_list}(index_values, includes: nil)
ids = fetch_multi_id_by_#{field_list}(index_values).values.flatten(1)
return ids if ids.empty?
fetch_multi(ids, includes: includes)
end
CODE
end
end


Expand Down Expand Up @@ -185,6 +195,14 @@ def fetch_#{alias_name}_by_#{field_list}(#{arg_list})
attribute_dynamic_fetcher(#{attribute}, #{fields.inspect}, [#{arg_list}], #{unique})
end
CODE

if fields.length == 1
self.instance_eval(<<-CODE, __FILE__, __LINE__ + 1)
def fetch_multi_#{alias_name}_by_#{field_list}(index_values)
batch_attribute_dynamic_fetcher(#{attribute}, #{fields.first.to_s.inspect}.freeze, index_values, #{unique})
end
CODE
end
end

def build_recursive_association_cache(association, options) #:nodoc:
Expand Down Expand Up @@ -272,6 +290,52 @@ def dynamic_attribute_cache_miss(attribute, fields, values, unique_index)
unique_index ? results.first : results
end

def batch_attribute_dynamic_fetcher(attribute, field, index_values, unique_index)
raise_if_scoped

type = type_for_attribute(field)
index_values = index_values.map { |value| type.cast(value) }

unless should_use_cache?
return batch_dynamic_attribute_cache_miss(attribute, field, index_values, unique_index)
end

fields = [field]
index_by_cache_key = index_values.each_with_object({}) do |index_value, index_by_cache_key|
cache_key = rails_cache_key_for_attribute_and_fields_and_values(attribute, fields, [index_value], unique_index)
index_by_cache_key[cache_key] = index_value
end
attribute_by_cache_key = IdentityCache.fetch_multi(index_by_cache_key.keys) do |unresolved_keys|
unresolved_index_values = unresolved_keys.map { |cache_key| index_by_cache_key.fetch(cache_key) }
resolved_attributes = batch_dynamic_attribute_cache_miss(attribute, field, unresolved_index_values, unique_index)
unresolved_index_values.map { |index_value| resolved_attributes.fetch(index_value) }
end
result = {}
attribute_by_cache_key.each do |cache_key, attribute_value|
result[index_by_cache_key.fetch(cache_key)] = attribute_value
end
result
end

def batch_dynamic_attribute_cache_miss(attribute, index_field, values, unique_index)
rows = reorder(nil).where(index_field => values).pluck(index_field, attribute)
result = {}
default = unique_index ? nil : []
values.each do |index_value|
result[index_value] = default.dup
end
if unique_index
rows.each do |index_value, attribute_value|
result[index_value] = attribute_value
end
else
rows.each do |index_value, attribute_value|
result[index_value] << attribute_value
end
end
result
end

def ensure_base_model
if self != cached_model
raise DerivedModelError, "IdentityCache class methods must be called on the same model that includes IdentityCache"
Expand Down
61 changes: 61 additions & 0 deletions test/fetch_multi_by_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
require "test_helper"

class FetchMultiByTest < IdentityCache::TestCase
NAMESPACE = IdentityCache::CacheKeyGeneration::DEFAULT_NAMESPACE

def setup
super
@bob = Item.new
@bob.id = 1
@bob.title = 'bob'

@bertha = Item.new
@bertha.id = 2
@bertha.title = 'bertha'
end

def test_fetch_multi_by_cache_key
Item.cache_index :title, unique: false

@bob.save!
@bertha.save!

assert_equal [@bob], Item.fetch_by_title('bob')

assert_equal [@bob, @bertha], Item.fetch_multi_by_title(['bob', 'bertha'])
end

def test_fetch_multi_by_unique_cache_key
Item.cache_index :title, unique: true

@bob.save!
@bertha.save!

assert_equal @bob, Item.fetch_by_title('bob')

assert_equal [@bob, @bertha], Item.fetch_multi_by_title(['bob', 'bertha'])
end

def test_fetch_multi_attribute_by_cache_key
Item.cache_attribute :title, by: :id, unique: false

@bob.save!
@bertha.save!

assert_equal ['bob'], Item.fetch_title_by_id(1)

assert_equal({ 1 => ['bob'], 2 => ['bertha'] }, Item.fetch_multi_title_by_id([1, 2]))
end


def test_fetch_multi_attribute_by_unique_cache_key
Item.cache_attribute :title, by: :id, unique: true

@bob.save!
@bertha.save!

assert_equal 'bob', Item.fetch_title_by_id(1)

assert_equal({ 1 => 'bob', 2 => 'bertha' }, Item.fetch_multi_title_by_id([1, 2]))
end
end
2 changes: 1 addition & 1 deletion test/index_cache_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def test_fetch_with_garbage_input
Item.cache_index :title, :id

assert_queries(1) do
assert_equal [], Item.fetch_by_title_and_id('garbage', 'garbage')
assert_equal [], Item.fetch_by_title_and_id('garbage_title', 'garbage_id')
end
end

Expand Down

0 comments on commit 5d2c5ef

Please sign in to comment.