sequel-association-filtering 0.0.4 → 0.0.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: db9148c1cf2c63a77fec6b64d6064a39cac6e811d0ec53feeb69b880e15a61e2
4
- data.tar.gz: 66a54816ac8284a3bb67e081d4637648a9c0c1979827ee2a32cf3865dde2483c
3
+ metadata.gz: eebfe24a7a4bc0831e9b5d983f7d49c46f1af66b531b137cbad11d65195c02dd
4
+ data.tar.gz: 812a28ac539eab752717576a44cd63f4e95d306c9ceeb3830f044227ec6c09db
5
5
  SHA512:
6
- metadata.gz: b06d337f0702b7381689eb9d7cf62114e73ce7e38ee5a7fcc8f7ed19e1416aa9623848f4179a3787b3db37e838666b4736ee448c30a4354275fd224e62dcc8e2
7
- data.tar.gz: f053fbae21edb9996e1a8a367c81feb5f23eae56d9e349dbdeecc6a2975f8b06a16e656b8cabc604fedaa86412bb7b78cad3c4a06050965eaea35e58ed623999
6
+ metadata.gz: d87c8e29c99c922be6411c7f74589b3685781502a38131f7f832465a334ab62cbee3125ba0fe4c844b98d496d1c0ef49cba6091c1f96c0268a0b46bc75f665d6
7
+ data.tar.gz: 00155ca49d9f3f01d8b4cf7385f81bb8e72e0acb8ec2db436ee3ae74468cb606cab343ac52e62120a65b4f5c129eaa3cb3a1ea6212fdd9f30520372cc3173087
@@ -19,27 +19,47 @@ module Sequel
19
19
  end
20
20
 
21
21
  module DatasetMethods
22
- def association_filter(association_name, invert: false)
22
+ COUNT_STAR = Sequel.virtual_row{count.function.*}
23
+
24
+ def association_filter(
25
+ association_name,
26
+ invert: false,
27
+ at_least: nil,
28
+ at_most: nil,
29
+ exactly: nil
30
+ )
31
+ case [at_least, at_most, exactly].compact.length
32
+ when 0
33
+ filtering_by_count = false
34
+ when 1
35
+ filtering_by_count = true
36
+ else
37
+ raise Error, "cannot pass more than one of :at_least, :at_most, and :exactly"
38
+ end
39
+
23
40
  reflection =
24
41
  model.association_reflections.fetch(association_name) do
25
42
  raise Error, "association #{association_name} not found on model #{model}"
26
43
  end
27
44
 
28
- if block_given?
29
- cond = yield(_association_filter_dataset(reflection)).exists
30
- invert ? exclude(cond) : where(cond)
31
- else
32
- cache_key =
33
- _association_filter_cache_key(
34
- reflection: reflection,
35
- extra: :"bare_#{invert}",
36
- )
45
+ ds = _association_filter_dataset(reflection, group_by_remote: filtering_by_count)
46
+ ds = yield(ds) if block_given?
37
47
 
38
- cached_dataset(cache_key) do
39
- cond = _association_filter_dataset(reflection).exists
40
- invert ? exclude(cond) : where(cond)
41
- end
48
+ if filtering_by_count
49
+ ds =
50
+ ds.having(
51
+ case
52
+ when at_least then COUNT_STAR >= at_least
53
+ when at_most then COUNT_STAR <= at_most
54
+ when exactly then COUNT_STAR =~ exactly
55
+ else raise Error, ""
56
+ end
57
+ )
42
58
  end
59
+
60
+ cond = ds.exists
61
+ cond = Sequel.~(cond) if invert
62
+ where(cond)
43
63
  end
44
64
 
45
65
  def association_exclude(association_name, &block)
@@ -48,8 +68,12 @@ module Sequel
48
68
 
49
69
  private
50
70
 
51
- def _association_filter_dataset(reflection)
52
- cache_key = _association_filter_cache_key(reflection: reflection)
71
+ def _association_filter_dataset(reflection, group_by_remote:)
72
+ cache_key =
73
+ _association_filter_cache_key(
74
+ reflection: reflection,
75
+ extra: (:group_by_remote if group_by_remote)
76
+ )
53
77
 
54
78
  ds = reflection.associated_dataset
55
79
 
@@ -71,12 +95,19 @@ module Sequel
71
95
  local_keys = Array(local_keys)
72
96
  remote_keys = Array(remote_keys)
73
97
 
74
- ds.where(
75
- remote_keys.
76
- zip(local_keys).
77
- map{|r,l| {r => l}}.
78
- inject{|a,b| Sequel.&(a, b)}
79
- ).select(1)
98
+ result =
99
+ ds.where(
100
+ remote_keys.
101
+ zip(local_keys).
102
+ map{|r,l| {r => l}}.
103
+ inject{|a,b| Sequel.&(a, b)}
104
+ ).select(1)
105
+
106
+ if group_by_remote
107
+ result.group_by(*remote_keys)
108
+ else
109
+ result
110
+ end
80
111
  end
81
112
  end
82
113
 
@@ -3,7 +3,7 @@
3
3
  module Sequel
4
4
  module Plugins
5
5
  module AssociationFiltering
6
- VERSION = '0.0.4'
6
+ VERSION = '0.0.5'
7
7
  end
8
8
  end
9
9
  end
@@ -29,5 +29,14 @@ class BasicSpec < AssociationFilteringSpecs
29
29
 
30
30
  assert_equal "association widgets not found on model BasicSpec::Artist", error.message
31
31
  end
32
+
33
+ it "with more than one of at_least, at_most, or exactly should throw an error" do
34
+ error =
35
+ assert_raises(Sequel::Plugins::AssociationFiltering::Error) do
36
+ Artist.association_filter(:widgets, at_most: 4, exactly: 5)
37
+ end
38
+
39
+ assert_equal "cannot pass more than one of :at_least, :at_most, and :exactly", error.message
40
+ end
32
41
  end
33
42
  end
@@ -58,6 +58,25 @@ class OneToManySpec < AssociationFilteringSpecs
58
58
  Album.where(artist_id: 5).delete
59
59
  assert_equal 9, ds.count
60
60
  end
61
+
62
+ it "should support at_least/exactly/at_most args" do
63
+ Album.create(artist_id: 1)
64
+
65
+ ds = Artist.association_filter(:albums, exactly: 11)
66
+ assert_equal 1, ds.count
67
+ assert_equal %(SELECT * FROM "artists" WHERE (EXISTS (SELECT 1 FROM "albums" WHERE ("albums"."artist_id" = "artists"."id") GROUP BY "albums"."artist_id" HAVING (count(*) = 11)))), ds.sql
68
+ assert_equal [1], ds.select_map(:id)
69
+
70
+ ds = Artist.association_filter(:albums, at_least: 2){|albums| albums.where{id > 90}}
71
+ assert_equal 1, ds.count
72
+ assert_equal %(SELECT * FROM "artists" WHERE (EXISTS (SELECT 1 FROM "albums" WHERE (("albums"."artist_id" = "artists"."id") AND ("id" > 90)) GROUP BY "albums"."artist_id" HAVING (count(*) >= 2)))), ds.sql
73
+ assert_equal [1], ds.select_map(:id)
74
+
75
+ ds = Artist.association_filter(:albums, at_most: 1){|albums| albums.where{id > 90}}
76
+ assert_equal 9, ds.count
77
+ assert_equal %(SELECT * FROM "artists" WHERE (EXISTS (SELECT 1 FROM "albums" WHERE (("albums"."artist_id" = "artists"."id") AND ("id" > 90)) GROUP BY "albums"."artist_id" HAVING (count(*) <= 1)))), ds.sql
78
+ assert_equal (2..10).to_a, ds.select_order_map(:id)
79
+ end
61
80
  end
62
81
  end
63
82
 
@@ -130,6 +149,25 @@ class OneToManySpec < AssociationFilteringSpecs
130
149
  Album.where(id_1: 5, id_2: 6).delete
131
150
  assert_equal 99, ds.count
132
151
  end
152
+
153
+ it "should support at_least/exactly/at_most args" do
154
+ Album.dataset.insert(id_1: 1, id_2: 1, id_3: 11)
155
+
156
+ ds = Artist.association_filter(:albums, exactly: 11)
157
+ assert_equal 1, ds.count
158
+ assert_equal %(SELECT * FROM "artists" WHERE (EXISTS (SELECT 1 FROM "albums" WHERE (("albums"."id_1" = "artists"."id_1") AND ("albums"."id_2" = "artists"."id_2")) GROUP BY "albums"."id_1", "albums"."id_2" HAVING (count(*) = 11)))), ds.sql
159
+ assert_equal [[1, 1]], ds.select_map([:id_1, :id_2])
160
+
161
+ ds = Artist.association_filter(:albums, at_least: 2){|albums| albums.where{serial_column > 900}}
162
+ assert_equal 1, ds.count
163
+ assert_equal %(SELECT * FROM "artists" WHERE (EXISTS (SELECT 1 FROM "albums" WHERE (("albums"."id_1" = "artists"."id_1") AND ("albums"."id_2" = "artists"."id_2") AND ("serial_column" > 900)) GROUP BY "albums"."id_1", "albums"."id_2" HAVING (count(*) >= 2)))), ds.sql
164
+ assert_equal [[1, 1]], ds.select_map([:id_1, :id_2])
165
+
166
+ ds = Artist.association_filter(:albums, at_most: 1){|albums| albums.where{serial_column > 900}}
167
+ assert_equal 99, ds.count
168
+ assert_equal %(SELECT * FROM "artists" WHERE (EXISTS (SELECT 1 FROM "albums" WHERE (("albums"."id_1" = "artists"."id_1") AND ("albums"."id_2" = "artists"."id_2") AND ("serial_column" > 900)) GROUP BY "albums"."id_1", "albums"."id_2" HAVING (count(*) <= 1)))), ds.sql
169
+ assert_equal (2..100).to_a, ds.select_order_map(:serial_column)
170
+ end
133
171
  end
134
172
 
135
173
  describe "association_exclude through a one_to_many association" do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel-association-filtering
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Hanks
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-05-11 00:00:00.000000000 Z
11
+ date: 2018-05-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler