attr_searchable 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +42 -6
- data/attr_searchable.gemspec +2 -2
- data/lib/attr_searchable.rb +8 -0
- data/lib/attr_searchable/version.rb +1 -1
- data/lib/attr_searchable_grammar.rb +1 -4
- data/lib/attr_searchable_grammar/attributes.rb +5 -1
- data/test/attr_searchable_test.rb +27 -1
- data/test/date_test.rb +2 -2
- data/test/datetime_test.rb +2 -2
- data/test/test_helper.rb +2 -1
- metadata +4 -4
data/README.md
CHANGED
@@ -106,9 +106,9 @@ AttrSearchable will use `LIKE '%...%'` queries. Unfortunately, unless you
|
|
106
106
|
create a [trigram index](http://www.postgresql.org/docs/9.1/static/pgtrgm.html)
|
107
107
|
(postgres only), theses queries can not use SQL indices, such that every row
|
108
108
|
needs to be scanned by your RDBMS when you search for `Book.search("Harry
|
109
|
-
Potter")` or similar.
|
110
|
-
capabilities of MySQL and PostgreSQL. To use
|
111
|
-
simply tell AttrSearchable to use them via:
|
109
|
+
Potter")` or similar. To avoid the penalty of `LIKE` queries, AttrSearchable
|
110
|
+
can exploit the fulltext index capabilities of MySQL and PostgreSQL. To use
|
111
|
+
already existing fulltext indices, simply tell AttrSearchable to use them via:
|
112
112
|
|
113
113
|
```ruby
|
114
114
|
class Book < ActiveRecord::Base
|
@@ -142,13 +142,17 @@ to search in, such that AttrSearchable must no longer search within all fields:
|
|
142
142
|
|
143
143
|
```ruby
|
144
144
|
attr_searchable :all => [:author, :title]
|
145
|
+
|
145
146
|
attr_searchable_options :all, :type => :fulltext, :default => true
|
147
|
+
|
148
|
+
# Use :default => true to explicitly enable fields as default fields (whitelist approach)
|
149
|
+
# Use :default => false to explicitly disable fields as default fields (blacklist approach)
|
146
150
|
```
|
147
151
|
|
148
152
|
Now AttrSearchable can optimize the following, not yet optimal query:
|
149
153
|
|
150
154
|
```ruby
|
151
|
-
|
155
|
+
Book.search("Rowling OR Tolkien stock > 1")
|
152
156
|
# MySQL: ... WHERE ((MATCH(books.author) AGAINST('+Rowling' IN BOOLEAN MODE) OR MATCH(books.title) AGAINST('+Rowling' IN BOOLEAN MODE)) OR (MATCH(books.author) AGAINST('+Tolkien' IN BOOLEAN MODE) OR MATCH(books.title) AGAINST('+Tolkien' IN BOOLEAN MODE))) AND books.stock > 1
|
153
157
|
# PostgreSQL: ... WHERE ((to_tsvector('simple', books.author) @@ to_tsquery('simple', 'Rowling') OR to_tsvector('simple', books.title) @@ to_tsquery('simple', 'Rowling')) OR (to_tsvector('simple', books.author) @@ to_tsquery('simple', 'Tolkien') OR to_tsvector('simple', books.title) @@ to_tsquery('simple', 'Tolkien'))) AND books.stock > 1
|
154
158
|
```
|
@@ -156,7 +160,7 @@ BookSearch("Rowling OR Tolkien stock > 1")
|
|
156
160
|
to the following, more performant query:
|
157
161
|
|
158
162
|
```ruby
|
159
|
-
|
163
|
+
Book.search("Rowling OR Tolkien stock > 1")
|
160
164
|
# MySQL: ... WHERE MATCH(books.author, books.title) AGAINST('Rowling Tolkien' IN BOOLEAN MODE) AND books.stock > 1
|
161
165
|
# PostgreSQL: ... WHERE to_tsvector('simple', books.author || ' ' || books.title) @@ to_tsquery('simple', 'Rowling | Tokien') and books.stock > 1
|
162
166
|
```
|
@@ -166,7 +170,7 @@ tries to minimize the fultext constraints within a query, namely `MATCH()
|
|
166
170
|
AGAINST()` for MySQL and `to_tsvector() @@ to_tsquery()` for PostgreSQL.
|
167
171
|
|
168
172
|
```ruby
|
169
|
-
|
173
|
+
Book.search("(Rowling -Potter) OR Tolkien")
|
170
174
|
# MySQL: ... WHERE MATCH(books.author, books.title) AGAINST('(+Rowling -Potter) Tolkien' IN BOOLEAN MODE)
|
171
175
|
# PostgreSQL: ... WHERE to_tsvector('simple', books.author || ' ' || books.title) @@ to_tsquery('simple', '(Rowling & !Potter) | Tolkien')
|
172
176
|
```
|
@@ -412,6 +416,31 @@ returns an empty relation. However, if you need to debug certain cases, use
|
|
412
416
|
Book.unsafe_search("stock: None") # => raise AttrSearchable::IncompatibleDatatype
|
413
417
|
```
|
414
418
|
|
419
|
+
## Reflection
|
420
|
+
|
421
|
+
AttrSearchable provides reflective methods, namely `#searchable_attributes`,
|
422
|
+
`#default_searchable_attributes`, `#searchable_attribute_options` and
|
423
|
+
`#searchable_attribute_aliases`. You can use these methods to e.g. provide an
|
424
|
+
individual search help widget for your models, that lists the attributes to
|
425
|
+
search in as well as the default ones, etc.
|
426
|
+
|
427
|
+
```ruby
|
428
|
+
class Product < ActiveRecord::Base
|
429
|
+
include AttrSearchable
|
430
|
+
|
431
|
+
attr_searchable :title, :description
|
432
|
+
attr_searchable_options :title, :default => true
|
433
|
+
end
|
434
|
+
|
435
|
+
Product.searchable_attributes
|
436
|
+
# {"title" => ["products.title"], "description" => ["products.description"]}
|
437
|
+
|
438
|
+
Product.default_searchable_attributes
|
439
|
+
# {"title" => ["products.title"]}
|
440
|
+
|
441
|
+
# ...
|
442
|
+
```
|
443
|
+
|
415
444
|
## Contributing
|
416
445
|
|
417
446
|
1. Fork it
|
@@ -422,6 +451,13 @@ Book.unsafe_search("stock: None") # => raise AttrSearchable::IncompatibleDatatyp
|
|
422
451
|
|
423
452
|
## Changelog
|
424
453
|
|
454
|
+
Version 0.0.5:
|
455
|
+
|
456
|
+
* Supporting :default => false
|
457
|
+
* Datetime/Date greater operator fix
|
458
|
+
* Use reflection to find associated models
|
459
|
+
* Providing reflection
|
460
|
+
|
425
461
|
Version 0.0.4:
|
426
462
|
|
427
463
|
* Fixed date attributes
|
data/attr_searchable.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = AttrSearchable::VERSION
|
9
9
|
spec.authors = ["Benjamin Vetter"]
|
10
10
|
spec.email = ["vetter@flakks.com"]
|
11
|
-
spec.description = %q{Search
|
12
|
-
spec.summary = %q{Easily perform complex search
|
11
|
+
spec.description = %q{Search engine like fulltext query support for ActiveRecord}
|
12
|
+
spec.summary = %q{Easily perform complex search engine like fulltext queries on your ActiveRecord models}
|
13
13
|
spec.homepage = "https://github.com/mrkamel/attr_searchable"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
data/lib/attr_searchable.rb
CHANGED
@@ -73,6 +73,14 @@ module AttrSearchable
|
|
73
73
|
end
|
74
74
|
end
|
75
75
|
|
76
|
+
def default_searchable_attributes
|
77
|
+
keys = searchable_attribute_options.select { |key, value| value[:default] == true }.keys
|
78
|
+
keys = searchable_attributes.keys.reject { |key| searchable_attribute_options[key] && searchable_attribute_options[key][:default] == false } if keys.empty?
|
79
|
+
keys = keys.to_set
|
80
|
+
|
81
|
+
searchable_attributes.select { |key, value| keys.include? key }
|
82
|
+
end
|
83
|
+
|
76
84
|
def search(query)
|
77
85
|
unsafe_search query
|
78
86
|
rescue AttrSearchable::RuntimeError
|
@@ -84,10 +84,7 @@ module AttrSearchableGrammar
|
|
84
84
|
|
85
85
|
class AnywhereExpression < BaseNode
|
86
86
|
def evaluate
|
87
|
-
|
88
|
-
keys = model.searchable_attributes.keys if keys.empty?
|
89
|
-
|
90
|
-
queries = keys.collect { |key| collection_for key }.select { |collection| collection.compatible? text_value }.collect { |collection| collection.matches text_value }
|
87
|
+
queries = model.default_searchable_attributes.keys.collect { |key| collection_for key }.select { |collection| collection.compatible? text_value }.collect { |collection| collection.matches text_value }
|
91
88
|
|
92
89
|
raise AttrSearchable::NoSearchableAttributes if queries.empty?
|
93
90
|
|
@@ -59,7 +59,7 @@ module AttrSearchableGrammar
|
|
59
59
|
klass = model.searchable_attribute_aliases[table]
|
60
60
|
klass ||= table
|
61
61
|
|
62
|
-
klass.classify.constantize
|
62
|
+
model.reflections[klass.to_sym] ? model.reflections[klass.to_sym].klass : klass.classify.constantize
|
63
63
|
end
|
64
64
|
|
65
65
|
def alias_for(table)
|
@@ -182,6 +182,10 @@ module AttrSearchableGrammar
|
|
182
182
|
between(parse(value)).not
|
183
183
|
end
|
184
184
|
|
185
|
+
def gt(value)
|
186
|
+
super parse(value).last
|
187
|
+
end
|
188
|
+
|
185
189
|
def between(range)
|
186
190
|
gteq(range.first).and(lteq(range.last))
|
187
191
|
end
|
@@ -49,7 +49,7 @@ class AttrSearchableTest < AttrSearchable::TestCase
|
|
49
49
|
assert_includes results, product2
|
50
50
|
end
|
51
51
|
|
52
|
-
def
|
52
|
+
def test_custom_default_enabled
|
53
53
|
product1 = create(:product, :title => "Expected")
|
54
54
|
product2 = create(:product, :description => "Expected")
|
55
55
|
product3 = create(:product, :brand => "Expected")
|
@@ -61,10 +61,36 @@ class AttrSearchableTest < AttrSearchable::TestCase
|
|
61
61
|
refute_includes results, product3
|
62
62
|
end
|
63
63
|
|
64
|
+
def test_custom_default_disabled
|
65
|
+
product1 = create(:product, :brand => "Expected")
|
66
|
+
product2 = create(:product, :notice => "Expected")
|
67
|
+
|
68
|
+
results = with_attr_searchable_options(Product, :notice, :default => false) { Product.search "Expected" }
|
69
|
+
|
70
|
+
assert_includes results, product1
|
71
|
+
refute_includes results, product2
|
72
|
+
end
|
73
|
+
|
64
74
|
def test_count
|
65
75
|
create_list :product, 2, :title => "Expected"
|
66
76
|
|
67
77
|
assert_equal 2, Product.search("Expected").count
|
68
78
|
end
|
79
|
+
|
80
|
+
def test_default_searchable_attributes_true
|
81
|
+
with_attr_searchable_options(Product, :title, :default => true) do
|
82
|
+
with_attr_searchable_options(Product, :description, :default => true) do
|
83
|
+
assert_equal ["title", "description"], Product.default_searchable_attributes.keys
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_default_searchable_attributes_false
|
89
|
+
with_attr_searchable_options(Product, :title, :default => false) do
|
90
|
+
with_attr_searchable_options(Product, :description, :default => false) do
|
91
|
+
assert_equal Product.searchable_attributes.keys - ["title", "description"], Product.default_searchable_attributes.keys
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
69
95
|
end
|
70
96
|
|
data/test/date_test.rb
CHANGED
@@ -41,8 +41,8 @@ class DateTest < AttrSearchable::TestCase
|
|
41
41
|
def test_greater
|
42
42
|
product = create(:product, :created_on => Date.parse("2014-05-01"))
|
43
43
|
|
44
|
-
assert_includes Product.search("created_on
|
45
|
-
refute_includes Product.search("created_on
|
44
|
+
assert_includes Product.search("created_on > 2014-04-01"), product
|
45
|
+
refute_includes Product.search("created_on > 2014-05-01"), product
|
46
46
|
end
|
47
47
|
|
48
48
|
def test_greater_equals
|
data/test/datetime_test.rb
CHANGED
@@ -42,8 +42,8 @@ class DatetimeTest < AttrSearchable::TestCase
|
|
42
42
|
def test_greater
|
43
43
|
product = create(:product, :created_at => Time.parse("2014-05-01"))
|
44
44
|
|
45
|
-
assert_includes Product.search("created_at
|
46
|
-
refute_includes Product.search("created_at
|
45
|
+
assert_includes Product.search("created_at > 2014-04-01"), product
|
46
|
+
refute_includes Product.search("created_at > 2014-05-01"), product
|
47
47
|
end
|
48
48
|
|
49
49
|
def test_greater_equals
|
data/test/test_helper.rb
CHANGED
@@ -34,7 +34,7 @@ end
|
|
34
34
|
class Product < ActiveRecord::Base
|
35
35
|
include AttrSearchable
|
36
36
|
|
37
|
-
attr_searchable :title, :description, :brand, :stock, :price, :created_at, :created_on, :available
|
37
|
+
attr_searchable :title, :description, :brand, :notice, :stock, :price, :created_at, :created_on, :available
|
38
38
|
attr_searchable :comment => ["comments.title", "comments.message"], :user => ["users.username", "users_products.username"]
|
39
39
|
attr_searchable :primary => [:title, :description]
|
40
40
|
|
@@ -81,6 +81,7 @@ ActiveRecord::Base.connection.create_table :products do |t|
|
|
81
81
|
t.date :created_on
|
82
82
|
t.boolean :available
|
83
83
|
t.string :brand
|
84
|
+
t.string :notice
|
84
85
|
end
|
85
86
|
|
86
87
|
ActiveRecord::Base.connection.create_table :comments do |t|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: attr_searchable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-07-
|
12
|
+
date: 2014-07-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: treetop
|
@@ -123,7 +123,7 @@ dependencies:
|
|
123
123
|
- - ! '>='
|
124
124
|
- !ruby/object:Gem::Version
|
125
125
|
version: '0'
|
126
|
-
description: Search
|
126
|
+
description: Search engine like fulltext query support for ActiveRecord
|
127
127
|
email:
|
128
128
|
- vetter@flakks.com
|
129
129
|
executables: []
|
@@ -189,7 +189,7 @@ rubyforge_project:
|
|
189
189
|
rubygems_version: 1.8.23
|
190
190
|
signing_key:
|
191
191
|
specification_version: 3
|
192
|
-
summary: Easily perform complex search
|
192
|
+
summary: Easily perform complex search engine like fulltext queries on your ActiveRecord
|
193
193
|
models
|
194
194
|
test_files:
|
195
195
|
- test/and_test.rb
|