scoped_search 4.1.8 → 4.1.9
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/.travis.yml +11 -0
- data/lib/scoped_search/query_builder.rb +40 -38
- data/lib/scoped_search/version.rb +1 -1
- data/spec/integration/nested_has_many_through_querying_spec.rb +100 -0
- data/spec/integration/relation_querying_spec.rb +63 -1
- data/spec/lib/database.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9c1453ebb8fe2391f71ea35171f2115ed89bb7920a1a042d86a4393fe218888c
|
4
|
+
data.tar.gz: 61d2d0e948a952f7e955240747dae7e529103c5871ad3160cb2213574f0f155a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 04e61c564c6117d60b6f82b970df5c6c7a68362d828d89d1cf5d2bf7f6b7c9ea777cf641dbb2a21be895905305c0c7305105c9840d391aa702cd69eab706fd4d
|
7
|
+
data.tar.gz: 842c31b1714558d8bd4578d47b87bca4fa4298617a16168b66a77e6ff06ee1f43406b30be3557a9c50222ba5effaa9c5eec27832839e75bb11faf085f95bb7bc
|
data/.travis.yml
CHANGED
@@ -21,6 +21,7 @@ rvm:
|
|
21
21
|
- "2.4.0"
|
22
22
|
- "2.5.1"
|
23
23
|
- "2.6.0"
|
24
|
+
- "2.7.1"
|
24
25
|
- ruby-head
|
25
26
|
- jruby-19mode
|
26
27
|
- jruby-head
|
@@ -76,3 +77,13 @@ matrix:
|
|
76
77
|
gemfile: Gemfile.activerecord60_with_activesupport60
|
77
78
|
- rvm: "2.4.0"
|
78
79
|
gemfile: Gemfile.activerecord60_with_activesupport60
|
80
|
+
- rvm: "2.7.1"
|
81
|
+
gemfile: Gemfile.activerecord42
|
82
|
+
- rvm: "2.7.1"
|
83
|
+
gemfile: Gemfile.activerecord50
|
84
|
+
- rvm: "2.7.1"
|
85
|
+
gemfile: Gemfile.activerecord51
|
86
|
+
- rvm: "2.7.1"
|
87
|
+
gemfile: Gemfile.activerecord52
|
88
|
+
- rvm: "2.7.1"
|
89
|
+
gemfile: Gemfile.activerecord52_with_activesupport52
|
@@ -229,11 +229,13 @@ module ScopedSearch
|
|
229
229
|
|
230
230
|
if field.relation && definition.reflection_by_name(field.definition.klass, field.relation).macro == :has_many
|
231
231
|
connection = field.definition.klass.connection
|
232
|
-
|
233
|
-
|
232
|
+
reflection = definition.reflection_by_name(field.definition.klass, field.relation)
|
233
|
+
primary_key_col = reflection.options[:primary_key] || field.definition.klass.primary_key
|
234
|
+
primary_key = "#{connection.quote_table_name(field.definition.klass.table_name)}.#{connection.quote_column_name(primary_key_col)}"
|
235
|
+
key, join_table = if reflection.options.has_key?(:through)
|
234
236
|
[primary_key, has_many_through_join(field)]
|
235
237
|
else
|
236
|
-
[connection.quote_column_name(field.reflection_keys(
|
238
|
+
[connection.quote_column_name(field.reflection_keys(reflection)[1]),
|
237
239
|
connection.quote_table_name(field.klass.table_name)]
|
238
240
|
end
|
239
241
|
|
@@ -269,46 +271,46 @@ module ScopedSearch
|
|
269
271
|
middle_table_association
|
270
272
|
end
|
271
273
|
|
274
|
+
# Walk the chain of has-many-throughs, collecting all tables we will need to join
|
275
|
+
def nested_has_many(many_class, relation)
|
276
|
+
acc = [relation]
|
277
|
+
while (reflection = definition.reflection_by_name(many_class, relation))
|
278
|
+
break if reflection.nil? || reflection.options[:through].nil?
|
279
|
+
relation = reflection.options[:through]
|
280
|
+
acc.unshift(relation)
|
281
|
+
end
|
282
|
+
acc.map { |relation| definition.reflection_by_name(many_class, relation) }
|
283
|
+
end
|
284
|
+
|
272
285
|
def has_many_through_join(field)
|
273
286
|
many_class = field.definition.klass
|
274
|
-
through = definition.reflection_by_name(many_class, field.relation).options[:through]
|
275
|
-
through_class = definition.reflection_by_name(many_class, through).klass
|
276
|
-
|
277
287
|
connection = many_class.connection
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
<<-SQL
|
299
|
-
#{connection.quote_table_name(many_table_name)}
|
300
|
-
INNER JOIN #{connection.quote_table_name(middle_table_name)}
|
301
|
-
ON #{connection.quote_table_name(many_table_name)}.#{connection.quote_column_name(pk1)} = #{connection.quote_table_name(middle_table_name)}.#{connection.quote_column_name(fk1)} #{condition_many_to_middle} #{condition_middle_to_end}
|
302
|
-
INNER JOIN #{connection.quote_table_name(endpoint_table_name)}
|
303
|
-
ON #{connection.quote_table_name(middle_table_name)}.#{connection.quote_column_name(fk2)} = #{connection.quote_table_name(endpoint_table_name)}.#{connection.quote_column_name(pk2)} #{condition2}
|
304
|
-
SQL
|
288
|
+
sql = connection.quote_table_name(many_class.table_name)
|
289
|
+
join_reflections = nested_has_many(many_class, field.relation)
|
290
|
+
table_names = [many_class.table_name] + join_reflections.map(&:table_name)
|
291
|
+
|
292
|
+
join_reflections.zip(table_names.zip(join_reflections.drop(1))).reduce(sql) do |acc, (reflection, (previous_table, next_reflection))|
|
293
|
+
klass = reflection.method(:join_keys).arity == 1 ? [reflection.klass] : [] # ActiveRecord <5.2 workaround
|
294
|
+
fk1, pk1 = reflection.join_keys(*klass).values # We are joining the tables "in reverse", so the PK and FK are swapped
|
295
|
+
|
296
|
+
# primary and foreign keys + optional conditions for the joins
|
297
|
+
join_condition = if with_polymorphism?(reflection)
|
298
|
+
field.reflection_conditions(definition.reflection_by_name(next_reflection.klass, previous_table))
|
299
|
+
else
|
300
|
+
''
|
301
|
+
end
|
302
|
+
|
303
|
+
acc + <<-SQL
|
304
|
+
INNER JOIN #{connection.quote_table_name(reflection.table_name)}
|
305
|
+
ON #{connection.quote_table_name(previous_table)}.#{connection.quote_column_name(pk1)} = #{connection.quote_table_name(reflection.table_name)}.#{connection.quote_column_name(fk1)} #{join_condition}
|
306
|
+
SQL
|
307
|
+
end
|
305
308
|
end
|
306
309
|
|
307
|
-
def with_polymorphism?(
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
definition.reflection_by_name(through_class, as.first).options[:polymorphic]
|
310
|
+
def with_polymorphism?(reflection)
|
311
|
+
as = reflection.options[:as]
|
312
|
+
return unless as
|
313
|
+
definition.reflection_by_name(reflection.klass, as).options[:polymorphic]
|
312
314
|
end
|
313
315
|
|
314
316
|
# This module gets included into the Field class to add SQL generation.
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
# These specs will run on all databases that are defined in the spec/database.yml file.
|
4
|
+
# Comment out any databases that you do not have available for testing purposes if needed.
|
5
|
+
ScopedSearch::RSpec::Database.test_databases.each do |db|
|
6
|
+
|
7
|
+
describe ScopedSearch, "using a #{db} database" do
|
8
|
+
|
9
|
+
before(:all) do
|
10
|
+
ScopedSearch::RSpec::Database.establish_named_connection(db)
|
11
|
+
end
|
12
|
+
|
13
|
+
after(:all) do
|
14
|
+
ScopedSearch::RSpec::Database.close_connection
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'quering on associations which are behind multiple has-many-through associations' do
|
18
|
+
|
19
|
+
before(:all) do
|
20
|
+
ActiveRecord::Migration.create_table(:sources) { |t| t.string :name }
|
21
|
+
ActiveRecord::Migration.create_table(:first_jumps) { |t| t.string :name; t.integer :source_id }
|
22
|
+
ActiveRecord::Migration.create_table(:join_jumps) { |t| t.string :name; t.integer :first_jump_id; t.integer :destination_id }
|
23
|
+
ActiveRecord::Migration.create_table(:destinations) { |t| t.string :name; }
|
24
|
+
|
25
|
+
class Source < ActiveRecord::Base
|
26
|
+
has_many :first_jumps
|
27
|
+
has_many :join_jumps, :through => :first_jumps
|
28
|
+
has_many :destinations, :through => :join_jumps
|
29
|
+
|
30
|
+
scoped_search :relation => :first_jumps, :on => :name, :rename => 'first_jump.name'
|
31
|
+
scoped_search :relation => :join_jumps, :on => :name, :rename => 'join_jump.name'
|
32
|
+
scoped_search :relation => :destinations, :on => :name, :rename => 'destination.name'
|
33
|
+
end
|
34
|
+
|
35
|
+
class FirstJump < ActiveRecord::Base
|
36
|
+
belongs_to :source
|
37
|
+
has_many :join_jumps
|
38
|
+
has_many :destinations, :through => :join_jumps
|
39
|
+
end
|
40
|
+
|
41
|
+
class JoinJump < ActiveRecord::Base
|
42
|
+
has_one :source, :through => :first_jump
|
43
|
+
belongs_to :first_jump
|
44
|
+
belongs_to :destination
|
45
|
+
end
|
46
|
+
|
47
|
+
class Destination < ActiveRecord::Base
|
48
|
+
has_many :join_jumps
|
49
|
+
has_many :first_jumps, :through => :join_jumps
|
50
|
+
has_many :sources, :through => :first_jumps
|
51
|
+
end
|
52
|
+
|
53
|
+
@destination1 = Destination.create!(:name => 'dest-1')
|
54
|
+
@destination2 = Destination.create!(:name => 'dest-2')
|
55
|
+
@destination3 = Destination.create!(:name => 'dest-3')
|
56
|
+
@source1 = Source.create!(:name => 'src1')
|
57
|
+
@first_jump1 = FirstJump.create!(:name => 'jump-1-1', :source => @source1)
|
58
|
+
@first_jump2 = FirstJump.create!(:name => 'jump-1-2', :source => @source1)
|
59
|
+
|
60
|
+
@source2 = Source.create!(:name => 'src2')
|
61
|
+
@first_jump_2_1 = FirstJump.create!(:name => 'jump-2-1', :source => @source2)
|
62
|
+
@first_jump_2_2 = FirstJump.create!(:name => 'jump-2-2', :source => @source2)
|
63
|
+
@first_jump_2_3 = FirstJump.create!(:name => 'jump-2-3', :source => @source2)
|
64
|
+
@first_jump_2_4 = FirstJump.create!(:name => 'jump-2-4', :source => @source2)
|
65
|
+
|
66
|
+
JoinJump.create!(:name => 'join-1-1', :destination => @destination1, :first_jump => @first_jump1)
|
67
|
+
JoinJump.create!(:name => 'join-1-2', :destination => @destination2, :first_jump => @first_jump2)
|
68
|
+
|
69
|
+
JoinJump.create!(:name => 'join-2-1', :destination => @destination1, :first_jump => @first_jump_2_1)
|
70
|
+
JoinJump.create!(:name => 'join-2-2', :destination => @destination2, :first_jump => @first_jump_2_2)
|
71
|
+
JoinJump.create!(:name => 'join-2-3', :destination => @destination2, :first_jump => @first_jump_2_3)
|
72
|
+
JoinJump.create!(:name => 'join-2-4', :destination => @destination3, :first_jump => @first_jump_2_4)
|
73
|
+
end
|
74
|
+
|
75
|
+
after(:all) do
|
76
|
+
ScopedSearch::RSpec::Database.drop_model(Source)
|
77
|
+
ScopedSearch::RSpec::Database.drop_model(FirstJump)
|
78
|
+
ScopedSearch::RSpec::Database.drop_model(JoinJump)
|
79
|
+
ScopedSearch::RSpec::Database.drop_model(Destination)
|
80
|
+
Object.send :remove_const, :Source
|
81
|
+
Object.send :remove_const, :FirstJump
|
82
|
+
Object.send :remove_const, :JoinJump
|
83
|
+
Object.send :remove_const, :Destination
|
84
|
+
end
|
85
|
+
|
86
|
+
it "allows searching on has many through has many" do
|
87
|
+
Source.search_for("join_jump.name = join-1-1").should == [@source1]
|
88
|
+
Source.search_for("join_jump.name = join-2-1").should == [@source2]
|
89
|
+
Source.search_for("join_jump.name ^ (join-1-1, join-2-1)").order(:id).should == [@source1, @source2]
|
90
|
+
end
|
91
|
+
|
92
|
+
it "allows searching on has many through has one through has many" do
|
93
|
+
Source.search_for("destination.name = dest-1").order(:id).should == [@source1, @source2]
|
94
|
+
Source.search_for("destination.name = dest-3").order(:id).should == [@source2]
|
95
|
+
Source.search_for("destination.name = dest-3 or destination.name = dest-2").order(:id).should == [@source1, @source2]
|
96
|
+
Source.search_for("destination.name = dest-3 and destination.name = dest-2").should == [@source2]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -351,9 +351,10 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
|
|
351
351
|
|
352
352
|
# Create some tables
|
353
353
|
ActiveRecord::Migration.create_table(:taggables) { |t| t.integer :taggable_id; t.string :taggable_type; t.integer :tag_id }
|
354
|
-
ActiveRecord::Migration.create_table(:dogs) { |t| t.string :related }
|
354
|
+
ActiveRecord::Migration.create_table(:dogs) { |t| t.string :related; t.integer :owner_id }
|
355
355
|
ActiveRecord::Migration.create_table(:cats) { |t| t.string :related }
|
356
356
|
ActiveRecord::Migration.create_table(:tags) { |t| t.string :foo }
|
357
|
+
ActiveRecord::Migration.create_table(:owners) { |t| t.string :name }
|
357
358
|
|
358
359
|
# The related classes
|
359
360
|
class Taggable < ActiveRecord::Base; belongs_to :tag; belongs_to :taggable, :polymorphic => true; end
|
@@ -369,6 +370,7 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
|
|
369
370
|
class Dog < ActiveRecord::Base
|
370
371
|
has_many :taggables, :as => :taggable
|
371
372
|
has_many :tags, :through => :taggables
|
373
|
+
belongs_to :owner
|
372
374
|
|
373
375
|
scoped_search :relation => :tags, :on => :foo
|
374
376
|
end
|
@@ -378,6 +380,14 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
|
|
378
380
|
has_many :tags, :through => :taggables
|
379
381
|
end
|
380
382
|
|
383
|
+
class Owner < ActiveRecord::Base
|
384
|
+
has_many :dogs
|
385
|
+
has_many :taggables, :as => :taggable, :through => :dogs
|
386
|
+
has_many :tags, :through => :taggables
|
387
|
+
|
388
|
+
scoped_search :relation => :tags, :on => :foo
|
389
|
+
end
|
390
|
+
|
381
391
|
@tag_1 = Tag.create!(:foo => 'foo')
|
382
392
|
@tag_2 = Tag.create!(:foo => 'foo too')
|
383
393
|
@tag_3 = Tag.create!(:foo => 'foo three')
|
@@ -386,6 +396,8 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
|
|
386
396
|
@dog_2 = Dog.create(:related => 'baz too!')
|
387
397
|
@cat_1 = Cat.create(:related => 'mitzi')
|
388
398
|
|
399
|
+
@owner_1 = Owner.create(:name => 'Fred', :dogs => [@dog_1])
|
400
|
+
|
389
401
|
Taggable.create!(:tag => @tag_1, :taggable => @dog_1, :taggable_type => 'Dog' )
|
390
402
|
Taggable.create!(:tag => @tag_1)
|
391
403
|
Taggable.create!(:tag => @tag_2, :taggable => @dog_1 , :taggable_type => 'Dog')
|
@@ -400,12 +412,18 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
|
|
400
412
|
ActiveRecord::Migration.drop_table(:taggables)
|
401
413
|
ActiveRecord::Migration.drop_table(:tags)
|
402
414
|
ActiveRecord::Migration.drop_table(:cats)
|
415
|
+
ActiveRecord::Migration.drop_table(:owners)
|
403
416
|
end
|
404
417
|
|
405
418
|
it "should find the two records that are related to a tag that contains foo record" do
|
406
419
|
Dog.search_for('foo').length.should == 2
|
407
420
|
end
|
408
421
|
|
422
|
+
it "should find the only record that is related to a tag" do
|
423
|
+
Owner.search_for('foo').length.should == 1
|
424
|
+
Owner.search_for('foo').to_sql.should =~ /taggable_type = 'Dog'/
|
425
|
+
end
|
426
|
+
|
409
427
|
it "should find one records that is related to both tags" do
|
410
428
|
Dog.search_for('foo=foo AND foo="foo too"').length.should == 1
|
411
429
|
end
|
@@ -720,5 +738,49 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
|
|
720
738
|
result.first.username.should == @usermat_1.username
|
721
739
|
end
|
722
740
|
end
|
741
|
+
|
742
|
+
context "querying on :has_many with primary key override" do
|
743
|
+
|
744
|
+
before do
|
745
|
+
ActiveRecord::Migration.create_table(:books) { |t| t.string :title; t.string :isbn }
|
746
|
+
ActiveRecord::Migration.create_table(:comments) { |t| t.string :comment; t.string :isbn }
|
747
|
+
|
748
|
+
class Book < ActiveRecord::Base
|
749
|
+
has_many :comments, foreign_key: 'isbn', primary_key: 'isbn'
|
750
|
+
|
751
|
+
scoped_search on: [:title]
|
752
|
+
scoped_search relation: :comments, on: [:comment]
|
753
|
+
end
|
754
|
+
|
755
|
+
class Comment < ActiveRecord::Base
|
756
|
+
belongs_to :book, foreign_key: 'isbn', primary_key: 'isbn'
|
757
|
+
end
|
758
|
+
|
759
|
+
@book1 = Book.create(:title => 'Eloquent Ruby', :isbn => '978-0321584106')
|
760
|
+
@book2 = Book.create(:title => 'The Well-Grounded Rubyist', :isbn => '978-1617295218')
|
761
|
+
Comment.create(:comment => 'Definitely worth a read', :isbn => @book1.isbn)
|
762
|
+
Comment.create(:comment => 'Wait what? I expected a book about gemstones', :isbn => @book1.isbn)
|
763
|
+
Comment.create(:comment => 'Cool book about ruby', :isbn => @book2.isbn)
|
764
|
+
end
|
765
|
+
|
766
|
+
after do
|
767
|
+
ActiveRecord::Migration.drop_table :comments
|
768
|
+
ActiveRecord::Migration.drop_table :books
|
769
|
+
end
|
770
|
+
|
771
|
+
it "correctly joins the tables" do
|
772
|
+
query = Book.search_for("test").to_sql
|
773
|
+
# On PostgreSQL and SQLite we use double quotes, on MySQL we use backticks
|
774
|
+
query.should =~ /LEFT OUTER JOIN ["`]comments["`] ON ["`]comments["`]\.["`]isbn["`] = ["`]books["`]\.["`]isbn["`]/
|
775
|
+
query.should =~ /["`]books["`]\.["`]isbn["`] IN \(SELECT ["`]isbn["`]/
|
776
|
+
end
|
777
|
+
|
778
|
+
it "finds the right results" do
|
779
|
+
Book.search_for('python').should == []
|
780
|
+
Book.search_for('comment ~ Wait').should == [@book1]
|
781
|
+
Book.search_for('comment ~ book').pluck(:id).sort.uniq.should == [@book1, @book2].map(&:id).sort
|
782
|
+
Book.search_for('comment ~ Cool').should == [@book2]
|
783
|
+
end
|
784
|
+
end
|
723
785
|
end
|
724
786
|
end
|
data/spec/lib/database.rb
CHANGED
@@ -48,7 +48,7 @@ module ScopedSearch::RSpec::Database
|
|
48
48
|
ActiveRecord::Migration.create_table(table_name) do |t|
|
49
49
|
fields.each do |name, field_type|
|
50
50
|
options = (field_type == :decimal) ? { :scale => 2, :precision => 10 } : {}
|
51
|
-
t.send(field_type.to_s.gsub(/^unindexed_/, '').to_sym, name, options)
|
51
|
+
t.send(field_type.to_s.gsub(/^unindexed_/, '').to_sym, name, **options)
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: scoped_search
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.1.
|
4
|
+
version: 4.1.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Amos Benari
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2020-
|
13
|
+
date: 2020-08-24 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activerecord
|
@@ -116,6 +116,7 @@ files:
|
|
116
116
|
- spec/integration/auto_complete_spec.rb
|
117
117
|
- spec/integration/ext_method_spec.rb
|
118
118
|
- spec/integration/key_value_querying_spec.rb
|
119
|
+
- spec/integration/nested_has_many_through_querying_spec.rb
|
119
120
|
- spec/integration/ordinal_querying_spec.rb
|
120
121
|
- spec/integration/profile_querying_spec.rb
|
121
122
|
- spec/integration/rails_helper_spec.rb
|
@@ -173,6 +174,7 @@ test_files:
|
|
173
174
|
- spec/integration/auto_complete_spec.rb
|
174
175
|
- spec/integration/ext_method_spec.rb
|
175
176
|
- spec/integration/key_value_querying_spec.rb
|
177
|
+
- spec/integration/nested_has_many_through_querying_spec.rb
|
176
178
|
- spec/integration/ordinal_querying_spec.rb
|
177
179
|
- spec/integration/profile_querying_spec.rb
|
178
180
|
- spec/integration/rails_helper_spec.rb
|