minidusen 0.7.0
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.
- checksums.yaml +7 -0
- data/.gitignore +7 -0
- data/.rspec +2 -0
- data/.ruby-version +2 -0
- data/.travis.yml +30 -0
- data/LICENSE +22 -0
- data/README.md +209 -0
- data/Rakefile +42 -0
- data/doc/filtered_index_view.cropped.png +0 -0
- data/gemfiles/Gemfile.3.2.mysql2 +10 -0
- data/gemfiles/Gemfile.3.2.mysql2.lock +59 -0
- data/gemfiles/Gemfile.4.2.mysql2 +9 -0
- data/gemfiles/Gemfile.4.2.mysql2.lock +63 -0
- data/gemfiles/Gemfile.4.2.pg +9 -0
- data/gemfiles/Gemfile.4.2.pg.lock +63 -0
- data/gemfiles/Gemfile.5.0.mysql2 +9 -0
- data/gemfiles/Gemfile.5.0.mysql2.lock +60 -0
- data/gemfiles/Gemfile.5.0.pg +9 -0
- data/gemfiles/Gemfile.5.0.pg.lock +60 -0
- data/lib/minidusen.rb +12 -0
- data/lib/minidusen/active_record_ext.rb +39 -0
- data/lib/minidusen/filter.rb +31 -0
- data/lib/minidusen/parser.rb +35 -0
- data/lib/minidusen/query.rb +54 -0
- data/lib/minidusen/syntax.rb +54 -0
- data/lib/minidusen/token.rb +25 -0
- data/lib/minidusen/util.rb +55 -0
- data/lib/minidusen/version.rb +3 -0
- data/minidusen.gemspec +23 -0
- data/spec/minidusen/active_record_ext_spec.rb +45 -0
- data/spec/minidusen/filter_spec.rb +196 -0
- data/spec/minidusen/parser_spec.rb +22 -0
- data/spec/minidusen/query_spec.rb +18 -0
- data/spec/minidusen/util_spec.rb +3 -0
- data/spec/spec_helper.rb +29 -0
- data/spec/support/database.rb +50 -0
- data/spec/support/database.sample.yml +10 -0
- data/spec/support/database.travis.yml +9 -0
- data/spec/support/models.rb +81 -0
- metadata +123 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 21b9abdca5644bbe2c9d7266988f8a0a22927435
|
4
|
+
data.tar.gz: 8fde451ba5ee361aa2c7ee3377ac195edd641d0e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fbd3c16faed08b0ec651898c609f78da008688701513dbfa372f495327540d14d283f031d9723d75a8425069499aab945129ee794b0bd1aa85699bc9bcd36e6c
|
7
|
+
data.tar.gz: fc869ed7bd8b21f1babd9c433bf6c5c31383188a6d9672e3a43680591bde2989dbd61ce4678d9ff525a1ef2d266935fd3da245a288eb3b110ba0c00846d01812
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-version
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
language: ruby
|
2
|
+
rvm:
|
3
|
+
- "2.1.8"
|
4
|
+
- "2.3.1"
|
5
|
+
gemfile:
|
6
|
+
- gemfiles/Gemfile.3.2.mysql2
|
7
|
+
- gemfiles/Gemfile.4.2.mysql2
|
8
|
+
- gemfiles/Gemfile.4.2.pg
|
9
|
+
- gemfiles/Gemfile.5.0.mysql2
|
10
|
+
- gemfiles/Gemfile.5.0.pg
|
11
|
+
before_script:
|
12
|
+
- psql -c 'create database minidusen_test;' -U postgres
|
13
|
+
- mysql -e 'create database IF NOT EXISTS minidusen_test;'
|
14
|
+
script: bundle exec rspec spec
|
15
|
+
sudo: false
|
16
|
+
cache: bundler
|
17
|
+
notifications:
|
18
|
+
email:
|
19
|
+
- fail@makandra.de
|
20
|
+
branches:
|
21
|
+
only:
|
22
|
+
- master
|
23
|
+
matrix:
|
24
|
+
exclude:
|
25
|
+
- rvm: "2.3.1"
|
26
|
+
gemfile: gemfiles/Gemfile.3.2.mysql2
|
27
|
+
- rvm: "2.1.8"
|
28
|
+
gemfile: gemfiles/Gemfile.5.0.mysql2
|
29
|
+
- rvm: "2.1.8"
|
30
|
+
gemfile: gemfiles/Gemfile.5.0.pg
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Henning Koch
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,209 @@
|
|
1
|
+
Minidusen [](https://travis-ci.org/makandra/minidusen)
|
2
|
+
=========
|
3
|
+
|
4
|
+
Low-tech search solution for ActiveRecord with MySQL or PostgreSQL
|
5
|
+
------------------------------------------------------------------
|
6
|
+
|
7
|
+
Minidusen lets you filter ActiveRecord models with a single query string.
|
8
|
+
It works with your existing MySQL or PostgreSQL schema by mostly relying on simple `LIKE` queries. No additional indexes, tables or indexing databases are required.
|
9
|
+
|
10
|
+
Minidusen accepts a single, Google-like query string and converts it into `WHERE` conditions for [an ActiveRecord scope](http://guides.rubyonrails.org/active_record_querying.html#conditions).
|
11
|
+
|
12
|
+
The following type of queries are supported:
|
13
|
+
|
14
|
+
- `foo` (case-insensitive search for `foo` in developer-defined columns)
|
15
|
+
- `foo bar` (rows must include both `foo` and `bar`)
|
16
|
+
- `"foo bar"` (rows must include the phrase `"foo bar"`)
|
17
|
+
- `-bar` (rows must not include the word `bar`)
|
18
|
+
- `filetype:pdf` (developer-defined filter for file type)
|
19
|
+
- `some words 'a phrase' filetype:pdf -excluded -'excluded phrase' -filetype:pdf` (combination of the above)
|
20
|
+
|
21
|
+
Minidusen is a quick way to implement find-as-you-type filters for index views:
|
22
|
+
|
23
|
+

|
24
|
+
|
25
|
+
We have found it to scale well for many thousand records. It's probably not a good idea to use Minidusen for hundreds of thousands of records or very long text columns. For this we recommend to use PostgreSQL with [pg_search](https://github.com/Casecommons/pg_search) or full-text databases like [Solr](https://github.com/sunspot/sunspot).
|
26
|
+
|
27
|
+
|
28
|
+
Installation
|
29
|
+
------------
|
30
|
+
|
31
|
+
In your `Gemfile` say:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
gem 'minidusen'
|
35
|
+
```
|
36
|
+
|
37
|
+
Now run `bundle install` and restart your server.
|
38
|
+
|
39
|
+
|
40
|
+
Basic Usage
|
41
|
+
-----------
|
42
|
+
|
43
|
+
Our example will be a simple address book:
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
class Contact < ActiveRecord::Base
|
47
|
+
validates_presence_of :name, :street, :city, :email
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
51
|
+
We create a new class `ContactFilter` that will describe the searchable columns:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
class ContactFilter
|
55
|
+
include Minidusen::Filter
|
56
|
+
|
57
|
+
filter :text do |scope, phrases|
|
58
|
+
columns = [:name, :email]
|
59
|
+
scope.where_like(columns => phrases)
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
We can now use `ContactFilter` to filter a scope of `Contact` records:
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
# We start by building a scope of all contacts.
|
69
|
+
# No SQL query is made.
|
70
|
+
all_contacts = Contact.all
|
71
|
+
# => ActiveRecord::Relation
|
72
|
+
|
73
|
+
# Now we filter the scope to only contain contacts with "gmail" in either :name or :email column.
|
74
|
+
# Again, no SQL query is made.
|
75
|
+
gmail_contacts = ContactFilter.new.filter(all_contacts, 'gmail')
|
76
|
+
# => ActiveRecord::Relation
|
77
|
+
|
78
|
+
# Inspect the filtered scope.
|
79
|
+
gmail_contacts.to_sql
|
80
|
+
# => "SELECT * FROM contacts WHERE name LIKE '%gmail%' OR email LIKE '%gmail%'"
|
81
|
+
|
82
|
+
# Finally we load the scope to produce an array of Contact records.
|
83
|
+
gmail_contacts.to_a
|
84
|
+
# => Array
|
85
|
+
```
|
86
|
+
|
87
|
+
### Filtering scopes with existing conditions
|
88
|
+
|
89
|
+
Note that you can also pass a scope with existing conditions to `ContactFilter#filter`. The returned scope will contain both the existing conditions and the conditions from the filter:
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
published_contacts = Contact.where(published: true)
|
93
|
+
# => ActiveRecord::Relation
|
94
|
+
|
95
|
+
published_contacts.to_sql
|
96
|
+
# => "SELECT * FROM contacts WHERE (published = 1)"
|
97
|
+
|
98
|
+
gmail_contacts = ContactFilter.new.filter(published_contacts, 'gmail')
|
99
|
+
# => ActiveRecord::Relation
|
100
|
+
|
101
|
+
gmail_contacts.to_sql
|
102
|
+
# => "SELECT * FROM contacts WHERE (published = 1) AND (name LIKE '%gmail%' OR email LIKE '%gmail%')"
|
103
|
+
```
|
104
|
+
|
105
|
+
### How `where_like` works
|
106
|
+
|
107
|
+
The example above uses `where_like`. You can call `where_like` on any scope to produce a new scope where the given array of column names must contain all of the given phrases.
|
108
|
+
|
109
|
+
Let's say we call `ContactFilter.new.filter(Contact.published, 'foo "bar baz" bam')`. This will call the block `filter :text do |scope, phrases|` with the following arguments:
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
scope == Contact.published
|
113
|
+
phrases == ['foo', 'bar baz', 'bam']
|
114
|
+
```
|
115
|
+
|
116
|
+
The scope `scope.where_like(columns => phrases)` will now represent the following SQL query:
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
SELECT * FROM contacts
|
120
|
+
WHERE (name LIKE "%foo%" OR email LIKE "%foo") AND (email LIKE "%foo%" OR email LIKE "%foo")
|
121
|
+
```
|
122
|
+
|
123
|
+
You can also use `where_like` to find all the records *not* matching some phrases, using the `:negate` option:
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
Contact.where_like(name: 'foo', negate: true)
|
127
|
+
```
|
128
|
+
|
129
|
+
Processing queries for qualified fields
|
130
|
+
---------------------------------------
|
131
|
+
|
132
|
+
Google supports queries like `filetype:pdf` that filters records by some criteria without performing a full text search. Minidusen gives you a simple way to support such search syntax.
|
133
|
+
|
134
|
+
Let's support a query like `email:foo@bar.com` to explictly search for a contact's email address, without filtering against other columns.
|
135
|
+
|
136
|
+
We can learn this syntax by adding a `filter:email` instruction
|
137
|
+
to our `ContactFilter` class`:
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
class ContactFilter
|
141
|
+
include Minidusen::Filter
|
142
|
+
|
143
|
+
search_by :email do |scope, email|
|
144
|
+
scope.where(emai: email)
|
145
|
+
end
|
146
|
+
|
147
|
+
search_by :text do |scope, phrases|
|
148
|
+
columns = [:name, :email]
|
149
|
+
scope.where_like(columns => phrases)
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
```
|
154
|
+
|
155
|
+
We can now explicitly search for a user's e-mail address:
|
156
|
+
|
157
|
+
```ruby
|
158
|
+
ContactFilter.new.filter(Contact, 'email:foo@bar.com').to_sql
|
159
|
+
# => "SELECT * FROM contacts WHERE email='foo@bar.com'"
|
160
|
+
```
|
161
|
+
|
162
|
+
### Caveat
|
163
|
+
|
164
|
+
If you search for a phrase containing a colon (e.g. `deploy:rollback`), Minidusen will mistake the first part as a – nonexistent – qualifier and return an empty set.
|
165
|
+
|
166
|
+
To prevent that, search for a phrase:
|
167
|
+
|
168
|
+
"deploy:rollback"
|
169
|
+
|
170
|
+
|
171
|
+
Supported Rails versions
|
172
|
+
------------------------
|
173
|
+
|
174
|
+
Minidusen is tested on:
|
175
|
+
|
176
|
+
- Rails 3.2
|
177
|
+
- Rails 4.2
|
178
|
+
- Rails 5.0
|
179
|
+
- MySQL 5.6
|
180
|
+
- PostgreSQL
|
181
|
+
|
182
|
+
If you need support for platforms not listed above, please submit a PR!
|
183
|
+
|
184
|
+
|
185
|
+
Development
|
186
|
+
-----------
|
187
|
+
|
188
|
+
- There are tests in `spec`. We only accept PRs with tests.
|
189
|
+
- We currently develop using Ruby 2.2.4 (see `.ruby-version`) since that version works for all versions of ActiveRecord that we support.
|
190
|
+
- TravisCI will test additional Ruby versions (2.1.8 and 2.3.1)
|
191
|
+
- Put your database credentials into `spec/support/database.yml`. There's a `database.sample.yml` you can use as a template.
|
192
|
+
- Create a database `minidusen_test` in both MySQL and PostgreSQL.
|
193
|
+
- There are gem bundles in `gemfiles` for each combination of ActiveRecord version and database type that we support.
|
194
|
+
- You can bundle all test applications by saying `bundle exec rake all:install`
|
195
|
+
- You can run specs from the project root by saying `bundle exec rake all:spec`.
|
196
|
+
|
197
|
+
If you would like to contribute:
|
198
|
+
|
199
|
+
- Fork the repository.
|
200
|
+
- Push your changes **with passing specs**.
|
201
|
+
- Send me a pull request.
|
202
|
+
|
203
|
+
Note that we're very eager to keep this gem lightweight. If you're unsure whether a change would make it into the gem, [open an issue](https://github.com/makandra/minidusen/issues/new).
|
204
|
+
|
205
|
+
|
206
|
+
Credits
|
207
|
+
-------
|
208
|
+
|
209
|
+
Henning Koch from [makandra](http://makandra.com/)
|
data/Rakefile
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'bundler/gem_tasks'
|
3
|
+
|
4
|
+
desc 'Default: Run all specs.'
|
5
|
+
task :default => 'all:spec'
|
6
|
+
|
7
|
+
namespace :all do
|
8
|
+
|
9
|
+
desc "Run specs on all versions"
|
10
|
+
task :spec do
|
11
|
+
success = true
|
12
|
+
for_each_gemfile do
|
13
|
+
success &= system("bundle exec rspec spec")
|
14
|
+
end
|
15
|
+
fail "Tests failed" unless success
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "Bundle all versions"
|
19
|
+
task :install do
|
20
|
+
for_each_gemfile do
|
21
|
+
system('bundle install')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
desc "Update all versions"
|
26
|
+
task :update do
|
27
|
+
for_each_gemfile do
|
28
|
+
system('bundle update')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
def for_each_gemfile
|
35
|
+
version = ENV['VERSION'] || '*'
|
36
|
+
Dir["gemfiles/Gemfile.#{version}"].sort.each do |gemfile|
|
37
|
+
next if gemfile =~ /.lock/
|
38
|
+
puts '', "\033[44m#{gemfile}\033[0m", ''
|
39
|
+
ENV['BUNDLE_GEMFILE'] = gemfile
|
40
|
+
yield
|
41
|
+
end
|
42
|
+
end
|
Binary file
|
@@ -0,0 +1,59 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ..
|
3
|
+
specs:
|
4
|
+
minidusen (0.7.0)
|
5
|
+
activerecord (>= 3.2)
|
6
|
+
activesupport (>= 3.2)
|
7
|
+
edge_rider (>= 0.2.5)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
activemodel (3.2.22)
|
13
|
+
activesupport (= 3.2.22)
|
14
|
+
builder (~> 3.0.0)
|
15
|
+
activerecord (3.2.22)
|
16
|
+
activemodel (= 3.2.22)
|
17
|
+
activesupport (= 3.2.22)
|
18
|
+
arel (~> 3.0.2)
|
19
|
+
tzinfo (~> 0.3.29)
|
20
|
+
activesupport (3.2.22)
|
21
|
+
i18n (~> 0.6, >= 0.6.4)
|
22
|
+
multi_json (~> 1.0)
|
23
|
+
arel (3.0.3)
|
24
|
+
builder (3.0.4)
|
25
|
+
byebug (8.2.0)
|
26
|
+
diff-lcs (1.2.5)
|
27
|
+
edge_rider (0.3.1)
|
28
|
+
activerecord
|
29
|
+
i18n (0.6.11)
|
30
|
+
multi_json (1.11.2)
|
31
|
+
mysql2 (0.3.17)
|
32
|
+
rspec (3.4.0)
|
33
|
+
rspec-core (~> 3.4.0)
|
34
|
+
rspec-expectations (~> 3.4.0)
|
35
|
+
rspec-mocks (~> 3.4.0)
|
36
|
+
rspec-core (3.4.1)
|
37
|
+
rspec-support (~> 3.4.0)
|
38
|
+
rspec-expectations (3.4.0)
|
39
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
40
|
+
rspec-support (~> 3.4.0)
|
41
|
+
rspec-mocks (3.4.1)
|
42
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
43
|
+
rspec-support (~> 3.4.0)
|
44
|
+
rspec-support (3.4.1)
|
45
|
+
tzinfo (0.3.46)
|
46
|
+
|
47
|
+
PLATFORMS
|
48
|
+
ruby
|
49
|
+
|
50
|
+
DEPENDENCIES
|
51
|
+
activerecord (= 3.2.22)
|
52
|
+
byebug
|
53
|
+
i18n (= 0.6.11)
|
54
|
+
minidusen!
|
55
|
+
mysql2 (= 0.3.17)
|
56
|
+
rspec (~> 3.4)
|
57
|
+
|
58
|
+
BUNDLED WITH
|
59
|
+
1.12.5
|
@@ -0,0 +1,63 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ..
|
3
|
+
specs:
|
4
|
+
minidusen (0.7.0)
|
5
|
+
activerecord (>= 3.2)
|
6
|
+
activesupport (>= 3.2)
|
7
|
+
edge_rider (>= 0.2.5)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
activemodel (4.2.5)
|
13
|
+
activesupport (= 4.2.5)
|
14
|
+
builder (~> 3.1)
|
15
|
+
activerecord (4.2.5)
|
16
|
+
activemodel (= 4.2.5)
|
17
|
+
activesupport (= 4.2.5)
|
18
|
+
arel (~> 6.0)
|
19
|
+
activesupport (4.2.5)
|
20
|
+
i18n (~> 0.7)
|
21
|
+
json (~> 1.7, >= 1.7.7)
|
22
|
+
minitest (~> 5.1)
|
23
|
+
thread_safe (~> 0.3, >= 0.3.4)
|
24
|
+
tzinfo (~> 1.1)
|
25
|
+
arel (6.0.3)
|
26
|
+
builder (3.2.2)
|
27
|
+
byebug (8.2.0)
|
28
|
+
diff-lcs (1.2.5)
|
29
|
+
edge_rider (0.3.1)
|
30
|
+
activerecord
|
31
|
+
i18n (0.7.0)
|
32
|
+
json (1.8.3)
|
33
|
+
minitest (5.8.3)
|
34
|
+
mysql2 (0.3.20)
|
35
|
+
rspec (3.4.0)
|
36
|
+
rspec-core (~> 3.4.0)
|
37
|
+
rspec-expectations (~> 3.4.0)
|
38
|
+
rspec-mocks (~> 3.4.0)
|
39
|
+
rspec-core (3.4.1)
|
40
|
+
rspec-support (~> 3.4.0)
|
41
|
+
rspec-expectations (3.4.0)
|
42
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
43
|
+
rspec-support (~> 3.4.0)
|
44
|
+
rspec-mocks (3.4.1)
|
45
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
46
|
+
rspec-support (~> 3.4.0)
|
47
|
+
rspec-support (3.4.1)
|
48
|
+
thread_safe (0.3.5)
|
49
|
+
tzinfo (1.2.2)
|
50
|
+
thread_safe (~> 0.1)
|
51
|
+
|
52
|
+
PLATFORMS
|
53
|
+
ruby
|
54
|
+
|
55
|
+
DEPENDENCIES
|
56
|
+
activerecord (~> 4.2.1)
|
57
|
+
byebug
|
58
|
+
minidusen!
|
59
|
+
mysql2 (~> 0.3.17)
|
60
|
+
rspec (~> 3.4)
|
61
|
+
|
62
|
+
BUNDLED WITH
|
63
|
+
1.12.5
|