Skip to content

Commit 6b30216

Browse files
committed
Refactor Chain module. Move #key_present? to separate class KeyesDetector
1 parent 8bf372e commit 6b30216

File tree

3 files changed

+144
-118
lines changed

3 files changed

+144
-118
lines changed

lib/dynamoid/criteria/chain.rb

Lines changed: 26 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
# frozen_string_literal: true
22

3+
require_relative 'keys_detector'
4+
35
module Dynamoid #:nodoc:
46
module Criteria
57
# The criteria chain is equivalent to an ActiveRecord relation (and realistically I should change the name from
68
# chain to relation). It is a chainable object that builds up a query and eventually executes it by a Query or Scan.
79
class Chain
8-
attr_reader :hash_key, :range_key, :index_name
9-
attr_reader :query, :source, :consistent_read
10+
attr_reader :query, :source, :consistent_read, :keys_detector
11+
1012
include Enumerable
1113
# Create a new criteria chain.
1214
#
@@ -22,6 +24,9 @@ def initialize(source)
2224
if @source.attributes.key?(type)
2325
@query[:"#{type}.in"] = @source.deep_subclasses.map(&:name) << @source.name
2426
end
27+
28+
# we should re-initialize keys detector every time we change query
29+
@keys_detector = KeysDetector.new(@query, @source)
2530
end
2631

2732
# The workhorse method of the criteria chain. Each key in the passed in hash will become another criteria that the
@@ -37,6 +42,10 @@ def initialize(source)
3742
# @since 0.2.0
3843
def where(args)
3944
query.update(args.dup.symbolize_keys)
45+
46+
# we should re-initialize keys detector every time we change query
47+
@keys_detector = KeysDetector.new(@query, @source)
48+
4049
self
4150
end
4251

@@ -53,7 +62,7 @@ def all
5362
end
5463

5564
def count
56-
if key_present?
65+
if @keys_detector.key_present?
5766
count_via_query
5867
else
5968
count_via_scan
@@ -74,7 +83,7 @@ def delete_all
7483
ids = []
7584
ranges = []
7685

77-
if key_present?
86+
if @keys_detector.key_present?
7887
Dynamoid.adapter.query(source.table_name, range_query).flat_map{ |i| i }.collect do |hash|
7988
ids << hash[source.hash_key.to_sym]
8089
ranges << hash[source.range_key.to_sym] if source.range_key
@@ -149,7 +158,7 @@ def records
149158
#
150159
# @since 3.1.0
151160
def pages
152-
if key_present?
161+
if @keys_detector.key_present?
153162
pages_via_query
154163
else
155164
issue_scan_warning if Dynamoid::Config.warn_on_scan && query.present?
@@ -257,24 +266,24 @@ def range_query
257266
opts = {}
258267

259268
# Add hash key
260-
opts[:hash_key] = @hash_key
261-
opts[:hash_value] = type_cast_condition_parameter(@hash_key, query[@hash_key])
269+
opts[:hash_key] = @keys_detector.hash_key
270+
opts[:hash_value] = type_cast_condition_parameter(@keys_detector.hash_key, query[@keys_detector.hash_key])
262271

263272
# Add range key
264-
if @range_key
265-
opts[:range_key] = @range_key
266-
if query[@range_key].present?
267-
value = type_cast_condition_parameter(@range_key, query[@range_key])
273+
if @keys_detector.range_key
274+
opts[:range_key] = @keys_detector.range_key
275+
if query[@keys_detector.range_key].present?
276+
value = type_cast_condition_parameter(@keys_detector.range_key, query[@keys_detector.range_key])
268277
opts.update(range_eq: value)
269278
end
270279

271-
query.keys.select { |k| k.to_s =~ /^#{@range_key}\./ }.each do |key|
280+
query.keys.select { |k| k.to_s =~ /^#{@keys_detector.range_key}\./ }.each do |key|
272281
opts.merge!(range_hash(key))
273282
end
274283
end
275284

276-
(query.keys.map(&:to_sym) - [@hash_key.to_sym, @range_key.try(:to_sym)])
277-
.reject { |k, _| k.to_s =~ /^#{@range_key}\./ }
285+
(query.keys.map(&:to_sym) - [@keys_detector.hash_key.to_sym, @keys_detector.range_key.try(:to_sym)])
286+
.reject { |k, _| k.to_s =~ /^#{@keys_detector.range_key}\./ }
278287
.each do |key|
279288
if key.to_s.include?('.')
280289
opts.update(field_hash(key))
@@ -303,58 +312,14 @@ def type_cast_condition_parameter(key, value)
303312
end
304313
end
305314

306-
def key_present?
307-
query_keys = query.keys.collect { |k| k.to_s.split('.').first }
308-
309-
# See if querying based on table hash key
310-
if query.keys.map(&:to_s).include?(source.hash_key.to_s)
311-
@hash_key = source.hash_key
312-
313-
# Use table's default range key
314-
if query_keys.include?(source.range_key.to_s)
315-
@range_key = source.range_key
316-
return true
317-
end
318-
319-
# See if can use any local secondary index range key
320-
# Chooses the first LSI found that can be utilized for the query
321-
source.local_secondary_indexes.each do |_, lsi|
322-
next unless query_keys.include?(lsi.range_key.to_s)
323-
324-
@range_key = lsi.range_key
325-
@index_name = lsi.name
326-
end
327-
328-
return true
329-
end
330-
331-
# See if can use any global secondary index
332-
# Chooses the first GSI found that can be utilized for the query
333-
# But only do so if projects ALL attributes otherwise we won't
334-
# get back full data
335-
result = false
336-
source.global_secondary_indexes.each do |_, gsi|
337-
next unless query.keys.map(&:to_s).include?(gsi.hash_key.to_s) && gsi.projected_attributes == :all
338-
next if @range_key.present? && !query_keys.include?(gsi.range_key.to_s)
339-
340-
@hash_key = gsi.hash_key
341-
@range_key = gsi.range_key
342-
@index_name = gsi.name
343-
result = true
344-
end
345-
346-
# Could not utilize any indices so we'll have to scan
347-
result
348-
end
349-
350315
# Start key needs to be set up based on the index utilized
351316
# If using a secondary index then we must include the index's composite key
352317
# as well as the tables composite key.
353318
def start_key
354319
return @start if @start.is_a?(Hash)
355320

356-
hash_key = @hash_key || source.hash_key
357-
range_key = @range_key || source.range_key
321+
hash_key = @keys_detector.hash_key || source.hash_key
322+
range_key = @keys_detector.range_key || source.range_key
358323

359324
key = {}
360325
key[hash_key] = type_cast_condition_parameter(hash_key, @start.send(hash_key))
@@ -373,7 +338,7 @@ def start_key
373338

374339
def query_opts
375340
opts = {}
376-
opts[:index_name] = @index_name if @index_name
341+
opts[:index_name] = @keys_detector.index_name if @keys_detector.index_name
377342
opts[:select] = 'ALL_ATTRIBUTES'
378343
opts[:record_limit] = @record_limit if @record_limit
379344
opts[:scan_limit] = @scan_limit if @scan_limit
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# frozen_string_literal: true
2+
3+
module Dynamoid #:nodoc:
4+
module Criteria
5+
class KeysDetector
6+
attr_reader :hash_key, :range_key, :index_name
7+
8+
def initialize(query, source)
9+
@query = query
10+
@source = source
11+
12+
detect_keys
13+
end
14+
15+
def key_present?
16+
@hash_key.present?
17+
end
18+
19+
private
20+
21+
def detect_keys
22+
query_keys = @query.keys.collect { |k| k.to_s.split('.').first }
23+
24+
# See if querying based on table hash key
25+
if @query.keys.map(&:to_s).include?(@source.hash_key.to_s)
26+
@hash_key = @source.hash_key
27+
28+
# Use table's default range key
29+
if query_keys.include?(@source.range_key.to_s)
30+
@range_key = @source.range_key
31+
return
32+
end
33+
34+
# See if can use any local secondary index range key
35+
# Chooses the first LSI found that can be utilized for the query
36+
@source.local_secondary_indexes.each do |_, lsi|
37+
next unless query_keys.include?(lsi.range_key.to_s)
38+
39+
@range_key = lsi.range_key
40+
@index_name = lsi.name
41+
end
42+
43+
return
44+
end
45+
46+
# See if can use any global secondary index
47+
# Chooses the first GSI found that can be utilized for the query
48+
# But only do so if projects ALL attributes otherwise we won't
49+
# get back full data
50+
@source.global_secondary_indexes.each do |_, gsi|
51+
next unless @query.keys.map(&:to_s).include?(gsi.hash_key.to_s) && gsi.projected_attributes == :all
52+
next if @range_key.present? && !query_keys.include?(gsi.range_key.to_s)
53+
54+
@hash_key = gsi.hash_key
55+
@range_key = gsi.range_key
56+
@index_name = gsi.name
57+
end
58+
end
59+
end
60+
end
61+
end

0 commit comments

Comments
 (0)