scoped_search 4.1.4 → 4.1.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,3 @@
1
1
  module ScopedSearch
2
- VERSION = "4.1.4"
2
+ VERSION = "4.1.9"
3
3
  end
@@ -17,6 +17,13 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
17
17
  t.string :other_c
18
18
  end
19
19
 
20
+ ActiveRecord::Migration.create_table(:quxes, :force => true) do |t|
21
+ t.string :related
22
+ t.string :other_a
23
+ t.string :other_b
24
+ t.string :other_c
25
+ end
26
+
20
27
  ActiveRecord::Migration.create_table(:foos, :force => true) do |t|
21
28
  t.string :string
22
29
  t.string :another
@@ -25,17 +32,29 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
25
32
  t.integer :int
26
33
  t.date :date
27
34
  t.integer :unindexed
35
+ t.integer :qux_id
36
+
37
+ if on_postgresql?
38
+ t.uuid :uuid
39
+ else
40
+ t.string :uuid
41
+ end
28
42
  end
29
43
 
30
44
  class ::Bar < ActiveRecord::Base
31
45
  belongs_to :foo
32
46
  end
33
47
 
48
+ class ::Qux < ActiveRecord::Base
49
+ has_many :foos
50
+ end
51
+
34
52
  class ::Foo < ActiveRecord::Base
35
53
  has_many :bars
54
+ belongs_to :qux
36
55
  default_scope { order(:string) }
37
56
 
38
- scoped_search :on => [:string, :date]
57
+ scoped_search :on => [:string, :date, :uuid]
39
58
  scoped_search :on => [:int], :complete_value => true
40
59
  scoped_search :on => :another, :default_operator => :eq, :aliases => [:alias], :complete_value => true
41
60
  scoped_search :on => :explicit, :only_explicit => true, :complete_value => true
@@ -49,7 +68,10 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
49
68
  class ::Infoo < ::Foo
50
69
  end
51
70
 
52
- @foo_1 = Foo.create!(:string => 'foo', :another => 'temp 1', :explicit => 'baz', :int => 9 , :date => 'February 8, 2011' , :unindexed => 10)
71
+ @qux_1 = Qux.create!()
72
+ @qux_2 = Qux.create!()
73
+ @foo_1 = Foo.create!(:string => 'foo', :another => 'temp 1', :explicit => 'baz', :int => 9 , :date => 'February 8, 2011' , :unindexed => 10, :qux => @qux_1)
74
+ @foo_2 = Foo.create!(:string => 'foo', :another => 'temp 2', :explicit => 'baz', :int => 10 , :date => 'February 8, 2011' , :unindexed => 10, :qux => @qux_2)
53
75
  Foo.create!(:string => 'bar', :another => 'temp "2"', :explicit => 'baz', :int => 22 , :date => 'February 10, 2011', :unindexed => 10)
54
76
  Foo.create!(:string => 'baz', :another => nil, :explicit => nil , :int => nil, :date => nil , :unindexed => nil)
55
77
  20.times { Foo.create!(:explicit => "aaa") }
@@ -90,6 +112,12 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
90
112
  Foo.complete_for('date ').should =~ (["date = ", "date < ", "date > "])
91
113
  end
92
114
 
115
+ it "should complete the uuid comparators" do
116
+ if on_postgresql?
117
+ Foo.complete_for('uuid ').should =~ (["uuid = ", "uuid != "])
118
+ end
119
+ end
120
+
93
121
  it "should complete when query is already distinct" do
94
122
  Foo.distinct.complete_for('int =').length.should > 0
95
123
  end
@@ -190,7 +218,7 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
190
218
  context 'exceptional search strings' do
191
219
 
192
220
  it "query that starts with 'or'" do
193
- Foo.complete_for('or ').length.should == 9
221
+ Foo.complete_for('or ').length.should == 10
194
222
  end
195
223
 
196
224
  it "value completion with quotes" do
@@ -212,7 +240,7 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
212
240
  # the string.
213
241
  context 'dotted options in the completion list' do
214
242
  it "query that starts with space should not include the dotted options" do
215
- Foo.complete_for(' ').length.should == 9
243
+ Foo.complete_for(' ').length.should == 10
216
244
  end
217
245
 
218
246
  it "query that starts with the dotted string should include the dotted options" do
@@ -230,5 +258,14 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
230
258
  Foo.complete_for('int > 2').first.should match(/22/)
231
259
  end
232
260
  end
261
+
262
+ context 'autocompleting with value_filter' do
263
+ it 'should return filtered values' do
264
+ Foo.complete_for('int =', value_filter: { qux_id: @qux_1.id }).should == ['int = 9']
265
+ end
266
+ it 'should return filtered for invalid value' do
267
+ Foo.complete_for('int =', value_filter: { qux_id: 99 }).should == []
268
+ end
269
+ end
233
270
  end
234
271
  end
@@ -33,6 +33,7 @@ require "spec_helper"
33
33
  has_many :keys, :through => :facts
34
34
 
35
35
  scoped_search :relation => :facts, :on => :value, :rename => :facts, :in_key => :keys, :on_key => :name, :complete_value => true
36
+ scoped_search :relation => :facts, :on => :value, :rename => 'myfacts.value', :aliases => ['prefixed', 'prefixed.value']
36
37
  end
37
38
  class ::MyItem < ::Item
38
39
  end
@@ -102,6 +103,20 @@ require "spec_helper"
102
103
  it "should find all bars with a fact name color and fact value gold of descendant class" do
103
104
  MyItem.search_for('facts.color = gold').first.name.should eql('barbary')
104
105
  end
106
+
107
+ describe 'with prefixed aliases' do
108
+ it 'should search by the prefix' do
109
+ Item.search_for('prefixed = green').length.should == 1
110
+ end
111
+
112
+ it 'should search by the full length variant' do
113
+ Item.search_for('prefixed.value = green').length.should == 1
114
+ end
115
+
116
+ it 'should not search by just any key with common prefix' do
117
+ proc { Item.search_for('prefixed.something_which_is_not_defined = green') }.should raise_error(ScopedSearch::QueryNotSupported)
118
+ end
119
+ end
105
120
  end
106
121
  end
107
122
  end
@@ -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
@@ -13,15 +13,18 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
13
13
 
14
14
  @parent_class = ScopedSearch::RSpec::Database.create_model(int: :integer, type: :string, related_id: :integer) do |klass|
15
15
  klass.scoped_search on: :int
16
+ klass.belongs_to @related_class.table_name.to_sym, foreign_key: :related_id
16
17
  end
17
18
  @subclass1 = ScopedSearch::RSpec::Database.create_sti_model(@parent_class)
18
19
  @subclass2 = ScopedSearch::RSpec::Database.create_sti_model(@parent_class) do |klass|
19
- klass.belongs_to @related_class.table_name.to_sym, foreign_key: :related_id
20
20
  klass.scoped_search on: :int, rename: :other_int
21
21
  klass.scoped_search relation: @related_class.table_name, on: :int, rename: :related_int
22
22
  end
23
23
 
24
- @related_class.has_many @subclass1.table_name.to_sym
24
+ @related_class.has_many @subclass1.table_name.to_sym, :foreign_key => :related_id
25
+ @related_class.has_many @subclass2.table_name.to_sym, :foreign_key => :related_id
26
+ @related_class.scoped_search :relation => @subclass1.table_name.to_sym, :on => :int, :rename => 'subclass1.id'
27
+ @related_class.scoped_search :relation => @subclass2.table_name.to_sym, :on => :int, :rename => 'subclass2.id'
25
28
 
26
29
  @record1 = @subclass1.create!(int: 7)
27
30
  @related_record1 = @related_class.create!(int: 42)
@@ -79,5 +82,12 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
79
82
  @subclass2.search_for('related_int = 42').should eq([@record2])
80
83
  end
81
84
  end
85
+
86
+ context 'querying related records' do
87
+ it 'shuld find only relevant instances of STI subclasses' do
88
+ @related_class.search_for("subclass1.id ^ (#{@record1.int})").should eq([])
89
+ @related_class.search_for("subclass2.id ^ (#{@record1.int}, #{@record2.int})").should eq([@related_record1])
90
+ end
91
+ end
82
92
  end
83
93
  end
@@ -0,0 +1,57 @@
1
+ require "spec_helper"
2
+ require 'securerandom'
3
+
4
+ # These specs will run on all databases that are defined in the spec/database.yml file.
5
+ # Comment out any databases that you do not have available for testing purposes if needed.
6
+ ScopedSearch::RSpec::Database.test_databases.each do |db|
7
+ describe ScopedSearch, "using a #{db} database" do
8
+ before(:all) do
9
+ ScopedSearch::RSpec::Database.establish_named_connection(db)
10
+
11
+ columns = on_postgresql? ? { :uuid => :uuid } : { :uuid => :string }
12
+
13
+ @class = ScopedSearch::RSpec::Database.create_model(columns.merge(:string => :string)) do |klass|
14
+ klass.scoped_search :on => :uuid
15
+ klass.scoped_search :on => :string
16
+ end
17
+ end
18
+
19
+ after(:all) do
20
+ ScopedSearch::RSpec::Database.drop_model(@class)
21
+ ScopedSearch::RSpec::Database.close_connection
22
+ end
23
+
24
+ context 'querying boolean fields' do
25
+
26
+ before(:all) do
27
+ @record1 = @class.create!(:uuid => SecureRandom.uuid)
28
+ @record2 = @class.create!(:uuid => SecureRandom.uuid)
29
+ @record3 = @class.create!(:uuid => SecureRandom.uuid)
30
+ end
31
+
32
+ after(:all) do
33
+ @record1.destroy
34
+ @record2.destroy
35
+ @record3.destroy
36
+ end
37
+
38
+ it "should find the first record" do
39
+ @class.search_for("uuid = #{@record1.uuid}").length.should == 1
40
+ end
41
+
42
+ it "should find two records with negative match" do
43
+ @class.search_for("uuid != #{@record3.uuid}").length.should == 2
44
+ end
45
+
46
+ it "should find a record by just specifying the uuid" do
47
+ @class.search_for(@record1.uuid).first.uuid.should == @record1.uuid
48
+ end
49
+
50
+ it "should not find a record if the uuid is not a valid uuid" do
51
+ if on_postgresql?
52
+ @class.search_for(@record1.uuid[0..-2]).length.should == 0
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -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