dynamoid 3.8.0 → 3.10.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|