search_magic 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +44 -27
- data/README.markdown +397 -0
- data/lib/search_magic.rb +2 -0
- data/lib/search_magic/breadcrumb.rb +1 -1
- data/lib/search_magic/full_text_search.rb +11 -4
- data/lib/search_magic/metadata.rb +14 -2
- data/lib/search_magic/version.rb +1 -1
- data/search_magic.gemspec +2 -0
- data/spec/fabricators/developer_fabricator.rb +4 -0
- data/spec/fabricators/field_skip_prefix_fabricator.rb +3 -0
- data/spec/models/field_skip_prefix.rb +7 -0
- data/spec/models/game.rb +1 -1
- data/spec/models/model_with_field_types.rb +22 -0
- data/spec/unit/search_magic/arrangements_spec.rb +2 -2
- data/spec/unit/search_magic/breadcrumb_spec.rb +33 -0
- data/spec/unit/search_magic/configuration_spec.rb +28 -0
- data/spec/unit/search_magic/date_parsing_spec.rb +15 -0
- data/spec/unit/search_magic/fields_spec.rb +31 -0
- data/spec/unit/search_magic/metadata_spec.rb +23 -0
- data/spec/unit/search_magic/metadata_type_spec.rb +19 -0
- data/spec/unit/search_magic/simple_inclusion_model_spec.rb +2 -0
- data/spec/unit/search_magic/stack_frame_spec.rb +28 -0
- metadata +106 -71
- data/README.textile +0 -178
data/Gemfile.lock
CHANGED
@@ -1,41 +1,57 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
search_magic (0.
|
4
|
+
search_magic (0.2.0)
|
5
|
+
chronic
|
5
6
|
mongoid (>= 2.0.0)
|
6
7
|
|
7
8
|
GEM
|
8
9
|
remote: http://rubygems.org/
|
9
10
|
specs:
|
10
|
-
activemodel (3.
|
11
|
-
activesupport (= 3.
|
12
|
-
builder (~>
|
13
|
-
i18n (~> 0.
|
14
|
-
activesupport (3.
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
11
|
+
activemodel (3.1.3)
|
12
|
+
activesupport (= 3.1.3)
|
13
|
+
builder (~> 3.0.0)
|
14
|
+
i18n (~> 0.6)
|
15
|
+
activesupport (3.1.3)
|
16
|
+
multi_json (~> 1.0)
|
17
|
+
archive-tar-minitar (0.5.2)
|
18
|
+
bson (1.5.1)
|
19
|
+
bson_ext (1.5.1)
|
20
|
+
builder (3.0.0)
|
21
|
+
chronic (0.6.6)
|
22
|
+
columnize (0.3.5)
|
23
|
+
database_cleaner (0.7.0)
|
24
|
+
diff-lcs (1.1.3)
|
25
|
+
fabrication (1.2.0)
|
26
|
+
i18n (0.6.0)
|
27
|
+
linecache19 (0.5.12)
|
28
|
+
ruby_core_source (>= 0.1.4)
|
29
|
+
mongo (1.5.1)
|
30
|
+
bson (= 1.5.1)
|
31
|
+
mongoid (2.3.4)
|
32
|
+
activemodel (~> 3.1)
|
26
33
|
mongo (~> 1.3)
|
27
34
|
tzinfo (~> 0.3.22)
|
28
|
-
|
29
|
-
rspec (2.
|
30
|
-
rspec-core (~> 2.
|
31
|
-
rspec-expectations (~> 2.
|
32
|
-
rspec-mocks (~> 2.
|
33
|
-
rspec-core (2.
|
34
|
-
rspec-expectations (2.
|
35
|
+
multi_json (1.0.4)
|
36
|
+
rspec (2.7.0)
|
37
|
+
rspec-core (~> 2.7.0)
|
38
|
+
rspec-expectations (~> 2.7.0)
|
39
|
+
rspec-mocks (~> 2.7.0)
|
40
|
+
rspec-core (2.7.1)
|
41
|
+
rspec-expectations (2.7.0)
|
35
42
|
diff-lcs (~> 1.1.2)
|
36
|
-
rspec-mocks (2.
|
37
|
-
|
38
|
-
|
43
|
+
rspec-mocks (2.7.0)
|
44
|
+
ruby-debug-base19 (0.11.25)
|
45
|
+
columnize (>= 0.3.1)
|
46
|
+
linecache19 (>= 0.5.11)
|
47
|
+
ruby_core_source (>= 0.1.4)
|
48
|
+
ruby-debug19 (0.11.6)
|
49
|
+
columnize (>= 0.3.1)
|
50
|
+
linecache19 (>= 0.5.11)
|
51
|
+
ruby-debug-base19 (>= 0.11.19)
|
52
|
+
ruby_core_source (0.1.5)
|
53
|
+
archive-tar-minitar (>= 0.5.2)
|
54
|
+
tzinfo (0.3.31)
|
39
55
|
|
40
56
|
PLATFORMS
|
41
57
|
ruby
|
@@ -45,4 +61,5 @@ DEPENDENCIES
|
|
45
61
|
database_cleaner
|
46
62
|
fabrication
|
47
63
|
rspec
|
64
|
+
ruby-debug19
|
48
65
|
search_magic!
|
data/README.markdown
ADDED
@@ -0,0 +1,397 @@
|
|
1
|
+
# SearchMagic
|
2
|
+
|
3
|
+
SearchMagic provides full-text search capabilities to [mongoid](http://github.com/mongoid/mongoid) documents, embedded documents, and referenced documents with a clean, consistent, and easy to use syntax. Documents specify which data they want to expose as searchable; users can then search for these documents in either a broad or in a targeted manner.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
SearchMagic is built on top of mongoid; in all likelihood, it will only work with versions greater than or equal to *2.0.0*. The project can be installed as a gem on a target system:
|
8
|
+
|
9
|
+
```
|
10
|
+
gem install search_magic
|
11
|
+
```
|
12
|
+
|
13
|
+
For environments where bundler is being used, it can be installed by adding the following to your Gemfile and running `bundle`.
|
14
|
+
|
15
|
+
```
|
16
|
+
gem 'search_magic'
|
17
|
+
```
|
18
|
+
|
19
|
+
Please check the [rubygems project](http://rubygems.org/gems/search_magic) for the current version.
|
20
|
+
|
21
|
+
## Getting Started
|
22
|
+
|
23
|
+
### Making a document searchable
|
24
|
+
|
25
|
+
Got a document you want to add full text search capabilities to? Great! A few simple steps are all that are needed to get you up and running.
|
26
|
+
|
27
|
+
1. Include the **SearchMagic** module into your document!
|
28
|
+
2. Tell SearchMagic about the data within your document you want to be searchable.
|
29
|
+
3. ????
|
30
|
+
4. Profit!
|
31
|
+
|
32
|
+
Let's say we have a document for storing addresses in the database. The model might look a little something like the following:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
class Address
|
36
|
+
include Mongoid::Document
|
37
|
+
field :street
|
38
|
+
field :city
|
39
|
+
field :state
|
40
|
+
field :postal_code
|
41
|
+
embedded_in :addressable, polymorphic: true
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
This boring little document is currently not searchable. However, with a splash of magic, we can make this document yield up its secrets to anyone who cares search for them.
|
46
|
+
|
47
|
+
First up: include the **SearchMagic** module:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
class Address
|
51
|
+
include Mongoid::Document
|
52
|
+
include SearchMagic
|
53
|
+
#...
|
54
|
+
end
|
55
|
+
```
|
56
|
+
|
57
|
+
This will extend the document with a small suite of utility methods and capabilities, which will be covered later on. For now, the important thing to note is that merely adding in the module will not actually make anything searchable. Full text search is an opt-in process, which means that you have complete control over which data you expose to prying eyes.
|
58
|
+
|
59
|
+
So how do we ask the gem to work its magic? By asking it to search_on particular fields within the document!
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
class Address
|
63
|
+
#...
|
64
|
+
search_on :street
|
65
|
+
search_on :city
|
66
|
+
search_on :state
|
67
|
+
search_on :postal_code
|
68
|
+
end
|
69
|
+
```
|
70
|
+
|
71
|
+
We'll meet search_on a bit latter; just know for now that it is the method you call to register fields with the gem. In the previous example, we made each of the four fields of the document searchable. The great news is that the exact same process is used for virtual properties and associations.
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
class Address
|
75
|
+
#...
|
76
|
+
def random_letter
|
77
|
+
('a'..'z').sample
|
78
|
+
end
|
79
|
+
|
80
|
+
search_on :addressable
|
81
|
+
search_on :random_letter
|
82
|
+
end
|
83
|
+
```
|
84
|
+
|
85
|
+
As long as it is invokable as an instance method on the document, SearchMagic can make it searchable.
|
86
|
+
|
87
|
+
### Example searches on a searchable document
|
88
|
+
|
89
|
+
Sure, marking up your data to make it searchable through SearchMagic is simply *fascinating*, but how do we go about actually searching the documents? Through one of the utility methods SearchMagic bundles in: search_for.
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
Address.search_for("Los Angeles CA")
|
93
|
+
```
|
94
|
+
|
95
|
+
The preceding example is search at its most simplistic: trawling over the addresses, returning any which happen to have the values "Los", "Angeles", and "CA" somewhere within their searchable fields. Simplistic? Why, yes! SearchMagic also allows any search term within the query pattern to target any specific searchable field on that document. For example, what if we wanted to ensure that the text, "CA", only matched values coming from the :state field? Simple!
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
Address.search_for("state:CA")
|
99
|
+
```
|
100
|
+
|
101
|
+
That is, any term within the query pattern can specify a **selector** to specify which field you want to limit that term to. You can mix and match simple and complex terms within a single query: SearchMagic thrives on that sort of thing.
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
Address.search_for("Los Angeles state:CA")
|
105
|
+
```
|
106
|
+
|
107
|
+
It is also possible to have multiple terms target the same field (as long as that makes semantic sense). The result set returned by SearchMagic will contain all documents which have every term within it, be it simple or complex.
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
Address.search_for("city:Los city:Angeles state:CA")
|
111
|
+
# Or, equivalently...
|
112
|
+
Address.search_for("city:'Los Angeles' state:CA")
|
113
|
+
```
|
114
|
+
|
115
|
+
As the previous example also shows, SearchMagic supports a shorthand notation for combining multiple terms together which are targeted to a specific field. Note that nothing is guaranteed about the contiguity of the terms when searched like this; the shorthand simply makes it a bit easier to find documents which have all the terms listed.
|
116
|
+
|
117
|
+
Finally, it should be noted that all searches performed by SearchMagic are case-insensitive. Any of the previous examples could just as easily have been written with any mixture of cases, either for the selectors or for the values.
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
Address.search_for("city:'Los Angeles' state:CA")
|
121
|
+
# Is the same as:
|
122
|
+
Address.search_for("city:'los angeles' state:ca")
|
123
|
+
```
|
124
|
+
|
125
|
+
To sum, if you want to search for data across a document, use the *search_for* method! That's what it is there for!
|
126
|
+
|
127
|
+
### Arranging data
|
128
|
+
|
129
|
+
Want to arrange your documents after you have searched for them? Look no further than the provided **arrange** method!
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
Address.search_for("state:ca").arrange(:city)
|
133
|
+
```
|
134
|
+
|
135
|
+
Arrange currently can take up to two parameters; the first specifies which searchable field you want to order the result set by, while the second specifies the direction of the ordering. (This defaults to ascending.)
|
136
|
+
|
137
|
+
Mongoid already comes with a mechanism for [ordering documents](http://mongoid.org/docs/querying/criteria.html#order_by), so why does SearchMagic provide its own variant? Well, **arrange** is actually built on top of order_by. What it brings to the table is the ability to sort documents based off of any of the searchable fields a document can see --- including those coming from virtual attributes and associations.
|
138
|
+
|
139
|
+
We'll revisit this topic in more detail after looking at how associations work.
|
140
|
+
|
141
|
+
### Querying docs about their searchables
|
142
|
+
|
143
|
+
SearchMagic provides some utility methods which can be used to find out information about a document's searchable fields:
|
144
|
+
|
145
|
+
1. **searchables**: a class method containing a hash of metadata pertaining to all searchable fields;
|
146
|
+
2. **searchable_values**: an instance method containing an array of all values the document can be found by.
|
147
|
+
3. **arrangeable_values**: an instance method containing a hash of all values the document can be sorted by.
|
148
|
+
|
149
|
+
For the suggested standard usage of the gem, the first method might only be interesting for the keys of the hash it stores:
|
150
|
+
|
151
|
+
```ruby
|
152
|
+
Address.searchables.keys # => [:street, :city, :state, :postal_code]
|
153
|
+
```
|
154
|
+
|
155
|
+
This could be useful for ensuring that some text value you are dealing with is actually a searchable. For example, if you wanted to support sortable columns for a searchable document in a controller within a Rails app, you could use the keys from the searchables hash to ensure you are not passing anything wonky to **arrange**.
|
156
|
+
|
157
|
+
The second method is mentioned mostly to bring to attention the potential cost of SearchMagic: to ensure that searching is speedy and straight-forward, all data marked as searchable is replicated in a marked-up format within the searchable document. **search_for** constructs its criteria by referencing this particular array. If that type of data replication is undesirable, SearchMagic might not be the best choice for full text search. Please note that searchable_values is automatically maintained by the owning document: whenever the document is saved, a callback is invoked which updates the values. So, under normal usage circumstances, you should not be touching this array, and manually updating it is right out.
|
158
|
+
|
159
|
+
Similar caveats exist for **arrangeable_values**. Its main purpose is to enable **arrange** to perform sorting in a speedy, straight-forward fashion.
|
160
|
+
|
161
|
+
## Global Configuration
|
162
|
+
|
163
|
+
For the most part, configuration options are local to the documents and fields they are defined within. However, there are a few global options which are used across models, which can be altered through global configuration. These options (okay, for right now, "option", as there is only one) can be accessed through SearchMagic's **config** hash:
|
164
|
+
|
165
|
+
```ruby
|
166
|
+
SearchMagic.config # for global options!
|
167
|
+
```
|
168
|
+
|
169
|
+
Unless otherwise specified, for a Rails environment, it is suggested that these options be set in an initializer.
|
170
|
+
|
171
|
+
### :selector_value_separator
|
172
|
+
|
173
|
+
SearchMagic stores data in **searchable_values** --- and eventually searches for data from the same location --- by marking up values with the field from which they originated. While slightly more complicated, this mark-up is essentially defined as:
|
174
|
+
|
175
|
+
```ruby
|
176
|
+
"#{field_path}#{separator}#{value}"
|
177
|
+
```
|
178
|
+
|
179
|
+
The default separator is a colon, ":", as is rather obvious from the examples shown elsewhere in this document. This can be changed through the **selector_value_separator** configuration option to whatever makes sense for your use case:
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
SearchMagic.config.selector_value_separator = '/'
|
183
|
+
address = Address.search_for("state/ca").first
|
184
|
+
address.searchable_values # [..., "city/los", "city/angeles", "state/ca", ...]
|
185
|
+
```
|
186
|
+
|
187
|
+
Note that **search_for** will immediately use the new separator value after a change is made to the configuration. However, no results may be returned, as pre-existing documents will still be using the previously defined separator. To force an update to your models, just re-save each one, and the **searchable_values** should be updated.
|
188
|
+
|
189
|
+
Setting **selector_value_separator** to **nil** results in the same behavior as setting it to ':'.
|
190
|
+
|
191
|
+
## A little more depth...
|
192
|
+
|
193
|
+
### Search Graph
|
194
|
+
|
195
|
+
Many of the examples provided to this point have been rather boring, focusing as they have on a single document; yet, there have been a few allusions to being able to use SearchMagic to perform full text searches across associations between documents. How about we finally get around to expanding upon that concept, and showcase some simple (though, hopefully, useful!) examples.
|
196
|
+
|
197
|
+
SearchMagic builds a hash of all searchable fields for a document; this is done the first time that the **searchables** method is called. (Don't worry about having to manually call it; saving documents automatically does this as does searching.) The gem constructs an in memory representation of a subset of the document graph that exists for the document it is currently processing; this subset, called the *search graph*, is synonymous with the searchables array. It represents the list of fields that are reachable from the current document, along with a trail of breadcrumbs SearchMagic needs to follow to access those fields. Generally, that is not *terribly* important to be aware of. It is important to be aware that this process of building the searchables treats associations in a special way: when specifying that you want to search on an association, all of the associated document's searchables are automatically added to the first document. Let's illustrate this with an example, yeah?
|
198
|
+
|
199
|
+
```ruby
|
200
|
+
class Game
|
201
|
+
include Mongoid::Document
|
202
|
+
include SearchMagic
|
203
|
+
field :title, :type => String
|
204
|
+
field :price, :type => Float
|
205
|
+
field :high_score, :type => Integer
|
206
|
+
field :released_on, :type => Date
|
207
|
+
references_and_referenced_in_many :players
|
208
|
+
referenced_in :developer
|
209
|
+
|
210
|
+
search_on :title
|
211
|
+
search_on :price
|
212
|
+
search_on :high_score
|
213
|
+
search_on :developer
|
214
|
+
end
|
215
|
+
|
216
|
+
class Developer
|
217
|
+
include Mongoid::Document
|
218
|
+
include SearchMagic
|
219
|
+
field :name
|
220
|
+
field :net_worth
|
221
|
+
field :opened_on, :type => Date
|
222
|
+
references_many :games
|
223
|
+
|
224
|
+
search_on :name
|
225
|
+
search_on :opened_on
|
226
|
+
end
|
227
|
+
```
|
228
|
+
|
229
|
+
Here we have two documents which are in a referential relationship with one another. As can be seen from the first document, Game is requesting that it have the ability to search for instances of its documents by their title, their price, their high score, and their developer. But wait, **developer** is an association! So, what does that mean for Game? By what is it really searchable? Let's take a look at its searchables to find out:
|
230
|
+
|
231
|
+
```ruby
|
232
|
+
Game.searchables.keys # [:title, :price, :high_score, :developer_name, :developer_opened_on]
|
233
|
+
```
|
234
|
+
|
235
|
+
See how the searchables from Developer were automatically added to Game? Only those fields which are within Developer's search graph will be subsumed into Game. Notice how there is a field and an association within Document which are not being searched on? Those are not added to Game's searchables.
|
236
|
+
|
237
|
+
Take another look at Game's searchables. Notice the way that fields coming from an association are handled? They are receive a prefix equivalent to the name of the association. This allows SearchMagic to build rather complex search graphs without having to necessarily worry about weird aliasing issues. The values coming from these fields will be stored within each Game instance's **searchable_values** with this prefixed name, and **search_for** will likewise be expecting the use of the prefixed names.
|
238
|
+
|
239
|
+
```ruby
|
240
|
+
game = Game.search_for("developer_name:bethesda title:skyrim").first
|
241
|
+
game.searchable_values # [..., "developer_name:bethesda", "title:skyrim", ...]
|
242
|
+
```
|
243
|
+
|
244
|
+
One level of searching is pretty neat. But what if you want to search deeply across your document graph? Let's extend this section's example be adding another document:
|
245
|
+
|
246
|
+
```ruby
|
247
|
+
class Player
|
248
|
+
include Mongoid::Document
|
249
|
+
include SearchMagic::FullTextSearch
|
250
|
+
field :name
|
251
|
+
references_and_referenced_in_many :games
|
252
|
+
|
253
|
+
search_on :name
|
254
|
+
search_on :games
|
255
|
+
end
|
256
|
+
```
|
257
|
+
|
258
|
+
Here, we have Player documents which can search on games. Player's searchables would look like the following:
|
259
|
+
|
260
|
+
```ruby
|
261
|
+
Player.searchables.keys # [:name, :game_title, :game_price, :game_high_score, :game_developer_name, :game_developer_opened_on]
|
262
|
+
```
|
263
|
+
|
264
|
+
There are three important things to note about this:
|
265
|
+
|
266
|
+
1. Player subsumed all of Game's searchables, including everything coming from Developer.
|
267
|
+
2. All of the searchables coming from **games** are prefixed with the singular form of the association name.
|
268
|
+
3. This includes even those searchables which are, themselves, representative of an association.
|
269
|
+
|
270
|
+
SearchMagic should be able to handle as complex of a document graph as you care to throw at it. (*SearchMagic and its developer are not liable for computer's exploding while attempting to process crazy large document graphs.*) While the running example is fairly linear, you are not limited to simple paths like this: you can have search graphs which are as broad and deep as you like. Which leads us to the next topic.
|
271
|
+
|
272
|
+
#### Cyclic Searches
|
273
|
+
|
274
|
+
What happens when we have two documents which, either directly or indirectly, end up searching on each other? Let's modify one of the documents from the last section and see what happens:
|
275
|
+
|
276
|
+
```ruby
|
277
|
+
class Developer
|
278
|
+
include Mongoid::Document
|
279
|
+
include SearchMagic
|
280
|
+
field :name
|
281
|
+
field :net_worth
|
282
|
+
field :opened_on, :type => Date
|
283
|
+
references_many :games
|
284
|
+
|
285
|
+
search_on :name
|
286
|
+
search_on :opened_on
|
287
|
+
search_on :games
|
288
|
+
end
|
289
|
+
```
|
290
|
+
|
291
|
+
Alright, so Developer now searches on games. We have formed a direct cyclic search: Game and Developer search on each other. What do their searchables look like?
|
292
|
+
|
293
|
+
```ruby
|
294
|
+
Game.searchables.keys # [:title, :price, :high_score, :developer_name, :developer_opened_on]
|
295
|
+
Developer.searchables.keys # [:name, :opened_on, :game_title, :game_price, :game_high_score]
|
296
|
+
```
|
297
|
+
|
298
|
+
As you can see, Game remains the same, while Developer gains a few extra searchable fields. The process which determines the search graph for a document is smart enough to keep track of previously visited documents within the graph; when such a cycle is detected, the revisited document is effectively ignored. This means that a given document will always only contribute its fields once to the searchables of another document.
|
299
|
+
|
300
|
+
### arrange
|
301
|
+
|
302
|
+
Now that we've explored the search graph, its time to revisit arranging data. Any values coming from a document's searchables are replicated in a hash within that document: its **arrangeable_values**. Will slightly costly, when it comes to replicating data across a document hierarchy, it provides a very clever trick: it allows a document to be sorted by any of its searchables, regardless of where they come from. It matters not whether the searchable is a field on the defining document or on an association. It matters not whether a field is coming from a referenced or an embedded document. All are welcome, and all are handled exactly the same.
|
303
|
+
|
304
|
+
Using the running examples from the last sections, let's explore some example usage:
|
305
|
+
|
306
|
+
```ruby
|
307
|
+
Game.arrange(:title) # Sort games based off of their title
|
308
|
+
Game.arrange(:developer_name) # Sort games based off the name of their developer
|
309
|
+
Developer.arrange(:name) # Sort developers based off of their name
|
310
|
+
Developer.arrange(:game_title) # Sort developers based off the names of their games
|
311
|
+
```
|
312
|
+
|
313
|
+
This method is a [mongoid scope](http://mongoid.org/docs/querying/scopes.html); it will always return a [criteria](http://mongoid.org/docs/querying/criteria.html) object after executing. As such, it plays nicely with other scopes on your models.
|
314
|
+
|
315
|
+
### search_on
|
316
|
+
|
317
|
+
As described earlier in the document, fields are marked as searchable through use of the **search_on** class method, which takes one required parameter and a set of options. The required parameter specifies a method on the document which returns a value to be searched on. Mostly. The options allow for certain aspects of the library's default behavior to be overridden according to taste. The first parameter is also used by SearchMagic as the field name by which values in **searchable_values** are marked-up and searched by.
|
318
|
+
|
319
|
+
Options currently supported by search_on are:
|
320
|
+
|
321
|
+
1. **as**: specifies a text value to override the default field name behavior; this allows a field to masquerade as something else when its slumming about your interface:
|
322
|
+
|
323
|
+
```ruby
|
324
|
+
search_on :postal_code, :as => :zip_code
|
325
|
+
```
|
326
|
+
|
327
|
+
In the preceding example, the field specified by **postal_code** will be represented as **zip_code** within **searchable_values**.
|
328
|
+
2. **keep_punctuation**: by default, all punctuation for a field is automatically replaced with whitespace when values are stored in **searchable_values**; this can be turned off for a particular field by turning on **keep_punctuation**.
|
329
|
+
|
330
|
+
```ruby
|
331
|
+
search_on :postal_code, :keep_punctuation => false # e.g. ["postal_code:12345", "postal_code:6789"]
|
332
|
+
search_on :postal_code, :keep_punctuation => true # e.g. ["postal_code:12345-6789"]
|
333
|
+
```
|
334
|
+
3. **skip_prefix**: as described previously, fields bubbling up the search graph are prefixed with extra information. This prefix is the name of the association as seen by the class defining the search. So, for example:
|
335
|
+
|
336
|
+
```ruby
|
337
|
+
class Foo
|
338
|
+
#...
|
339
|
+
field :name
|
340
|
+
embeds_many :bars
|
341
|
+
|
342
|
+
search_on :name
|
343
|
+
search_on :bars
|
344
|
+
end
|
345
|
+
|
346
|
+
class Bar
|
347
|
+
#...
|
348
|
+
field :name
|
349
|
+
|
350
|
+
search_on :name
|
351
|
+
end
|
352
|
+
|
353
|
+
Foo.searchables.keys # [..., :name, :bar_name, ...]
|
354
|
+
```
|
355
|
+
|
356
|
+
Which is to say, that **Foo** has a searchable on one of its local fields, and a searchable on a field coming from **Bar**. This second searchable is automatically prefixed with "bar_". This prefix, for only this level of the search graph, can be skipped through use of **skip_prefix**. If the above exampled were modified like so:
|
357
|
+
|
358
|
+
```ruby
|
359
|
+
search_on :bars, :skip_prefix => true
|
360
|
+
```
|
361
|
+
|
362
|
+
Then anything coming through **bars** will not have any prefix. (This particular example is bad; don't do it! It would result in two searchables with the field name of "name", which defeats the awesome of SearchMagic.) Note that **skip_prefix** takes precedence over **as**; the two cannot be used together in any meaningful sense. Also, this option is really only intended for use on associations; regular fields should not have prefixes skipped.
|
363
|
+
4. **only** / **except**: when building its search graph, SearchMagic will automatically include any searchable fields from an associated document into the current document. The fields that are included can be controlled through the **only** and **except** options. Only specifies that only the specified fields are gobbled up; except specifies that everything but the specified fields are included. These two parameters can be used concurrently, although that would be somewhat strange. They both can take either a single field name or an array of field names.
|
364
|
+
|
365
|
+
```ruby
|
366
|
+
search_on :bars, :only => :name
|
367
|
+
search_on :bars, :except => [:name, ...]
|
368
|
+
```
|
369
|
+
|
370
|
+
### search_for
|
371
|
+
|
372
|
+
Searching a model with SearchMagic is simple: each model gains a class method called ***search_for*** which accepts one parameter, the search pattern. Like **arrange**, this method is a mongoid scope, and will always return a criteria object.
|
373
|
+
|
374
|
+
SearchMagic expects the incoming *pattern* to be a string containing whitespace delimited phrases. Each phrase can either be a single word, or a *selector:value* pair. Multiple phrases will narrow the search field: each additional phrase places an additional requirement on a matching document. Single word phrases are matched across all entries in a model’s ***searchable_values*** array. The pairs, on the other hand, restrict the search for *value* against only those entries which match *selector*. In either case, *word* or *value* may contain fragments of whole entries stored within ***searchable_values***.
|
375
|
+
|
376
|
+
***search_for*** can be called multiple times within the same scope chain. Doing so will append each successive pattern to the previous searches. Effectively, this is the same as performing a single ***search_for*** with whitespace delimited terms in the pattern. To make such expressions slightly more readable, ***search_for*** is aliased as ***and_for***:
|
377
|
+
|
378
|
+
```ruby
|
379
|
+
Game.search_for("developer_name:bethesda").and_for("title:skyrim") # is functionally equivalent to...
|
380
|
+
Game.search_for("developer_name:bethesda title:skyrim")
|
381
|
+
```
|
382
|
+
|
383
|
+
#### Natural language date processing via chronic
|
384
|
+
|
385
|
+
SearchMagic handles fields which represent temporal data in a special way: anything which is datable can be searched for based off of a (relatively) natural language syntax, provided by [chronic](https://github.com/mojombo/chronic). If ***search_for*** determines that the searchable field being searched for is datable, it will pass *value* to chronic to get a date/time representation. This representation is then converted to text and used for the comparison against ***searchable_values***.
|
386
|
+
|
387
|
+
```ruby
|
388
|
+
Developer.search_for("opened_on:'1 year ago'")
|
389
|
+
Developer.search_for("opened_on:'January 1986'")
|
390
|
+
```
|
391
|
+
|
392
|
+
Note, in order for the natural language processing to be invoked properly, the *value* part of a search must be wrapped in quotes; multiple occurrences of a datable searchable will be processed separately, rather than as a unit.
|
393
|
+
|
394
|
+
## Problems? Comments?
|
395
|
+
|
396
|
+
Feel free to add an [issue on GitHub](search_magic/issues) or fork the project and send a pull request.
|
397
|
+
I’m always looking for new ways of bending hardware to my will, so suggestions are welcome.
|