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 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. Therefore, AttrSearchable can exploit the fulltext index
110
- capabilities of MySQL and PostgreSQL. To use already existing fulltext indices,
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
- BookSearch("Rowling OR Tolkien stock > 1")
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
- BookSearch("Rowling OR Tolkien stock > 1")
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
- BookSearch("(Rowling -Potter) OR Tolkien")
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
@@ -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-engine like fulltext query support for ActiveRecord}
12
- spec.summary = %q{Easily perform complex search-engine like fulltext queries on your ActiveRecord models}
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
 
@@ -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
@@ -1,3 +1,3 @@
1
1
  module AttrSearchable
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  end
@@ -84,10 +84,7 @@ module AttrSearchableGrammar
84
84
 
85
85
  class AnywhereExpression < BaseNode
86
86
  def evaluate
87
- keys = model.searchable_attribute_options.select { |key, value| value[:default] == true }.keys
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 test_custom_default
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
 
@@ -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 < 2014-05-02"), product
45
- refute_includes Product.search("created_on < 2014-05-01"), product
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
@@ -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 < 2014-05-02"), product
46
- refute_includes Product.search("created_at < 2014-05-01"), product
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
@@ -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
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-05 00:00:00.000000000 Z
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-engine like fulltext query support for ActiveRecord
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-engine like fulltext queries on your ActiveRecord
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