dynamoid 3.8.0 → 3.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +54 -3
- data/README.md +111 -60
- data/SECURITY.md +17 -0
- data/dynamoid.gemspec +65 -0
- data/lib/dynamoid/adapter.rb +20 -13
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/create_table.rb +2 -2
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/execute_statement.rb +62 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/filter_expression_convertor.rb +78 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +28 -2
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/middleware/limit.rb +3 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/projection_expression_convertor.rb +38 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/query.rb +46 -61
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/scan.rb +33 -27
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +116 -70
- data/lib/dynamoid/associations/belongs_to.rb +6 -6
- data/lib/dynamoid/associations.rb +1 -1
- data/lib/dynamoid/components.rb +2 -3
- data/lib/dynamoid/config/options.rb +12 -12
- data/lib/dynamoid/config.rb +1 -0
- data/lib/dynamoid/criteria/chain.rb +101 -138
- data/lib/dynamoid/criteria/key_fields_detector.rb +6 -7
- data/lib/dynamoid/criteria/nonexistent_fields_detector.rb +2 -2
- data/lib/dynamoid/criteria/where_conditions.rb +29 -0
- data/lib/dynamoid/dirty.rb +57 -57
- data/lib/dynamoid/document.rb +39 -3
- data/lib/dynamoid/dumping.rb +2 -2
- data/lib/dynamoid/errors.rb +2 -0
- data/lib/dynamoid/fields/declare.rb +6 -6
- data/lib/dynamoid/fields.rb +9 -27
- data/lib/dynamoid/finders.rb +26 -30
- data/lib/dynamoid/indexes.rb +7 -10
- data/lib/dynamoid/loadable.rb +2 -2
- data/lib/dynamoid/log/formatter.rb +19 -4
- data/lib/dynamoid/persistence/import.rb +4 -1
- data/lib/dynamoid/persistence/inc.rb +66 -0
- data/lib/dynamoid/persistence/save.rb +55 -12
- data/lib/dynamoid/persistence/update_fields.rb +2 -2
- data/lib/dynamoid/persistence/update_validations.rb +2 -2
- data/lib/dynamoid/persistence.rb +128 -48
- data/lib/dynamoid/type_casting.rb +15 -14
- data/lib/dynamoid/undumping.rb +1 -1
- data/lib/dynamoid/version.rb +1 -1
- metadata +27 -49
- data/lib/dynamoid/criteria/ignored_conditions_detector.rb +0 -41
- data/lib/dynamoid/criteria/overwritten_conditions_detector.rb +0 -40
@@ -1,29 +1,35 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'key_fields_detector'
|
4
|
-
require_relative 'ignored_conditions_detector'
|
5
|
-
require_relative 'overwritten_conditions_detector'
|
6
4
|
require_relative 'nonexistent_fields_detector'
|
5
|
+
require_relative 'where_conditions'
|
7
6
|
|
8
7
|
module Dynamoid
|
9
8
|
module Criteria
|
10
9
|
# The criteria chain is equivalent to an ActiveRecord relation (and realistically I should change the name from
|
11
10
|
# chain to relation). It is a chainable object that builds up a query and eventually executes it by a Query or Scan.
|
12
11
|
class Chain
|
13
|
-
attr_reader :
|
12
|
+
attr_reader :source, :consistent_read, :key_fields_detector
|
14
13
|
|
15
14
|
include Enumerable
|
15
|
+
|
16
|
+
ALLOWED_FIELD_OPERATORS = Set.new(
|
17
|
+
%w[
|
18
|
+
eq ne gt lt gte lte between begins_with in contains not_contains null not_null
|
19
|
+
]
|
20
|
+
).freeze
|
21
|
+
|
16
22
|
# Create a new criteria chain.
|
17
23
|
#
|
18
24
|
# @param [Class] source the class upon which the ultimate query will be performed.
|
19
25
|
def initialize(source)
|
20
|
-
@
|
26
|
+
@where_conditions = WhereConditions.new
|
21
27
|
@source = source
|
22
28
|
@consistent_read = false
|
23
29
|
@scan_index_forward = true
|
24
30
|
|
25
|
-
# we should re-initialize keys detector every time we change
|
26
|
-
@key_fields_detector = KeyFieldsDetector.new(@
|
31
|
+
# we should re-initialize keys detector every time we change @where_conditions
|
32
|
+
@key_fields_detector = KeyFieldsDetector.new(@where_conditions, @source)
|
27
33
|
end
|
28
34
|
|
29
35
|
# Returns a chain which is a result of filtering current chain with the specified conditions.
|
@@ -92,25 +98,15 @@ module Dynamoid
|
|
92
98
|
# @return [Dynamoid::Criteria::Chain]
|
93
99
|
# @since 0.2.0
|
94
100
|
def where(args)
|
95
|
-
detector = IgnoredConditionsDetector.new(args)
|
96
|
-
if detector.found?
|
97
|
-
Dynamoid.logger.warn(detector.warning_message)
|
98
|
-
end
|
99
|
-
|
100
|
-
detector = OverwrittenConditionsDetector.new(@query, args)
|
101
|
-
if detector.found?
|
102
|
-
Dynamoid.logger.warn(detector.warning_message)
|
103
|
-
end
|
104
|
-
|
105
101
|
detector = NonexistentFieldsDetector.new(args, @source)
|
106
102
|
if detector.found?
|
107
103
|
Dynamoid.logger.warn(detector.warning_message)
|
108
104
|
end
|
109
105
|
|
110
|
-
|
106
|
+
@where_conditions.update(args.symbolize_keys)
|
111
107
|
|
112
|
-
# we should re-initialize keys detector every time we change
|
113
|
-
@key_fields_detector = KeyFieldsDetector.new(@
|
108
|
+
# we should re-initialize keys detector every time we change @where_conditions
|
109
|
+
@key_fields_detector = KeyFieldsDetector.new(@where_conditions, @source, forced_index_name: @forced_index_name)
|
114
110
|
|
115
111
|
self
|
116
112
|
end
|
@@ -187,10 +183,10 @@ module Dynamoid
|
|
187
183
|
def first(*args)
|
188
184
|
n = args.first || 1
|
189
185
|
|
190
|
-
return
|
186
|
+
return dup.scan_limit(n).to_a.first(*args) if @where_conditions.empty?
|
191
187
|
return super if @key_fields_detector.non_key_present?
|
192
188
|
|
193
|
-
|
189
|
+
dup.record_limit(n).to_a.first(*args)
|
194
190
|
end
|
195
191
|
|
196
192
|
# Returns the last item matching the criteria.
|
@@ -230,12 +226,12 @@ module Dynamoid
|
|
230
226
|
ranges = []
|
231
227
|
|
232
228
|
if @key_fields_detector.key_present?
|
233
|
-
Dynamoid.adapter.query(source.table_name,
|
229
|
+
Dynamoid.adapter.query(source.table_name, query_key_conditions, query_non_key_conditions, query_options).flat_map { |i| i }.collect do |hash|
|
234
230
|
ids << hash[source.hash_key.to_sym]
|
235
231
|
ranges << hash[source.range_key.to_sym] if source.range_key
|
236
232
|
end
|
237
233
|
else
|
238
|
-
Dynamoid.adapter.scan(source.table_name,
|
234
|
+
Dynamoid.adapter.scan(source.table_name, scan_conditions, scan_options).flat_map { |i| i }.collect do |hash|
|
239
235
|
ids << hash[source.hash_key.to_sym]
|
240
236
|
ranges << hash[source.range_key.to_sym] if source.range_key
|
241
237
|
end
|
@@ -384,7 +380,7 @@ module Dynamoid
|
|
384
380
|
raise Dynamoid::Errors::InvalidIndex, "Unknown index #{index_name}" unless @source.find_index_by_name(index_name)
|
385
381
|
|
386
382
|
@forced_index_name = index_name
|
387
|
-
@key_fields_detector = KeyFieldsDetector.new(@
|
383
|
+
@key_fields_detector = KeyFieldsDetector.new(@where_conditions, @source, forced_index_name: index_name)
|
388
384
|
self
|
389
385
|
end
|
390
386
|
|
@@ -451,9 +447,9 @@ module Dynamoid
|
|
451
447
|
# It takes one or more field names and returns a collection of models with only
|
452
448
|
# these fields set.
|
453
449
|
#
|
454
|
-
# Post.where('views_count.gt' => 1000).
|
455
|
-
# Post.where('views_count.gt' => 1000).
|
456
|
-
# Post.
|
450
|
+
# Post.where('views_count.gt' => 1000).project(:title)
|
451
|
+
# Post.where('views_count.gt' => 1000).project(:title, :created_at)
|
452
|
+
# Post.project(:id)
|
457
453
|
#
|
458
454
|
# It can be used to avoid loading large field values and to decrease a
|
459
455
|
# memory footprint.
|
@@ -487,7 +483,9 @@ module Dynamoid
|
|
487
483
|
def pluck(*args)
|
488
484
|
fields = args.map(&:to_sym)
|
489
485
|
|
490
|
-
|
486
|
+
# `project` has a side effect - it sets `@project` instance variable.
|
487
|
+
# So use a duplicate to not pollute original chain.
|
488
|
+
scope = dup
|
491
489
|
scope.project(*fields)
|
492
490
|
|
493
491
|
if fields.many?
|
@@ -525,6 +523,7 @@ module Dynamoid
|
|
525
523
|
def pages
|
526
524
|
raw_pages.lazy.map do |items, options|
|
527
525
|
models = items.map { |i| source.from_database(i) }
|
526
|
+
models.each { |m| m.run_callbacks :find }
|
528
527
|
[models, options]
|
529
528
|
end.each
|
530
529
|
end
|
@@ -534,7 +533,7 @@ module Dynamoid
|
|
534
533
|
if @key_fields_detector.key_present?
|
535
534
|
raw_pages_via_query
|
536
535
|
else
|
537
|
-
issue_scan_warning if Dynamoid::Config.warn_on_scan &&
|
536
|
+
issue_scan_warning if Dynamoid::Config.warn_on_scan && !@where_conditions.empty?
|
538
537
|
raw_pages_via_scan
|
539
538
|
end
|
540
539
|
end
|
@@ -546,7 +545,7 @@ module Dynamoid
|
|
546
545
|
# @since 3.1.0
|
547
546
|
def raw_pages_via_query
|
548
547
|
Enumerator.new do |y|
|
549
|
-
Dynamoid.adapter.query(source.table_name,
|
548
|
+
Dynamoid.adapter.query(source.table_name, query_key_conditions, query_non_key_conditions, query_options).each do |items, metadata|
|
550
549
|
options = metadata.slice(:last_evaluated_key)
|
551
550
|
|
552
551
|
y.yield items, options
|
@@ -561,7 +560,7 @@ module Dynamoid
|
|
561
560
|
# @since 3.1.0
|
562
561
|
def raw_pages_via_scan
|
563
562
|
Enumerator.new do |y|
|
564
|
-
Dynamoid.adapter.scan(source.table_name,
|
563
|
+
Dynamoid.adapter.scan(source.table_name, scan_conditions, scan_options).each do |items, metadata|
|
565
564
|
options = metadata.slice(:last_evaluated_key)
|
566
565
|
|
567
566
|
y.yield items, options
|
@@ -574,121 +573,88 @@ module Dynamoid
|
|
574
573
|
Dynamoid.logger.warn "You can index this query by adding index declaration to #{source.to_s.underscore}.rb:"
|
575
574
|
Dynamoid.logger.warn "* global_secondary_index hash_key: 'some-name', range_key: 'some-another-name'"
|
576
575
|
Dynamoid.logger.warn "* local_secondary_index range_key: 'some-name'"
|
577
|
-
Dynamoid.logger.warn "Not indexed attributes: #{
|
576
|
+
Dynamoid.logger.warn "Not indexed attributes: #{@where_conditions.keys.sort.collect { |name| ":#{name}" }.join(', ')}"
|
578
577
|
end
|
579
578
|
|
580
579
|
def count_via_query
|
581
|
-
Dynamoid.adapter.query_count(source.table_name,
|
580
|
+
Dynamoid.adapter.query_count(source.table_name, query_key_conditions, query_non_key_conditions, query_options)
|
582
581
|
end
|
583
582
|
|
584
583
|
def count_via_scan
|
585
|
-
Dynamoid.adapter.scan_count(source.table_name,
|
586
|
-
end
|
587
|
-
|
588
|
-
def range_hash(key)
|
589
|
-
name, operation = key.to_s.split('.')
|
590
|
-
val = type_cast_condition_parameter(name, query[key])
|
591
|
-
|
592
|
-
case operation
|
593
|
-
when 'gt'
|
594
|
-
{ range_greater_than: val }
|
595
|
-
when 'lt'
|
596
|
-
{ range_less_than: val }
|
597
|
-
when 'gte'
|
598
|
-
{ range_gte: val }
|
599
|
-
when 'lte'
|
600
|
-
{ range_lte: val }
|
601
|
-
when 'between'
|
602
|
-
{ range_between: val }
|
603
|
-
when 'begins_with'
|
604
|
-
{ range_begins_with: val }
|
605
|
-
end
|
584
|
+
Dynamoid.adapter.scan_count(source.table_name, scan_conditions, scan_options)
|
606
585
|
end
|
607
586
|
|
608
|
-
def
|
609
|
-
name,
|
610
|
-
|
611
|
-
|
612
|
-
hash = case operation
|
613
|
-
when 'ne'
|
614
|
-
{ ne: val }
|
615
|
-
when 'gt'
|
616
|
-
{ gt: val }
|
617
|
-
when 'lt'
|
618
|
-
{ lt: val }
|
619
|
-
when 'gte'
|
620
|
-
{ gte: val }
|
621
|
-
when 'lte'
|
622
|
-
{ lte: val }
|
623
|
-
when 'between'
|
624
|
-
{ between: val }
|
625
|
-
when 'begins_with'
|
626
|
-
{ begins_with: val }
|
627
|
-
when 'in'
|
628
|
-
{ in: val }
|
629
|
-
when 'contains'
|
630
|
-
{ contains: val }
|
631
|
-
when 'not_contains'
|
632
|
-
{ not_contains: val }
|
633
|
-
# NULL/NOT_NULL operators don't have parameters
|
634
|
-
# So { null: true } means NULL check and { null: false } means NOT_NULL one
|
635
|
-
# The same logic is used for { not_null: BOOL }
|
636
|
-
when 'null'
|
637
|
-
val ? { null: nil } : { not_null: nil }
|
638
|
-
when 'not_null'
|
639
|
-
val ? { not_null: nil } : { null: nil }
|
640
|
-
end
|
641
|
-
|
642
|
-
{ name.to_sym => hash }
|
643
|
-
end
|
644
|
-
|
645
|
-
def consistent_opts
|
646
|
-
{ consistent_read: consistent_read }
|
647
|
-
end
|
648
|
-
|
649
|
-
def range_query
|
650
|
-
opts = {}
|
651
|
-
query = self.query
|
587
|
+
def field_condition(key, value_before_type_casting)
|
588
|
+
name, operator = key.to_s.split('.')
|
589
|
+
value = type_cast_condition_parameter(name, value_before_type_casting)
|
590
|
+
operator ||= 'eq'
|
652
591
|
|
653
|
-
|
654
|
-
|
655
|
-
@key_fields_detector.hash_key.to_sym != @source.inheritance_field.to_sym
|
656
|
-
query.update(sti_condition)
|
592
|
+
unless operator.in? ALLOWED_FIELD_OPERATORS
|
593
|
+
raise Dynamoid::Errors::Error, "Unsupported operator #{operator} in #{key}"
|
657
594
|
end
|
658
595
|
|
596
|
+
condition =
|
597
|
+
case operator
|
598
|
+
# NULL/NOT_NULL operators don't have parameters
|
599
|
+
# So { null: true } means NULL check and { null: false } means NOT_NULL one
|
600
|
+
# The same logic is used for { not_null: BOOL }
|
601
|
+
when 'null'
|
602
|
+
value ? [:null, nil] : [:not_null, nil]
|
603
|
+
when 'not_null'
|
604
|
+
value ? [:not_null, nil] : [:null, nil]
|
605
|
+
else
|
606
|
+
[operator.to_sym, value]
|
607
|
+
end
|
608
|
+
|
609
|
+
[name.to_sym, condition]
|
610
|
+
end
|
611
|
+
|
612
|
+
def query_key_conditions
|
613
|
+
opts = {}
|
614
|
+
|
659
615
|
# Add hash key
|
660
|
-
|
661
|
-
|
616
|
+
# TODO: always have hash key in @where_conditions?
|
617
|
+
_, condition = field_condition(@key_fields_detector.hash_key, @where_conditions[@key_fields_detector.hash_key])
|
618
|
+
opts[@key_fields_detector.hash_key] = [condition]
|
662
619
|
|
663
620
|
# Add range key
|
664
621
|
if @key_fields_detector.range_key
|
665
|
-
|
666
|
-
|
622
|
+
if @where_conditions[@key_fields_detector.range_key].present?
|
623
|
+
_, condition = field_condition(@key_fields_detector.range_key, @where_conditions[@key_fields_detector.range_key])
|
624
|
+
opts[@key_fields_detector.range_key] = [condition]
|
625
|
+
end
|
667
626
|
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
opts.update(field_hash(key))
|
673
|
-
else
|
674
|
-
value = type_cast_condition_parameter(key, query[key])
|
675
|
-
opts[key] = { eq: value }
|
627
|
+
@where_conditions.keys.select { |k| k.to_s =~ /^#{@key_fields_detector.range_key}\./ }.each do |key|
|
628
|
+
name, condition = field_condition(key, @where_conditions[key])
|
629
|
+
opts[name] ||= []
|
630
|
+
opts[name] << condition
|
676
631
|
end
|
677
632
|
end
|
678
633
|
|
679
|
-
opts
|
634
|
+
opts
|
680
635
|
end
|
681
636
|
|
682
|
-
def
|
683
|
-
opts
|
684
|
-
|
685
|
-
|
686
|
-
|
637
|
+
def query_non_key_conditions
|
638
|
+
opts = {}
|
639
|
+
|
640
|
+
# Honor STI and :type field if it presents
|
641
|
+
if @source.attributes.key?(@source.inheritance_field) &&
|
642
|
+
@key_fields_detector.hash_key.to_sym != @source.inheritance_field.to_sym
|
643
|
+
@where_conditions.update(sti_condition)
|
687
644
|
end
|
688
645
|
|
689
|
-
|
690
|
-
|
646
|
+
# TODO: Separate key conditions and non-key conditions properly:
|
647
|
+
# only =, >, >=, <, <=, between and begins_with
|
648
|
+
# could be used for sort key in KeyConditionExpression
|
649
|
+
keys = (@where_conditions.keys.map(&:to_sym) - [@key_fields_detector.hash_key.to_sym, @key_fields_detector.range_key.try(:to_sym)])
|
650
|
+
.reject { |k, _| k.to_s =~ /^#{@key_fields_detector.range_key}\./ }
|
651
|
+
keys.each do |key|
|
652
|
+
name, condition = field_condition(key, @where_conditions[key])
|
653
|
+
opts[name] ||= []
|
654
|
+
opts[name] << condition
|
691
655
|
end
|
656
|
+
|
657
|
+
opts
|
692
658
|
end
|
693
659
|
|
694
660
|
# TODO: casting should be operator aware
|
@@ -736,7 +702,7 @@ module Dynamoid
|
|
736
702
|
key
|
737
703
|
end
|
738
704
|
|
739
|
-
def
|
705
|
+
def query_options
|
740
706
|
opts = {}
|
741
707
|
# Don't specify select = ALL_ATTRIBUTES option explicitly because it's
|
742
708
|
# already a default value of Select statement. Explicite Select value
|
@@ -748,30 +714,26 @@ module Dynamoid
|
|
748
714
|
opts[:exclusive_start_key] = start_key if @start
|
749
715
|
opts[:scan_index_forward] = @scan_index_forward
|
750
716
|
opts[:project] = @project
|
717
|
+
opts[:consistent_read] = true if @consistent_read
|
751
718
|
opts
|
752
719
|
end
|
753
720
|
|
754
|
-
def
|
755
|
-
query = self.query
|
756
|
-
|
721
|
+
def scan_conditions
|
757
722
|
# Honor STI and :type field if it presents
|
758
723
|
if sti_condition
|
759
|
-
|
724
|
+
@where_conditions.update(sti_condition)
|
760
725
|
end
|
761
726
|
|
762
727
|
{}.tap do |opts|
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
value = type_cast_condition_parameter(key, query[key])
|
768
|
-
opts[key] = { eq: value }
|
769
|
-
end
|
728
|
+
@where_conditions.keys.map(&:to_sym).each do |key|
|
729
|
+
name, condition = field_condition(key, @where_conditions[key])
|
730
|
+
opts[name] ||= []
|
731
|
+
opts[name] << condition
|
770
732
|
end
|
771
733
|
end
|
772
734
|
end
|
773
735
|
|
774
|
-
def
|
736
|
+
def scan_options
|
775
737
|
opts = {}
|
776
738
|
opts[:index_name] = @key_fields_detector.index_name if @key_fields_detector.index_name
|
777
739
|
opts[:record_limit] = @record_limit if @record_limit
|
@@ -783,13 +745,14 @@ module Dynamoid
|
|
783
745
|
opts
|
784
746
|
end
|
785
747
|
|
748
|
+
# TODO: return Array, not String
|
786
749
|
def sti_condition
|
787
750
|
condition = {}
|
788
751
|
type = @source.inheritance_field
|
789
752
|
|
790
|
-
if @source.attributes.key?(type)
|
791
|
-
|
792
|
-
condition[:"#{type}.in"] =
|
753
|
+
if @source.attributes.key?(type) && !@source.abstract_class?
|
754
|
+
sti_names = @source.deep_subclasses.map(&:sti_name) << @source.sti_name
|
755
|
+
condition[:"#{type}.in"] = sti_names
|
793
756
|
end
|
794
757
|
|
795
758
|
condition
|
@@ -5,10 +5,10 @@ module Dynamoid
|
|
5
5
|
# @private
|
6
6
|
class KeyFieldsDetector
|
7
7
|
class Query
|
8
|
-
def initialize(
|
9
|
-
@
|
10
|
-
@fields_with_operator =
|
11
|
-
@fields =
|
8
|
+
def initialize(where_conditions)
|
9
|
+
@where_conditions = where_conditions
|
10
|
+
@fields_with_operator = where_conditions.keys.map(&:to_s)
|
11
|
+
@fields = where_conditions.keys.map(&:to_s).map { |s| s.split('.').first }
|
12
12
|
end
|
13
13
|
|
14
14
|
def contain_only?(field_names)
|
@@ -24,10 +24,9 @@ module Dynamoid
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
def initialize(
|
28
|
-
@query = query
|
27
|
+
def initialize(where_conditions, source, forced_index_name: nil)
|
29
28
|
@source = source
|
30
|
-
@query = Query.new(
|
29
|
+
@query = Query.new(where_conditions)
|
31
30
|
@forced_index_name = forced_index_name
|
32
31
|
@result = find_keys_in_query
|
33
32
|
end
|
@@ -20,8 +20,8 @@ module Dynamoid
|
|
20
20
|
fields_list = @nonexistent_fields.map { |s| "`#{s}`" }.join(', ')
|
21
21
|
count = @nonexistent_fields.size
|
22
22
|
|
23
|
-
'where conditions contain nonexistent' \
|
24
|
-
"
|
23
|
+
'where conditions contain nonexistent ' \
|
24
|
+
"field #{'name'.pluralize(count)} #{fields_list}"
|
25
25
|
end
|
26
26
|
|
27
27
|
private
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dynamoid
|
4
|
+
module Criteria
|
5
|
+
# @private
|
6
|
+
class WhereConditions
|
7
|
+
def initialize
|
8
|
+
@conditions = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def update(hash)
|
12
|
+
@conditions << hash.symbolize_keys
|
13
|
+
end
|
14
|
+
|
15
|
+
def keys
|
16
|
+
@conditions.flat_map(&:keys)
|
17
|
+
end
|
18
|
+
|
19
|
+
def empty?
|
20
|
+
@conditions.empty?
|
21
|
+
end
|
22
|
+
|
23
|
+
def [](key)
|
24
|
+
hash = @conditions.find { |h| h.key?(key) }
|
25
|
+
hash[key] if hash
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|