ohm 1.0.0.rc3 → 1.0.0.rc4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/lib/ohm.rb +67 -23
  2. data/test/filtering.rb +69 -0
  3. metadata +31 -11
data/lib/ohm.rb CHANGED
@@ -423,10 +423,9 @@ module Ohm
423
423
  # set.find(:age => 30)
424
424
  #
425
425
  def find(dict)
426
- keys = model.filters(dict)
427
- keys.push(key)
426
+ filters = model.filters(dict).push(key)
428
427
 
429
- MultiSet.new(keys, namespace, model)
428
+ MultiSet.new(namespace, model).append(:sinterstore, filters)
430
429
  end
431
430
 
432
431
  # Reduce the set using any number of filters.
@@ -440,7 +439,7 @@ module Ohm
440
439
  # User.find(:name => "John").except(:country => "US")
441
440
  #
442
441
  def except(dict)
443
- MultiSet.new([key], namespace, model).except(dict)
442
+ MultiSet.new(namespace, model).append(:sinterstore, key).except(dict)
444
443
  end
445
444
 
446
445
  # Do a union to the existing set using any number of filters.
@@ -454,7 +453,7 @@ module Ohm
454
453
  # User.find(:name => "John").union(:name => "Jane")
455
454
  #
456
455
  def union(dict)
457
- MultiSet.new([key], namespace, model).union(dict)
456
+ MultiSet.new(namespace, model).append(:sinterstore, key).union(dict)
458
457
  end
459
458
 
460
459
  private
@@ -516,7 +515,6 @@ module Ohm
516
515
  end
517
516
  end
518
517
 
519
-
520
518
  # Anytime you filter a set with more than one requirement, you
521
519
  # internally use a `MultiSet`. `MutiSet` is a bit slower than just
522
520
  # a `Set` because it has to `SINTERSTORE` all the keys prior to
@@ -533,9 +531,15 @@ module Ohm
533
531
  # User.find(:name => "John", :age => 30).kind_of?(Ohm::MultiSet)
534
532
  # # => true
535
533
  #
536
- class MultiSet < Struct.new(:keys, :namespace, :model)
534
+ class MultiSet < Struct.new(:namespace, :model)
537
535
  include Collection
538
536
 
537
+ def append(operation, list)
538
+ filters.push([operation, list])
539
+
540
+ return self
541
+ end
542
+
539
543
  # Chain new fiters on an existing set.
540
544
  #
541
545
  # Example:
@@ -544,10 +548,9 @@ module Ohm
544
548
  # set.find(:status => 'pending')
545
549
  #
546
550
  def find(dict)
547
- keys = model.filters(dict)
548
- keys.push(*self.keys)
551
+ filters.push([:sinterstore, model.filters(dict)])
549
552
 
550
- MultiSet.new(keys, namespace, model)
553
+ return self
551
554
  end
552
555
 
553
556
  # Reduce the set using any number of filters.
@@ -561,7 +564,7 @@ module Ohm
561
564
  # User.find(:name => "John").except(:country => "US")
562
565
  #
563
566
  def except(dict)
564
- sdiff.push(*model.filters(dict)).uniq!
567
+ filters.push([:sdiffstore, model.filters(dict)])
565
568
 
566
569
  return self
567
570
  end
@@ -577,30 +580,71 @@ module Ohm
577
580
  # User.find(:name => "John").union(:name => "Jane")
578
581
  #
579
582
  def union(dict)
580
- sunion.push(*model.filters(dict)).uniq!
583
+ filters.push([:sunionstore, model.filters(dict)])
581
584
 
582
585
  return self
583
586
  end
584
587
 
585
588
  private
586
- def sunion
587
- @sunion ||= []
589
+ def filters
590
+ @filters ||= []
588
591
  end
589
592
 
590
- def sdiff
591
- @sdiff ||= []
593
+ def temp_keys
594
+ @temp_keys ||= []
592
595
  end
593
596
 
594
- def execute
597
+ def clean_temp_keys
598
+ model.db.del(*temp_keys)
599
+ temp_keys.clear
600
+ end
601
+
602
+ def generate_temp_key
595
603
  key = namespace[:temp][SecureRandom.hex(32)]
596
- key.sinterstore(*keys)
597
- key.sdiffstore(key, *sdiff) if sdiff.any?
598
- key.sunionstore(key, *sunion) if sunion.any?
604
+ temp_keys << key
605
+ key
606
+ end
607
+
608
+ def execute
609
+
610
+ # Hold the final result key for this MultiSet.
611
+ main = nil
612
+
613
+ filters.each do |operation, list|
614
+
615
+ # Operation can be sinterstore, sdiffstore, or sunionstore.
616
+ # each operation we do, i.e. `.union(...)`, will be considered
617
+ # one intersected set, hence we need to `sinterstore` all
618
+ # the filters in a temporary set.
619
+ temp = generate_temp_key
620
+ temp.sinterstore(*list)
621
+
622
+ # If this is the first set, we simply assign the generated
623
+ # set to main, which could possibly be the return value
624
+ # for simple filters like one `.find(...)`.
625
+ if main.nil?
626
+ main = temp
627
+ else
628
+
629
+ # Append the generated temporary set using the operation.
630
+ # i.e. if we have (mood=happy & book=1) and we have an
631
+ # `sunionstore`, we do (mood=happy & book=1) | (mood=sad & book=1)
632
+ main.send(operation, main, temp)
633
+ end
634
+ end
599
635
 
600
636
  begin
601
- yield key
637
+
638
+ # At this point, we have the final aggregated set, which we yield
639
+ # to the caller. the caller can do all the normal set operations,
640
+ # i.e. SCARD, SMEMBERS, etc.
641
+ yield main
642
+
602
643
  ensure
603
- key.del
644
+
645
+ # We have to make sure we clean up the temporary keys to avoid
646
+ # memory leaks and the unintended explosion of memory usage.
647
+ clean_temp_keys
604
648
  end
605
649
  end
606
650
  end
@@ -789,7 +833,7 @@ module Ohm
789
833
  if keys.size == 1
790
834
  Ohm::Set.new(keys.first, key, self)
791
835
  else
792
- Ohm::MultiSet.new(keys, key, self)
836
+ Ohm::MultiSet.new(key, self).append(:sinterstore, keys)
793
837
  end
794
838
  end
795
839
 
data/test/filtering.rb CHANGED
@@ -84,4 +84,73 @@ test "#union" do |john, jane|
84
84
  assert res.include?(john)
85
85
  assert res.include?(jane)
86
86
  assert res.include?(included)
87
+
88
+ res = User.find(:status => "active").union(:status => "inactive").find(:lname => "Doe")
89
+
90
+ assert res.any? { |e| e.status == "inactive" }
91
+ end
92
+
93
+ # book author thing via @myobie
94
+ scope do
95
+ class Book < Ohm::Model
96
+ collection :authors, :Author
97
+ end
98
+
99
+ class Author < Ohm::Model
100
+ reference :book, :Book
101
+
102
+ attribute :mood
103
+ index :mood
104
+ end
105
+
106
+ setup do
107
+ book1 = Book.create
108
+ book2 = Book.create
109
+
110
+ auth1 = Author.create(:book => book1, :mood => "happy")
111
+ auth2 = Author.create(:book => book1, :mood => "sad")
112
+ auth3 = Author.create(:book => book2, :mood => "sad")
113
+
114
+ [book1, book2]
115
+ end
116
+
117
+ test "straight up intersection + union" do |book1, book2|
118
+ result = book1.authors.find(:mood => "happy").
119
+ union(:book_id => book1.id, :mood => "sad")
120
+
121
+ assert_equal 2, result.size
122
+ end
123
+
124
+ test "appending an empty set via union" do |book1, book2|
125
+ res = Author.find(:book_id => book1.id, :mood => "happy").
126
+ union(:book_id => book2.id, :mood => "sad").
127
+ union(:book_id => book2.id, :mood => "happy")
128
+
129
+ assert_equal 2, res.size
130
+ end
131
+
132
+ test "revert by applying the original intersection" do |book1, book2|
133
+ res = Author.find(:book_id => book1.id, :mood => "happy").
134
+ union(:book_id => book2.id, :mood => "sad").
135
+ find(:book_id => book1.id, :mood => "happy")
136
+
137
+ assert_equal 1, res.size
138
+ end
139
+
140
+ test "remove original intersection by doing diff" do |book1, book2|
141
+ res = Author.find(:book_id => book1.id, :mood => "happy").
142
+ union(:book_id => book2.id, :mood => "sad").
143
+ except(:book_id => book1.id, :mood => "happy")
144
+
145
+ assert_equal 1, res.size
146
+ assert res.map(&:mood).include?("sad")
147
+ assert res.map(&:book_id).include?(book2.id)
148
+ end
149
+
150
+ test "@myobie usecase" do |book1, book2|
151
+ res = book1.authors.find(:mood => "happy").
152
+ union(:mood => "sad", :book_id => book1.id)
153
+
154
+ assert_equal 2, res.size
155
+ end
87
156
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ohm
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.rc3
4
+ version: 1.0.0.rc4
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -10,11 +10,11 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-04-20 00:00:00.000000000 Z
13
+ date: 2012-04-24 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: nest
17
- requirement: &70347702087860 !ruby/object:Gem::Requirement
17
+ requirement: !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ~>
@@ -22,10 +22,15 @@ dependencies:
22
22
  version: '1.0'
23
23
  type: :runtime
24
24
  prerelease: false
25
- version_requirements: *70347702087860
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ~>
29
+ - !ruby/object:Gem::Version
30
+ version: '1.0'
26
31
  - !ruby/object:Gem::Dependency
27
32
  name: scrivener
28
- requirement: &70347702087160 !ruby/object:Gem::Requirement
33
+ requirement: !ruby/object:Gem::Requirement
29
34
  none: false
30
35
  requirements:
31
36
  - - ~>
@@ -33,10 +38,15 @@ dependencies:
33
38
  version: 0.0.3
34
39
  type: :runtime
35
40
  prerelease: false
36
- version_requirements: *70347702087160
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ version: 0.0.3
37
47
  - !ruby/object:Gem::Dependency
38
48
  name: cutest
39
- requirement: &70347702086580 !ruby/object:Gem::Requirement
49
+ requirement: !ruby/object:Gem::Requirement
40
50
  none: false
41
51
  requirements:
42
52
  - - ~>
@@ -44,10 +54,15 @@ dependencies:
44
54
  version: '0.1'
45
55
  type: :development
46
56
  prerelease: false
47
- version_requirements: *70347702086580
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ~>
61
+ - !ruby/object:Gem::Version
62
+ version: '0.1'
48
63
  - !ruby/object:Gem::Dependency
49
64
  name: batch
50
- requirement: &70347702086060 !ruby/object:Gem::Requirement
65
+ requirement: !ruby/object:Gem::Requirement
51
66
  none: false
52
67
  requirements:
53
68
  - - ~>
@@ -55,7 +70,12 @@ dependencies:
55
70
  version: 0.0.1
56
71
  type: :development
57
72
  prerelease: false
58
- version_requirements: *70347702086060
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ~>
77
+ - !ruby/object:Gem::Version
78
+ version: 0.0.1
59
79
  description: Ohm is a library that allows to store an object in Redis, a persistent
60
80
  key-value database. It includes an extensible list of validations and has very good
61
81
  performance.
@@ -117,7 +137,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
117
137
  version: 1.3.1
118
138
  requirements: []
119
139
  rubyforge_project: ohm
120
- rubygems_version: 1.8.11
140
+ rubygems_version: 1.8.23
121
141
  signing_key:
122
142
  specification_version: 3
123
143
  summary: Object-hash mapping library for Redis.