attr_searchable 0.0.4 → 0.0.5

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.
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