search_cop 1.0.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.
data/README.md ADDED
@@ -0,0 +1,530 @@
1
+ # SearchCop
2
+
3
+ [![Build Status](https://secure.travis-ci.org/mrkamel/search_cop.png?branch=master)](http://travis-ci.org/mrkamel/search_cop)
4
+ [![Code Climate](https://codeclimate.com/github/mrkamel/search_cop.png)](https://codeclimate.com/github/mrkamel/search_cop)
5
+ [![Dependency Status](https://gemnasium.com/mrkamel/search_cop.png?travis)](https://gemnasium.com/mrkamel/search_cop)
6
+ [![Gem Version](https://badge.fury.io/rb/search_cop.svg)](http://badge.fury.io/rb/search_cop)
7
+
8
+ ![search_cop](https://raw.githubusercontent.com/mrkamel/search_cop_logo/master/search_cop.png)
9
+
10
+ SearchCop extends your ActiveRecord models to support fulltext search
11
+ engine like queries via simple query strings and hash-based queries. Assume you
12
+ have a `Book` model having various attributes like `title`, `author`, `stock`,
13
+ `price`, `available`. Using SearchCop you can perform:
14
+
15
+ ```ruby
16
+ Book.search("Joanne Rowling Harry Potter")
17
+ Book.search("author: Rowling title:'Harry Potter'")
18
+ Book.search("price > 10 AND price < 20 -stock:0 (Potter OR Rowling)")
19
+ # ...
20
+ ```
21
+
22
+ Thus, you can hand out a search query string to your models and you, your app's
23
+ admins and/or users will get powerful query features without the need for
24
+ integrating additional third party search servers, since SearchCop can use
25
+ fulltext index capabilities of your RDBMS in a database agnostic way (currently
26
+ MySQL and PostgreSQL fulltext indices are supported) and optimizes the queries
27
+ to make optimal use of them. Read more below.
28
+
29
+ Complex hash-based queries are supported as well:
30
+
31
+ ```ruby
32
+ Book.search(:author => "Rowling", :title => "Harry Potter")
33
+ Book.search(:or => [{:author => "Rowling"}, {:author => "Tolkien"}])
34
+ Book.search(:and => [{:price => {:gt => 10}}, {:not => {:stock => 0}}, :or => [{:title => "Potter"}, {:author => "Rowling"}]])
35
+ Book.search(:or => [{:query => "Rowling -Potter"}, {:query => "Tolkien -Rings"}])
36
+ # ...
37
+ ```
38
+
39
+ ## AttrSearchable is now SearchCop
40
+
41
+ As the set of features of AttrSearchable grew and grew, it has been neccessary
42
+ to change its DSL and name, as no `attr_searchable` method is present anymore.
43
+ The new DSL is cleaner and more concise. Morever, the migration process is
44
+ simple. Please take a look into the migration guide
45
+ [MIGRATION.md](https://github.com/mrkamel/search_cop/MIGRATION.md)
46
+
47
+ ## Installation
48
+
49
+ For Rails/ActiveRecord 3 (or 4), add this line to your application's Gemfile:
50
+
51
+ gem 'search_cop'
52
+
53
+ And then execute:
54
+
55
+ $ bundle
56
+
57
+ Or install it yourself as:
58
+
59
+ $ gem install search_cop
60
+
61
+ ## Usage
62
+
63
+ To enable SearchCop for a model, `include SearchCop` and specify the
64
+ attributes you want to expose to search queries within a `search_scope`:
65
+
66
+ ```ruby
67
+ class Book < ActiveRecord::Base
68
+ include SearchCop
69
+
70
+ search_scope :search do
71
+ attributes :title, :description, :stock, :price, :created_at, :available
72
+ attributes :comment => ["comments.title", "comments.message"]
73
+ attributes :author => "author.name"
74
+ # ...
75
+ end
76
+
77
+ has_many :comments
78
+ belongs_to :author
79
+ end
80
+ ```
81
+
82
+ ## How does it work
83
+
84
+ SearchCop parses the query and maps it to an SQL Query using Arel.
85
+ Thus, SearchCop is not bound to a specific RDBMS.
86
+
87
+ ```ruby
88
+ Book.search("stock > 0")
89
+ # ... WHERE books.stock > 0
90
+
91
+ Book.search("price > 10 stock > 0")
92
+ # ... WHERE books.price > 10 AND books.stock > 0
93
+
94
+ Book.search("Harry Potter")
95
+ # ... WHERE (books.title LIKE '%Harry%' OR books.description LIKE '%Harry%' OR ...) AND (books.title LIKE '%Potter%' OR books.description LIKE '%Potter%' ...)
96
+
97
+ Book.search("available:yes OR created_at:2014")
98
+ # ... WHERE books.available = 1 OR (books.created_at >= '2014-01-01 00:00:00' and books.created_at <= '2014-12-31 00:00:00')
99
+ ```
100
+
101
+ Of course, these `LIKE '%...%'` queries won't achieve optimal performance, but
102
+ check out the section below on SearchCop's fulltext capabilities to
103
+ understand how the resulting queries can be optimized.
104
+
105
+ As `Book.search(...)` returns an `ActiveRecord::Relation`, you are free to pre-
106
+ or post-process the search results in every possible way:
107
+
108
+ ```ruby
109
+ Book.where(:available => true).search("Harry Potter").order("books.id desc").paginate(:page => params[:page])
110
+ ```
111
+
112
+ ## Fulltext index capabilities
113
+
114
+ By default, i.e. if you don't tell SearchCop about your fulltext indices,
115
+ SearchCop will use `LIKE '%...%'` queries. Unfortunately, unless you
116
+ create a [trigram index](http://www.postgresql.org/docs/9.1/static/pgtrgm.html)
117
+ (postgres only), theses queries can not use SQL indices, such that every row
118
+ needs to be scanned by your RDBMS when you search for `Book.search("Harry
119
+ Potter")` or similar. To avoid the penalty of `LIKE` queries, SearchCop
120
+ can exploit the fulltext index capabilities of MySQL and PostgreSQL. To use
121
+ already existing fulltext indices, simply tell SearchCop to use them via:
122
+
123
+ ```ruby
124
+ class Book < ActiveRecord::Base
125
+ # ...
126
+
127
+ search_scope :search do
128
+ attributes :title, :author
129
+
130
+ options :title, :type => :fulltext
131
+ options :author, :type => :fulltext
132
+ end
133
+
134
+ # ...
135
+ end
136
+ ```
137
+
138
+ SearchCop will then transparently change its SQL queries for the
139
+ attributes having fulltext indices to:
140
+
141
+ ```ruby
142
+ Book.search("Harry Potter")
143
+ # MySQL: ... WHERE (MATCH(books.title) AGAINST('+Harry' IN BOOLEAN MODE) OR MATCH(books.author) AGAINST('+Harry' IN BOOLEAN MODE)) AND (MATCH(books.title) AGAINST ('+Potter' IN BOOLEAN MODE) OR MATCH(books.author) AGAINST('+Potter' IN BOOLEAN MODE))
144
+ # PostgreSQL: ... WHERE (to_tsvector('simple', books.title) @@ to_tsquery('simple', 'Harry') OR to_tsvector('simple', books.author) @@ to_tsquery('simple', 'Harry')) AND (to_tsvector('simple', books.title) @@ to_tsquery('simple', 'Potter') OR to_tsvector('simple', books.author) @@ to_tsquery('simple', 'Potter'))
145
+ ```
146
+
147
+ Obviously, theses queries won't always return the same results as wildcard
148
+ `LIKE` queries, because we search for words instead of sub-strings. However,
149
+ fulltext indices will usually of course provide better performance.
150
+
151
+ Moreover, the query above is not yet perfect. To improve it even more,
152
+ SearchCop tries to optimize the queries to make optimal use of fulltext
153
+ indices while still allowing to mix them with non-fulltext attributes. To
154
+ improve queries even more, you can group attributes via aliases and specify a
155
+ default field to search in, such that SearchCop must no longer search
156
+ within all fields:
157
+
158
+ ```ruby
159
+ search_scope :search do
160
+ attributes :all => [:author, :title]
161
+
162
+ options :all, :type => :fulltext, :default => true
163
+
164
+ # Use :default => true to explicitly enable fields as default fields (whitelist approach)
165
+ # Use :default => false to explicitly disable fields as default fields (blacklist approach)
166
+ end
167
+ ```
168
+
169
+ Now SearchCop can optimize the following, not yet optimal query:
170
+
171
+ ```ruby
172
+ Book.search("Rowling OR Tolkien stock > 1")
173
+ # 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
174
+ # 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
175
+ ```
176
+
177
+ to the following, more performant query:
178
+
179
+
180
+ ```ruby
181
+ Book.search("Rowling OR Tolkien stock > 1")
182
+ # MySQL: ... WHERE MATCH(books.author, books.title) AGAINST('Rowling Tolkien' IN BOOLEAN MODE) AND books.stock > 1
183
+ # PostgreSQL: ... WHERE to_tsvector('simple', books.author || ' ' || books.title) @@ to_tsquery('simple', 'Rowling | Tokien') and books.stock > 1
184
+ ```
185
+
186
+ What is happening here? Well, we specified `all` as an alias that consists of
187
+ `author` and `title`. As we, in addition, specified `all` to be a fulltext
188
+ attribute, SearchCop assumes there is a compound fulltext index present on
189
+ `author` and `title`, such that the query is optimized accordingly. Finally, we
190
+ specified `all` to be the default attribute to search in, such that
191
+ SearchCop can ignore other attributes, like e.g. `stock`, as long as they
192
+ are not specified within queries directly (like for `stock > 0`).
193
+
194
+ Other queries will be optimized in a similar way, such that SearchCop
195
+ tries to minimize the fultext constraints within a query, namely `MATCH()
196
+ AGAINST()` for MySQL and `to_tsvector() @@ to_tsquery()` for PostgreSQL.
197
+
198
+ ```ruby
199
+ Book.search("(Rowling -Potter) OR Tolkien")
200
+ # MySQL: ... WHERE MATCH(books.author, books.title) AGAINST('(+Rowling -Potter) Tolkien' IN BOOLEAN MODE)
201
+ # PostgreSQL: ... WHERE to_tsvector('simple', books.author || ' ' || books.title) @@ to_tsquery('simple', '(Rowling & !Potter) | Tolkien')
202
+ ```
203
+
204
+ To create a fulltext index on `books.title` in MySQL, simply use:
205
+
206
+ ```ruby
207
+ add_index :books, :title, :type => :fulltext
208
+ ```
209
+
210
+ Regarding compound indices, which will e.g. be used for the default field `all`
211
+ we already specified above, use:
212
+
213
+ ```ruby
214
+ add_index :books, [:author, :title], :type => :fulltext
215
+ ```
216
+
217
+ Please note that MySQL supports fulltext indices for MyISAM and, as of MySQL
218
+ version 5.6+, for InnoDB as well. For more details about MySQL fulltext indices
219
+ visit
220
+ [http://dev.mysql.com/doc/refman/5.6/en/fulltext-search.html](http://dev.mysql.com/doc/refman/5.6/en/fulltext-search.html)
221
+
222
+ Regarding PostgreSQL there are more ways to create a fulltext index. However,
223
+ one of the easiest ways is:
224
+
225
+ ```ruby
226
+ ActiveRecord::Base.connection.execute "CREATE INDEX fulltext_index_books_on_title ON books USING GIN(to_tsvector('simple', title))"
227
+ ```
228
+
229
+ Regarding compound indices for PostgreSQL, use:
230
+
231
+ ```ruby
232
+ ActiveRecord::Base.connection.execute "CREATE INDEX fulltext_index_books_on_title ON books USING GIN(to_tsvector('simple', author || ' ' || title))"
233
+ ```
234
+
235
+ To use another PostgreSQL dictionary than `simple`, you have to create the
236
+ index accordingly and you need tell SearchCop about it, e.g.:
237
+
238
+ ```ruby
239
+ search_scope :search do
240
+ attributes :title
241
+
242
+ options :title, :type => :fulltext, :dictionary => "english"
243
+ end
244
+ ```
245
+
246
+ For more details about PostgreSQL fulltext indices visit
247
+ [http://www.postgresql.org/docs/9.3/static/textsearch.html](http://www.postgresql.org/docs/9.3/static/textsearch.html)
248
+
249
+ ## Other indices
250
+
251
+ In case you expose non-fulltext attributes to search queries (price, stock,
252
+ etc.), the respective queries, like `Book.search("stock > 0")`, will profit
253
+ from from the usual non-fulltext indices. Thus, you should add a usual index on
254
+ every column you expose to search queries plus a fulltext index for every
255
+ fulltext attribute.
256
+
257
+ In case you can't use fulltext indices, because you're e.g. still on MySQL 5.5
258
+ while using InnoDB or another RDBMS without fulltext support, you can make your
259
+ RDBMS use usual non-fulltext indices for string columns if you don't need the
260
+ left wildcard within `LIKE` queries. Simply supply the following option:
261
+
262
+ ```ruby
263
+ class User < ActiveRecord::Base
264
+ include SearchCop
265
+
266
+ search_scope :search do
267
+ attributes :username
268
+
269
+ options :username, :left_wildcard => false
270
+ end
271
+
272
+ # ...
273
+ ```
274
+
275
+ such that SearchCop will omit the left most wildcard.
276
+
277
+ ```ruby
278
+ User.search("admin")
279
+ # ... WHERE users.username LIKE 'admin%'
280
+ ```
281
+
282
+ ## Associations
283
+
284
+ If you specify searchable attributes from another model, like
285
+
286
+ ```ruby
287
+ class Book < ActiveRecord::Base
288
+ # ...
289
+
290
+ belongs_to :author
291
+
292
+ search_scope :search do
293
+ attributes :author => "author.name"
294
+ end
295
+
296
+ # ...
297
+ end
298
+ ```
299
+
300
+ SearchCop will by default `eager_load` the referenced associations, when
301
+ you perform `Book.search(...)`. If you don't want the automatic `eager_load`
302
+ or need to perform special operations, specify a `scope`:
303
+
304
+ ```ruby
305
+ class Book < ActiveRecord::Base
306
+ # ...
307
+
308
+ search_scope :search do
309
+ # ...
310
+
311
+ scope { joins(:author).eager_load(:comments) } # etc.
312
+ end
313
+
314
+ # ...
315
+ end
316
+ ```
317
+
318
+ SearchCop will then skip any association auto loading and will use the
319
+ scope instead. Assocations of associations can as well be referenced
320
+ and used:
321
+
322
+ ```ruby
323
+ class Book < ActiveRecord::Base
324
+ # ...
325
+
326
+ has_many :comments
327
+ has_many :users, :through => :comments
328
+
329
+ search_scope :search do
330
+ attributes :user => "users.username"
331
+ end
332
+
333
+ # ...
334
+ end
335
+ ```
336
+
337
+ ## Custom table names and associations
338
+
339
+ SearchCop tries to infer a model's class name and SQL alias from the
340
+ specified attributes to autodetect datatype definitions, etc. This usually
341
+ works quite fine. In case you're using custom table names via `self.table_name
342
+ = ...` or if a model is associated multiple times, SearchCop however can't
343
+ infer the class and alias names, e.g.
344
+
345
+ ```ruby
346
+ class Book < ActiveRecord::Base
347
+ # ...
348
+
349
+ has_many :users, :through => :comments
350
+ belongs_to :user
351
+
352
+ search_scope :search do
353
+ attributes :user => ["users.username", "users_books.username"]
354
+ end
355
+
356
+ # ...
357
+ end
358
+ ```
359
+
360
+ Here, for queries to work you have to use `users_books.username`, because
361
+ ActiveRecord assigns a different SQL alias for users within its SQL queries,
362
+ because the user model is associated multiple times. However, as SearchCop
363
+ now can't infer the `User` model from `users_books`, you have to add:
364
+
365
+ ```ruby
366
+ class Book < ActiveRecord::Base
367
+ # ...
368
+
369
+ search_scope :search do
370
+ # ...
371
+
372
+ aliases :users_books => :user
373
+ end
374
+
375
+ # ...
376
+ end
377
+ ```
378
+
379
+ to tell SearchCop about the custom SQL alias and mapping.
380
+
381
+ ## Supported operators
382
+
383
+ Query string queries support `AND/and`, `OR/or`, `:`, `=`, `!=`, `<`, `<=`,
384
+ `>`, `>=`, `NOT/not/-`, `()`, `"..."` and `'...'`. Default operators are `AND`
385
+ and `matches`, `OR` has precedence over `AND`. `NOT` can only be used as infix
386
+ operator regarding a single attribute.
387
+
388
+ Hash based queries support `:and => [...]` and `:or => [...]`, which take an array
389
+ of `:not => {...}`, `:matches => {...}`, `:eq => {...}`, `:not_eq => {...}`,
390
+ `:lt => {...}`, `:lteq => {...}`, `gt => {...}`, `:gteq => {...}` and `:query => "..."`
391
+ arguments. Moreover, `:query => "..."` makes it possible to create sub-queries.
392
+ The other rules for query string queries apply to hash based queries as well.
393
+
394
+ ## Mapping
395
+
396
+ When searching in boolean, datetime, timestamp, etc. fields, SearchCop
397
+ performs some mapping. The following queries are equivalent:
398
+
399
+ ```ruby
400
+ Book.search("available:true")
401
+ Book.search("available:1")
402
+ Book.search("available:yes")
403
+ ```
404
+
405
+ as well as
406
+
407
+ ```ruby
408
+ Book.search("available:false")
409
+ Book.search("available:0")
410
+ Book.search("available:no")
411
+ ```
412
+
413
+ For datetime and timestamp fields, SearchCop expands certain values to
414
+ ranges:
415
+
416
+ ```ruby
417
+ Book.search("created_at:2014")
418
+ # ... WHERE created_at >= '2014-01-01 00:00:00' AND created_at <= '2014-12-31 23:59:59'
419
+
420
+ Book.search("created_at:2014-06")
421
+ # ... WHERE created_at >= '2014-06-01 00:00:00' AND created_at <= '2014-06-30 23:59:59'
422
+
423
+ Book.search("created_at:2014-06-15")
424
+ # ... WHERE created_at >= '2014-06-15 00:00:00' AND created_at <= '2014-06-15 23:59:59'
425
+ ```
426
+
427
+ ## Chaining
428
+
429
+ Chaining of searches is possible. However, chaining does currently not allow
430
+ SearchCop to optimize the individual queries for fulltext indices.
431
+
432
+ ```ruby
433
+ Book.search("Harry").search("Potter")
434
+ ```
435
+
436
+ will generate
437
+
438
+ ```ruby
439
+ # MySQL: ... WHERE MATCH(...) AGAINST('+Harry' IN BOOLEAN MODE) AND MATCH(...) AGAINST('+Potter' IN BOOLEAN MODE)
440
+ # PostgreSQL: ... WHERE to_tsvector(...) @@ to_tsquery('simple', 'Harry') AND to_tsvector(...) @@ to_tsquery('simple', 'Potter')
441
+ ```
442
+
443
+ instead of
444
+
445
+ ```ruby
446
+ # MySQL: ... WHERE MATCH(...) AGAINST('+Harry +Potter' IN BOOLEAN MODE)
447
+ # PostgreSQL: ... WHERE to_tsvector(...) @@ to_tsquery('simple', 'Harry & Potter')
448
+ ```
449
+
450
+ Thus, if you use fulltext indices, you better avoid chaining.
451
+
452
+ ## Debugging
453
+
454
+ When using `Model#search`, SearchCop conveniently prevents certain
455
+ exceptions from being raised in case the query string passed to it is invalid
456
+ (parse errors, incompatible datatype errors, etc). Instead, `Model#search`
457
+ returns an empty relation. However, if you need to debug certain cases, use
458
+ `Model#unsafe_search`, which will raise them.
459
+
460
+ ```ruby
461
+ Book.unsafe_search("stock: None") # => raise SearchCop::IncompatibleDatatype
462
+ ```
463
+
464
+ ## Reflection
465
+
466
+ SearchCop provides reflective methods, namely `#attributes`,
467
+ `#default_attributes`, `#options` and `#aliases`. You can use these methods to
468
+ e.g. provide an individual search help widget for your models, that lists the
469
+ attributes to search in as well as the default ones, etc.
470
+
471
+ ```ruby
472
+ class Product < ActiveRecord::Base
473
+ include SearchCop
474
+
475
+ search_scope :search do
476
+ attributes :title, :description
477
+
478
+ options :title, :default => true
479
+ end
480
+ end
481
+
482
+ Product.search_reflection(:search).attributes
483
+ # {"title" => ["products.title"], "description" => ["products.description"]}
484
+
485
+ Product.search_reflection(:search).default_attributes
486
+ # {"title" => ["products.title"]}
487
+
488
+ # ...
489
+ ```
490
+
491
+ ## Contributing
492
+
493
+ 1. Fork it
494
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
495
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
496
+ 4. Push to the branch (`git push origin my-new-feature`)
497
+ 5. Create new Pull Request
498
+
499
+ ## Changelog
500
+
501
+ Version 1.0.0:
502
+
503
+ * Project name changed to SearchCop
504
+ * Scope support added
505
+ * Multiple DSL changes
506
+
507
+ Version 0.0.5:
508
+
509
+ * Supporting :default => false
510
+ * Datetime/Date greater operator fix
511
+ * Use reflection to find associated models
512
+ * Providing reflection
513
+
514
+ Version 0.0.4:
515
+
516
+ * Fixed date attributes
517
+ * Fail softly for mixed datatype attributes
518
+ * Support custom table, class and alias names via attr_searchable_alias
519
+
520
+ Version 0.0.3:
521
+
522
+ * belongs_to association fixes
523
+
524
+ Version 0.0.2:
525
+
526
+ * Arel abstraction layer added
527
+ * count() queries resulting in "Cannot visit AttrSearchableGrammar::Nodes..." fixed
528
+ * Better error messages
529
+ * Model#unsafe_search added
530
+
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "lib"
6
+ t.pattern = "test/**/*_test.rb"
7
+ t.verbose = true
8
+ end
9
+
@@ -0,0 +1,26 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 3.2.18"
6
+ gem "search_cop", :path => "../"
7
+
8
+ platforms :jruby do
9
+ gem "activerecord-jdbcmysql-adapter"
10
+ gem "activerecord-jdbcsqlite3-adapter"
11
+ gem "activerecord-jdbcpostgresql-adapter"
12
+ end
13
+
14
+ platforms :ruby do
15
+ gem "sqlite3"
16
+ gem "mysql2"
17
+ gem "pg"
18
+ end
19
+
20
+ platforms :rbx do
21
+ gem "racc"
22
+ gem "rubysl", "~> 2.0"
23
+ gem "psych"
24
+ end
25
+
26
+ gemspec :path => "../"
@@ -0,0 +1,26 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 4.0.0"
6
+ gem "search_cop", :path => "../"
7
+
8
+ platforms :jruby do
9
+ gem "activerecord-jdbcmysql-adapter"
10
+ gem "activerecord-jdbcsqlite3-adapter"
11
+ gem "activerecord-jdbcpostgresql-adapter"
12
+ end
13
+
14
+ platforms :ruby do
15
+ gem "sqlite3"
16
+ gem "mysql2"
17
+ gem "pg"
18
+ end
19
+
20
+ platforms :rbx do
21
+ gem "racc"
22
+ gem "rubysl", "~> 2.0"
23
+ gem "psych"
24
+ end
25
+
26
+ gemspec :path => "../"
@@ -0,0 +1,26 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 4.1.0.beta"
6
+ gem "search_cop", :path => "../"
7
+
8
+ platforms :jruby do
9
+ gem "activerecord-jdbcmysql-adapter"
10
+ gem "activerecord-jdbcsqlite3-adapter"
11
+ gem "activerecord-jdbcpostgresql-adapter"
12
+ end
13
+
14
+ platforms :ruby do
15
+ gem "sqlite3"
16
+ gem "mysql2"
17
+ gem "pg"
18
+ end
19
+
20
+ platforms :rbx do
21
+ gem "racc"
22
+ gem "rubysl", "~> 2.0"
23
+ gem "psych"
24
+ end
25
+
26
+ gemspec :path => "../"