attr_searchable 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -104,28 +104,9 @@ AttrSearchable will use `LIKE '%...%'` queries. Unfortunately, unless you
104
104
  create a [trigram index](http://www.postgresql.org/docs/9.1/static/pgtrgm.html)
105
105
  (postgres only), theses queries can not use SQL indices, such that every row
106
106
  needs to be scanned by your RDBMS when you search for `Book.search("Harry
107
- Potter")` or similar, which is btw. usually ok for small data sets and a small
108
- amount of regular queries. Contrary, when you search for
109
- `Book.search("title=Potter")` indices can and will be used. Moreover, other
110
- indices (on price, stock, etc) will of course be used by your RDBMS when you
111
- search for `Book.search("stock > 0")`, etc.
112
-
113
- Regarding the `LIKE` penalty, the easiest way to make them use indices is
114
- to remove the left wildcard. AttrSearchble supports this via:
115
-
116
- ```ruby
117
- class Book < ActiveRecord::Base
118
- # ...
119
-
120
- attr_searchable_options, :title, :left_wildcard => false
121
-
122
- # ...
123
- end
124
- ```
125
-
126
- However, this is often not desirable. Therefore, AttrSearchable can exploit the
127
- fulltext index capabilities of MySQL and PostgreSQL. To use already existing
128
- fulltext indices, simply tell AttrSearchable to use them via:
107
+ Potter")` or similar. Therefore, AttrSearchable can exploit the fulltext index
108
+ capabilities of MySQL and PostgreSQL. To use already existing fulltext indices,
109
+ simply tell AttrSearchable to use them via:
129
110
 
130
111
  ```ruby
131
112
  class Book < ActiveRecord::Base
@@ -148,7 +129,7 @@ Book.search("Harry Potter")
148
129
  ```
149
130
 
150
131
  Obviously, theses queries won't always return the same results as wildcard
151
- `LIKE` queries, because we search for words instead of substrings. However,
132
+ `LIKE` queries, because we search for words instead of sub-strings. However,
152
133
  fulltext indices will usually of course provide better performance.
153
134
 
154
135
  Moreover, the query above is not yet perfect. To improve it even more,
@@ -229,6 +210,14 @@ attr_searchable_options :title, :dictionary => "english"
229
210
  For more details about PostgreSQL fulltext indices visit
230
211
  [http://www.postgresql.org/docs/9.3/static/textsearch.html](http://www.postgresql.org/docs/9.3/static/textsearch.html)
231
212
 
213
+ ## Other indices
214
+
215
+ In case you expose non-fulltext attributes to search queries (price, stock,
216
+ etc.), the respective queries, like `Book.search("stock > 0")`, will profit
217
+ from from the usual non-fulltext indices. Thus, you should add a usual index on
218
+ every column you expose to search queries plus a fulltext index for every
219
+ fulltext attribute.
220
+
232
221
  ## Associations
233
222
 
234
223
  If you specify searchable attributes from another model, like
@@ -237,14 +226,32 @@ If you specify searchable attributes from another model, like
237
226
  class Book < ActiveRecord::Base
238
227
  # ...
239
228
 
229
+ belongs_to :author
230
+
240
231
  attr_searchable :author => "author.name"
241
232
 
242
233
  # ...
243
234
  end
244
235
  ```
245
236
 
246
- AttrSearchable will by default `eager_load` these associations, when you
247
- perform `Book.search(...)`. If you don't want that or need to perform special
237
+ AttrSearchable will by default `eager_load` the referenced associations, when
238
+ you perform `Book.search(...)`. Assocations of associations can thus as well be
239
+ referenced and used:
240
+
241
+ ```ruby
242
+ class Book < ActiveRecord::Base
243
+ # ...
244
+
245
+ has_many :comments
246
+ has_many :users, :through => :comments
247
+
248
+ attr_searchable :user => "users.username"
249
+
250
+ # ...
251
+ end
252
+ ```
253
+
254
+ If you don't want the automatic `eager_load` or need to perform special
248
255
  operations, define a `search_scope` within your model:
249
256
 
250
257
  ```ruby
@@ -333,9 +340,11 @@ Thus, if you use fulltext indices, you better avoid chaining.
333
340
 
334
341
  ## Debugging
335
342
 
336
- AttrSearchable conveniently hides certain errors, like parse errors, and
337
- instead returns an empty relation. However, if you need to debug certain
338
- cases, use `Model#unsafe_search`, which will raise them.
343
+ When using `Model#search`, AttrSearchable conveniently prevents certain
344
+ exceptions from being raised in case the query string passed to it is invalid
345
+ (parse errors, incompatible datatype errors, etc). Instead, `Model#search`
346
+ returns an empty relation. However, if you need to debug certain cases, use
347
+ `Model#unsafe_search`, which will raise them.
339
348
 
340
349
  ```ruby
341
350
  Book.unsafe_search("stock: None") # => raise AttrSearchable::IncompatibleDatatype
@@ -348,3 +357,17 @@ Book.unsafe_search("stock: None") # => raise AttrSearchable::IncompatibleDatatyp
348
357
  3. Commit your changes (`git commit -am 'Add some feature'`)
349
358
  4. Push to the branch (`git push origin my-new-feature`)
350
359
  5. Create new Pull Request
360
+
361
+ ## Changelog
362
+
363
+ Version 0.0.3:
364
+
365
+ * belongs_to association fixes
366
+
367
+ Version 0.0.2:
368
+
369
+ * Arel abstraction layer added
370
+ * count() queries resulting in "Cannot visit AttrSearchableGrammar::Nodes..." fixed
371
+ * Better error messages
372
+ * Model#unsafe_search added
373
+
@@ -53,9 +53,9 @@ module AttrSearchable
53
53
  def attr_searchable_hash(hash)
54
54
  hash.each do |key, value|
55
55
  self.searchable_attributes[key.to_s] = Array(value).collect do |column|
56
- table, attribute = column.to_s =~ /\./ ? column.to_s.split(".") : [name, column]
56
+ table, attribute = column.to_s =~ /\./ ? column.to_s.split(".") : [name.tableize, column]
57
57
 
58
- "#{table.tableize}.#{attribute}"
58
+ "#{table}.#{attribute}"
59
59
  end
60
60
  end
61
61
  end
@@ -1,3 +1,3 @@
1
1
  module AttrSearchable
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
@@ -2,7 +2,7 @@
2
2
  require File.expand_path("../test_helper", __FILE__)
3
3
 
4
4
  class AttrSearchableTest < AttrSearchable::TestCase
5
- def test_associations
5
+ def test_multi_associations
6
6
  product = create(:product, :comments => [
7
7
  create(:comment, :title => "Title1", :message => "Message1"),
8
8
  create(:comment, :title => "Title2", :message => "Message2")
@@ -12,6 +12,26 @@ class AttrSearchableTest < AttrSearchable::TestCase
12
12
  assert_includes Product.search("comment: Title2 comment: Message2"), product
13
13
  end
14
14
 
15
+ def test_single_association
16
+ expected = create(:comment, :user => create(:user, :username => "Expected"))
17
+ rejected = create(:comment, :user => create(:user, :username => "Rejected"))
18
+
19
+ results = Comment.search("user: Expected")
20
+
21
+ assert_includes results, expected
22
+ refute_includes results, rejected
23
+ end
24
+
25
+ def test_deep_associations
26
+ expected = create(:product, :comments => [create(:comment, :user => create(:user, :username => "Expected"))])
27
+ rejected = create(:product, :comments => [create(:comment, :user => create(:user, :username => "Rejected"))])
28
+
29
+ results = Product.search("user: Expected")
30
+
31
+ assert_includes results, expected
32
+ refute_includes results, rejected
33
+ end
34
+
15
35
  def test_multiple
16
36
  product = create(:product, :comments => [create(:comment, :title => "Title", :message => "Message")])
17
37
 
data/test/test_helper.rb CHANGED
@@ -20,13 +20,22 @@ DATABASE = ENV["DATABASE"] || "sqlite"
20
20
 
21
21
  ActiveRecord::Base.establish_connection YAML.load_file(File.expand_path("../database.yml", __FILE__))[DATABASE]
22
22
 
23
- class Comment < ActiveRecord::Base; end
23
+ class User < ActiveRecord::Base; end
24
+
25
+ class Comment < ActiveRecord::Base
26
+ include AttrSearchable
27
+
28
+ belongs_to :user
29
+
30
+ attr_searchable :user => "user.username"
31
+ attr_searchable :title, :message
32
+ end
24
33
 
25
34
  class Product < ActiveRecord::Base
26
35
  include AttrSearchable
27
36
 
28
37
  attr_searchable :title, :description, :brand, :stock, :price, :created_at, :available
29
- attr_searchable :comment => ["comments.title", "comments.message"]
38
+ attr_searchable :comment => ["comments.title", "comments.message"], :user => "users.username"
30
39
 
31
40
  attr_searchable :primary => [:title, :description]
32
41
 
@@ -41,6 +50,7 @@ class Product < ActiveRecord::Base
41
50
  end
42
51
 
43
52
  has_many :comments
53
+ has_many :users, :through => :comments
44
54
  end
45
55
 
46
56
  FactoryGirl.define do
@@ -49,10 +59,14 @@ FactoryGirl.define do
49
59
 
50
60
  factory :comment do
51
61
  end
62
+
63
+ factory :user do
64
+ end
52
65
  end
53
66
 
54
67
  ActiveRecord::Base.connection.execute "DROP TABLE IF EXISTS products"
55
68
  ActiveRecord::Base.connection.execute "DROP TABLE IF EXISTS comments"
69
+ ActiveRecord::Base.connection.execute "DROP TABLE IF EXISTS users"
56
70
 
57
71
  ActiveRecord::Base.connection.create_table :products do |t|
58
72
  t.string :title
@@ -66,10 +80,15 @@ end
66
80
 
67
81
  ActiveRecord::Base.connection.create_table :comments do |t|
68
82
  t.references :product
83
+ t.references :user
69
84
  t.string :title
70
85
  t.text :message
71
86
  end
72
87
 
88
+ ActiveRecord::Base.connection.create_table :users do |t|
89
+ t.string :username
90
+ end
91
+
73
92
  if DATABASE == "mysql"
74
93
  ActiveRecord::Base.connection.execute "ALTER TABLE products ENGINE=MyISAM"
75
94
  ActiveRecord::Base.connection.execute "ALTER TABLE products ADD FULLTEXT INDEX(title), ADD FULLTEXT INDEX(description), ADD FULLTEXT INDEX(title, description)"
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.2
4
+ version: 0.0.3
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-06-21 00:00:00.000000000 Z
12
+ date: 2014-06-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: treetop