sequel-association-filtering 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 650d4f992d2994676afd8b8cab81c98522dcd3a7f3c7700702fe2eeab78f7f64
4
+ data.tar.gz: dc67094e11cdefd1b267524de4cecc4fe7729025eb9bdcadd9cfd8aaf72e4794
5
+ SHA512:
6
+ metadata.gz: 8c07e15a8e8c2f0160cc98c3ccbc0292e6257d1d70ebeb914209d7d4288179f5030689ec6159d7f26ba52f1e449b38c3d3f2c03824d48e07030d2c6ffd0bf6c9
7
+ data.tar.gz: 72490ad79e2f0b1a8cadb2150dccc0a9544f3c0ab018298407f4bce4deb549cd62935a505279c139b955c74b84fabb460e641fc2ada8d9e7025e95dff2e5fe3c
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --order random
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem 'minitest', '5.5.1'
7
+ gem 'minitest-rg', '5.1.0'
8
+
9
+ gem 'pry'
10
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Chris Hanks
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # Sequel::Plugins::AssociationFiltering
2
+
3
+ This gem provides support for simple filtering through associations to Sequel.
4
+
5
+ ### Caveats
6
+
7
+ The gem is tested on PostgreSQL. It may or may not work for other databases.
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ Dir["./tasks/*.rb"].sort.each &method(:require)
@@ -0,0 +1 @@
1
+ require 'sequel/plugins/association_filtering'
@@ -0,0 +1,62 @@
1
+ require 'sequel'
2
+ require 'sequel/plugins/association_filtering/version'
3
+
4
+ module Sequel
5
+ module Plugins
6
+ module AssociationFiltering
7
+ class Error < StandardError; end
8
+
9
+ module ClassMethods
10
+ Plugins.def_dataset_methods(self, :association_filter)
11
+ end
12
+
13
+ module DatasetMethods
14
+ def association_filter(association_name)
15
+ reflection =
16
+ model.association_reflections.fetch(association_name) do
17
+ raise Error, "association #{association_name} not found on model #{model}"
18
+ end
19
+
20
+ if block_given?
21
+ ds = yield(_association_filter_dataset(reflection))
22
+ where(ds.exists)
23
+ else
24
+ cached_dataset(_association_filter_cache_key(reflection, suffix: :bare)) do
25
+ where(_association_filter_dataset(reflection).exists)
26
+ end
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def _association_filter_dataset(reflection)
33
+ cache_key = _association_filter_cache_key(reflection)
34
+
35
+ ds = reflection.associated_dataset
36
+
37
+ ds.send(:cached_dataset, cache_key) do
38
+ case t = reflection[:type]
39
+ when :one_to_many
40
+ local_keys = reflection.qualified_primary_key
41
+ remote_keys = reflection.predicate_key
42
+ when :many_to_one
43
+ local_keys = reflection[:qualified_key]
44
+ remote_keys = reflection.qualified_primary_key
45
+ when :many_to_many
46
+ local_keys = reflection.qualify_cur(reflection[:left_primary_key])
47
+ remote_keys = reflection.qualified_left_key
48
+ else
49
+ raise Error, "Unsupported reflection type: #{t}"
50
+ end
51
+
52
+ ds.where(remote_keys => local_keys).select(1)
53
+ end
54
+ end
55
+
56
+ def _association_filter_cache_key(reflection, suffix: nil)
57
+ :"_association_filter_#{reflection[:model]}_#{reflection[:name]}_#{suffix}"
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,7 @@
1
+ module Sequel
2
+ module Plugins
3
+ module AssociationFiltering
4
+ VERSION = '0.0.1'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'sequel/plugins/association_filtering/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'sequel-association-filtering'
8
+ spec.version = Sequel::Plugins::AssociationFiltering::VERSION
9
+ spec.authors = ["Chris Hanks"]
10
+ spec.email = ['christopher.m.hanks@gmail.com']
11
+ spec.summary = %q{Easy filtering through Sequel associations.}
12
+ spec.description = %q{Easily filter records by the presence/absence of associated records.}
13
+ spec.homepage = 'https://github.com/chanks/sequel-association-filtering'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_development_dependency 'bundler', '~> 1.6'
22
+ spec.add_development_dependency 'rake'
23
+ spec.add_development_dependency 'pg'
24
+
25
+ spec.add_dependency 'sequel', '~> 5.0'
26
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ class BasicSpec < AssociationFilteringSpecs
4
+ describe "association_filter" do
5
+ it "with an unknown association should throw an error" do
6
+ error =
7
+ assert_raises(Sequel::Plugins::AssociationFiltering::Error) do
8
+ Album.association_filter(:widgets)
9
+ end
10
+
11
+ assert_equal "association widgets not found on model Album", error.message
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ class AssociationFilteringSpec < AssociationFilteringSpecs
4
+ describe "association_filter through a many_to_many_association" do
5
+ it "should support an empty filter that checks for existence" do
6
+ expected_count = DB[:album_genres].distinct(:album_id).count
7
+
8
+ ds = Album.association_filter(:genres)
9
+ assert_equal %(SELECT * FROM "albums" WHERE (EXISTS (SELECT 1 FROM "genres" INNER JOIN "album_genres" ON ("album_genres"."genre_id" = "genres"."id") WHERE ("album_genres"."album_id" = "albums"."id")))), ds.sql
10
+ assert_equal expected_count, ds.count
11
+
12
+ album_id_to_delete = DB[:album_genres].order_by{random.function}.get(:album_id)
13
+
14
+ DB[:album_genres].where(album_id: album_id_to_delete).delete
15
+ assert_equal expected_count - 1, ds.count
16
+ end
17
+
18
+ it "should support a simple filter" do
19
+ expected_count = DB[:album_genres].where(genre_id: 5).distinct(:album_id).count
20
+
21
+ ds = Album.association_filter(:genres){|t| t.where(Sequel[:genres][:id] =~ 5)}
22
+ assert_equal expected_count, ds.count
23
+ assert_equal %(SELECT * FROM \"albums\" WHERE (EXISTS (SELECT 1 FROM "genres" INNER JOIN "album_genres" ON ("album_genres"."genre_id" = "genres"."id") WHERE (("album_genres"."album_id" = "albums"."id") AND ("genres"."id" = 5))))), ds.sql
24
+
25
+ record = ds.first!
26
+ assert_includes record.genres.map(&:id), 5
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ class AssociationFilteringSpec < AssociationFilteringSpecs
4
+ describe "association_filter through a many_to_one association" do
5
+ it "should support a simple filter" do
6
+ ds = Album.association_filter(:artist){|a| a.where{mod(id, 5) =~ 0}}
7
+ assert_equal 20, ds.count
8
+ assert_equal %(SELECT * FROM "albums" WHERE (EXISTS (SELECT 1 FROM "artists" WHERE (("artists"."id" = "albums"."artist_id") AND (mod("id", 5) = 0)) LIMIT 1))), ds.sql
9
+
10
+ record = ds.order_by{random.function}.first
11
+ assert_includes [5, 10], record.artist_id
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ class AssociationFilteringSpec < AssociationFilteringSpecs
4
+ describe "association_filter through a one_to_many association" do
5
+ it "should support a simple filter" do
6
+ ds = Album.association_filter(:tracks){|t| t.where(id: 40)}
7
+ assert_equal 1, ds.count
8
+ assert_equal %(SELECT * FROM "albums" WHERE (EXISTS (SELECT 1 FROM "tracks" WHERE (("tracks"."album_id" = "albums"."id") AND ("id" = 40))))), ds.sql
9
+
10
+ record = ds.first!
11
+ assert_includes record.tracks.map(&:id), 40
12
+ end
13
+
14
+ it "should support an empty filter that checks for existence" do
15
+ ds = Album.association_filter(:tracks)
16
+ assert_equal 100, ds.count
17
+ assert_equal %(SELECT * FROM "albums" WHERE (EXISTS (SELECT 1 FROM "tracks" WHERE ("tracks"."album_id" = "albums"."id")))), ds.sql
18
+
19
+ Track.where(album_id: 50).delete
20
+ assert_equal 99, ds.count
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,74 @@
1
+ require 'sequel'
2
+
3
+ require './lib/sequel-association-filtering'
4
+
5
+ Sequel::Model.plugin :association_filtering
6
+
7
+ DB = Sequel.connect "postgres:///sequel-association-filtering-test"
8
+
9
+ DB.drop_table? :album_genres, :genres, :tracks, :albums, :artists
10
+
11
+ DB.create_table :artists do
12
+ primary_key :id
13
+ end
14
+
15
+ DB.create_table :albums do
16
+ primary_key :id
17
+ foreign_key :artist_id, :artists
18
+ end
19
+
20
+ DB.create_table :tracks do
21
+ primary_key :id
22
+ foreign_key :album_id, :albums
23
+ end
24
+
25
+ DB.create_table :genres do
26
+ primary_key :id
27
+ end
28
+
29
+ DB.create_table :album_genres do
30
+ primary_key :id
31
+ foreign_key :album_id, :albums
32
+ foreign_key :genre_id, :genres
33
+
34
+ unique [:album_id, :genre_id]
35
+ end
36
+
37
+ class Artist < Sequel::Model
38
+ one_to_many :albums
39
+ end
40
+
41
+ class Album < Sequel::Model
42
+ many_to_one :artist
43
+ one_to_many :tracks
44
+
45
+ many_to_many :genres, join_table: :album_genres
46
+ end
47
+
48
+ class Track < Sequel::Model
49
+ many_to_one :album
50
+ end
51
+
52
+ class Genre < Sequel::Model
53
+ many_to_many :albums, join_table: :album_genres
54
+ end
55
+
56
+ DB.run <<-SQL
57
+ INSERT INTO artists SELECT i FROM generate_series(1, 10) i;
58
+ INSERT INTO albums (artist_id) SELECT (i % 10) + 1 FROM generate_series(1, 100) i;
59
+ INSERT INTO tracks (album_id) SELECT (i % 100) + 1 FROM generate_series(1, 1000) i;
60
+ INSERT INTO genres SELECT i FROM generate_series(1, 10) i;
61
+
62
+ INSERT INTO album_genres (album_id, genre_id)
63
+ SELECT DISTINCT ceil(random() * 100), ceil(random() * 10) FROM generate_series(1, 300);
64
+ SQL
65
+
66
+ require 'pry'
67
+ require 'minitest/autorun'
68
+ require 'minitest/pride'
69
+
70
+ class AssociationFilteringSpecs < Minitest::Spec
71
+ def around
72
+ DB.transaction(rollback: :always) { super }
73
+ end
74
+ end
data/tasks/specs.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new :default do |t|
5
+ t.libs = ['spec']
6
+ t.pattern = 'spec/**/*_spec.rb'
7
+ end
8
+
9
+ task :spec => :default
10
+ task :test => :default
metadata ADDED
@@ -0,0 +1,121 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sequel-association-filtering
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Chris Hanks
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-04-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pg
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sequel
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '5.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '5.0'
69
+ description: Easily filter records by the presence/absence of associated records.
70
+ email:
71
+ - christopher.m.hanks@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - ".rspec"
78
+ - Gemfile
79
+ - LICENSE.txt
80
+ - README.md
81
+ - Rakefile
82
+ - lib/sequel-association-filtering.rb
83
+ - lib/sequel/plugins/association_filtering.rb
84
+ - lib/sequel/plugins/association_filtering/version.rb
85
+ - sequel-association-filtering.gemspec
86
+ - spec/basic_spec.rb
87
+ - spec/many_to_many_spec.rb
88
+ - spec/many_to_one_spec.rb
89
+ - spec/one_to_many_spec.rb
90
+ - spec/spec_helper.rb
91
+ - tasks/specs.rb
92
+ homepage: https://github.com/chanks/sequel-association-filtering
93
+ licenses:
94
+ - MIT
95
+ metadata: {}
96
+ post_install_message:
97
+ rdoc_options: []
98
+ require_paths:
99
+ - lib
100
+ required_ruby_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ requirements: []
111
+ rubyforge_project:
112
+ rubygems_version: 2.7.3
113
+ signing_key:
114
+ specification_version: 4
115
+ summary: Easy filtering through Sequel associations.
116
+ test_files:
117
+ - spec/basic_spec.rb
118
+ - spec/many_to_many_spec.rb
119
+ - spec/many_to_one_spec.rb
120
+ - spec/one_to_many_spec.rb
121
+ - spec/spec_helper.rb