stretchie 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +24 -0
- data/.rspec +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +187 -0
- data/Rakefile +4 -0
- data/lib/stretchie/pants.rb +219 -0
- data/lib/stretchie/version.rb +3 -0
- data/lib/stretchie.rb +107 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/stretchie_pants_spec.rb +225 -0
- data/spec/stretchie_spec.rb +83 -0
- data/spec/support/elastic_search.rb +9 -0
- data/spec/support/model.rb +50 -0
- data/stretchie.gemspec +31 -0
- metadata +199 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 027f752423a87c8f0e75f9d8b137b0a1d3542bed
|
4
|
+
data.tar.gz: be1f1ca84334a7b2a7fdc0456d0263b27d9529ec
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8362eaead32327b7a0c4c6c1e282bbaa566e364c695b954aec94207695ac1e453f3f0add51895b3251a9bec09300cfbed2bdcf20b0e6d79d8549e330d5b0fc0d
|
7
|
+
data.tar.gz: bac222dde6a7d2cc3406661ec3c3dc63d579173a1b6f820e45506a267b881e0acb38572d51e62203d58a25921e7b3a5a64d50d02e598b77be0dd86b9194dac43
|
data/.gitignore
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
23
|
+
.DS_Store
|
24
|
+
|
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Adam Bregenzer
|
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,187 @@
|
|
1
|
+
Stretchie
|
2
|
+
=========
|
3
|
+
|
4
|
+
Comfortable searching pants for ActiveRecord Models. Stretchie simplifies
|
5
|
+
using elastic search in your models and provides hooks to ease testing.
|
6
|
+
|
7
|
+
|
8
|
+
Defining Indices
|
9
|
+
----------------
|
10
|
+
|
11
|
+
Stretchie simply builds on elasticsearch-model, allowing you to pull the
|
12
|
+
details out into a concern.
|
13
|
+
|
14
|
+
With a model like this:
|
15
|
+
```ruby
|
16
|
+
class User < ActiveRecord::Base
|
17
|
+
attr_accessor :name, :email, :misc_id
|
18
|
+
end
|
19
|
+
```
|
20
|
+
|
21
|
+
Your concern could look like this:
|
22
|
+
```ruby
|
23
|
+
module Search
|
24
|
+
module User
|
25
|
+
extend ActiveSupport::Concern
|
26
|
+
include Stretchie::Pants
|
27
|
+
|
28
|
+
included do
|
29
|
+
settings index: { number_of_shards: 1 } do
|
30
|
+
mappings dynamic: 'false' do
|
31
|
+
indexes :name, analyzer: 'whitespace', index_options: 'offsets'
|
32
|
+
indexes :email
|
33
|
+
indexes :misc_id
|
34
|
+
indexes :sortable_name
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def as_indexed_json(options={})
|
40
|
+
json = as_json(only: [:name, :email, :misc_id])
|
41
|
+
json['sortable_name'] = self.name.downcase
|
42
|
+
json
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
```
|
47
|
+
|
48
|
+
And you would add this to your model:
|
49
|
+
```ruby
|
50
|
+
include Search::User
|
51
|
+
```
|
52
|
+
|
53
|
+
If you have liked models, you can have them re-index automatically with `index_dependent_models`:
|
54
|
+
```ruby
|
55
|
+
class User < ActiveRecord::Base
|
56
|
+
include Search::User
|
57
|
+
attr_accessor :name, :email
|
58
|
+
has_may :tags
|
59
|
+
end
|
60
|
+
|
61
|
+
class Tag < ActiveRecord::Base
|
62
|
+
include Search::Tag
|
63
|
+
attr_accessor :name
|
64
|
+
|
65
|
+
belongs_to :user
|
66
|
+
end
|
67
|
+
|
68
|
+
module Search
|
69
|
+
module Tag
|
70
|
+
extend ActiveSupport::Concern
|
71
|
+
include Stretchie::Pants
|
72
|
+
|
73
|
+
included do
|
74
|
+
settings index: { number_of_shards: 1 } do
|
75
|
+
mappings dynamic: 'false' do
|
76
|
+
indexes :name, analyzer: 'whitespace', index_options: 'offsets'
|
77
|
+
indexes :sortable_name
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def as_indexed_json(options={})
|
83
|
+
json = as_json(only: [:name])
|
84
|
+
json['sortable_name'] = self.name.downcase
|
85
|
+
json
|
86
|
+
end
|
87
|
+
|
88
|
+
def index_dependent_models
|
89
|
+
self.users
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
```
|
94
|
+
|
95
|
+
|
96
|
+
Maintaining Indices
|
97
|
+
-------------------
|
98
|
+
|
99
|
+
Create / Update your indices:
|
100
|
+
```ruby
|
101
|
+
Stretchie.update_indices
|
102
|
+
Stretchie.update_indices :users
|
103
|
+
```
|
104
|
+
|
105
|
+
Delete your indices:
|
106
|
+
```ruby
|
107
|
+
Stretchie.delete_indices
|
108
|
+
Stretchie.delete_indices :users
|
109
|
+
```
|
110
|
+
|
111
|
+
Refresh your indices:
|
112
|
+
```ruby
|
113
|
+
Stretchie.refresh_indices
|
114
|
+
Stretchie.refresh_indices :users
|
115
|
+
```
|
116
|
+
|
117
|
+
|
118
|
+
Maintaining Documents in the Index
|
119
|
+
----------------------------------
|
120
|
+
|
121
|
+
To add or update changes:
|
122
|
+
```ruby
|
123
|
+
user = User.create(name: 'Adam Bregenzer', email: 'adam@bregenzer.net')
|
124
|
+
user.update_in_index
|
125
|
+
```
|
126
|
+
|
127
|
+
To remove:
|
128
|
+
```ruby
|
129
|
+
user.delete_from_index
|
130
|
+
```
|
131
|
+
|
132
|
+
|
133
|
+
Searching
|
134
|
+
---------
|
135
|
+
|
136
|
+
You can do a simple search:
|
137
|
+
```ruby
|
138
|
+
User.search 'Adam'
|
139
|
+
User.search 'Adam', limit: 10, skip: 20, order: {'name' => 'asc'}
|
140
|
+
```
|
141
|
+
|
142
|
+
You can scope searches:
|
143
|
+
```ruby
|
144
|
+
Tag.search 'rails', terms: {'user_id': current_user.id}
|
145
|
+
```
|
146
|
+
|
147
|
+
You can search specific fields:
|
148
|
+
```ruby
|
149
|
+
User.field_search :email, 'adam@bregenzer.net'
|
150
|
+
```
|
151
|
+
|
152
|
+
You can search however you want:
|
153
|
+
```ruby
|
154
|
+
User.query_search {'match' => {'name' => 'Foo'}}
|
155
|
+
```
|
156
|
+
|
157
|
+
|
158
|
+
Talk to Me!
|
159
|
+
-----------
|
160
|
+
|
161
|
+
Let me know what you think, if you use it, etc.
|
162
|
+
|
163
|
+
|
164
|
+
Installation
|
165
|
+
------------
|
166
|
+
|
167
|
+
Add this line to your application's Gemfile:
|
168
|
+
|
169
|
+
gem 'stretchie'
|
170
|
+
|
171
|
+
And then execute:
|
172
|
+
|
173
|
+
$ bundle
|
174
|
+
|
175
|
+
Or install it yourself as:
|
176
|
+
|
177
|
+
$ gem install stretchie
|
178
|
+
|
179
|
+
|
180
|
+
Contributing
|
181
|
+
------------
|
182
|
+
|
183
|
+
1. Fork it ( https://github.com/adambregenzer/stretchie/fork )
|
184
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
185
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
186
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
187
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,219 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'elasticsearch/model'
|
3
|
+
|
4
|
+
module Stretchie
|
5
|
+
module Pants
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
include Elasticsearch::Model
|
10
|
+
|
11
|
+
|
12
|
+
# Add the current model to the list of searchable models
|
13
|
+
Stretchie::add_model self
|
14
|
+
|
15
|
+
# Have the index name include the rails environment
|
16
|
+
index_name "#{document_type}-#{ENV['RAILS_ENV']}"
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
# Update the document in the ElasticSearch index
|
21
|
+
def update_in_index
|
22
|
+
__elasticsearch__.index_document
|
23
|
+
|
24
|
+
index_dependent_models.map(&:update_in_index)
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
# Delete the document from the ElasticSearch index
|
29
|
+
def delete_from_index
|
30
|
+
begin
|
31
|
+
__elasticsearch__.delete_document
|
32
|
+
rescue Elasticsearch::Transport::Transport::Errors::NotFound
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
|
36
|
+
index_dependent_models.map(&:update_in_index)
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
# Override to trigger dependent models to be re-indexed, should return
|
41
|
+
# an array of models to run .update_in_index on.
|
42
|
+
def index_dependent_models
|
43
|
+
[]
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
module ClassMethods
|
48
|
+
|
49
|
+
# Search the index for a matching term
|
50
|
+
def search(q, options={})
|
51
|
+
fields = options.fetch(:fields, nil)
|
52
|
+
|
53
|
+
query_string = {'query' => q}
|
54
|
+
|
55
|
+
# If asked, limit the search to specific fields
|
56
|
+
if !fields.nil?
|
57
|
+
if !fields.kind_of? Array
|
58
|
+
fields = [fields.to_s]
|
59
|
+
end
|
60
|
+
|
61
|
+
query_string['fields'] = fields
|
62
|
+
end
|
63
|
+
|
64
|
+
# Perform a default query search
|
65
|
+
query = {
|
66
|
+
'bool' => {
|
67
|
+
'must' => [{'query_string' => query_string}]
|
68
|
+
}
|
69
|
+
}
|
70
|
+
|
71
|
+
begin
|
72
|
+
run_query query, options
|
73
|
+
rescue Elasticsearch::Transport::Transport::Errors::BadRequest
|
74
|
+
# This is probably because the query couldn't be parsed, clean it up
|
75
|
+
# and try again
|
76
|
+
query_string['query'] = sanitize(query_string['query'])
|
77
|
+
|
78
|
+
run_query query, options
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
# Search the specified field for a match
|
84
|
+
def field_search(field, q, options={})
|
85
|
+
# Search for a match based on prefix or a string match as a backup
|
86
|
+
query = {
|
87
|
+
'bool' => {
|
88
|
+
'minimum_should_match' => 1,
|
89
|
+
'should' => [
|
90
|
+
{'prefix' => {field => q}},
|
91
|
+
{'match' => {field => q}}
|
92
|
+
]
|
93
|
+
}
|
94
|
+
}
|
95
|
+
|
96
|
+
run_query query, options
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
# Execute a query hash in ElasticSearch
|
101
|
+
def query_search(query, options={})
|
102
|
+
run_query query, options
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
#
|
107
|
+
# Private Methods
|
108
|
+
#
|
109
|
+
|
110
|
+
|
111
|
+
# Run a query hash in ElasticSearch
|
112
|
+
def run_query(query, options={})
|
113
|
+
query = prepare_query query, options
|
114
|
+
|
115
|
+
# Run the query
|
116
|
+
response = self.__elasticsearch__.search query
|
117
|
+
|
118
|
+
Hashie::Mash.new({
|
119
|
+
records: response.records,
|
120
|
+
total_entries: response.results.total
|
121
|
+
})
|
122
|
+
end
|
123
|
+
private :run_query
|
124
|
+
|
125
|
+
|
126
|
+
# sanitize query string for Lucene. Useful if the original query raises an
|
127
|
+
# exception due to invalid DSL parse.
|
128
|
+
#
|
129
|
+
# http://stackoverflow.com/questions/16205341/symbols-in-query-string-for-elasticsearch
|
130
|
+
#
|
131
|
+
# @return self [Stretchie::QueryString] the modified QueryString.
|
132
|
+
def sanitize(q)
|
133
|
+
# Escape special characters
|
134
|
+
# http://lucene.apache.org/core/4_8_1/queryparser/org/apache/lucene/queryparser/classic/package-summary.html#package_description#Escaping_Special_Characters
|
135
|
+
escaped_characters = Regexp.escape('\\+-&|!(){}[]^~*?:\/')
|
136
|
+
q = q.gsub(/([#{escaped_characters}])/, '\\\\\1')
|
137
|
+
|
138
|
+
# AND, OR and NOT are used by lucene as logical operators. We need
|
139
|
+
# to escape them
|
140
|
+
['AND', 'OR', 'NOT'].each do |word|
|
141
|
+
escaped_word = word.split('').map {|char| "\\#{char}" }.join('')
|
142
|
+
q = q.gsub(/\s*\b(#{word.upcase})\b\s*/, " #{escaped_word} ")
|
143
|
+
end
|
144
|
+
|
145
|
+
# Escape odd quotes
|
146
|
+
if q.count('"') % 2 == 1
|
147
|
+
q = q.gsub(/(.*)"(.*)/, '\1\"\2')
|
148
|
+
end
|
149
|
+
|
150
|
+
q
|
151
|
+
end
|
152
|
+
private :sanitize
|
153
|
+
|
154
|
+
|
155
|
+
# Prepare a query hash for ElasticSearch
|
156
|
+
def prepare_query(q_hash, options={})
|
157
|
+
terms = options.fetch(:terms, {})
|
158
|
+
limit = options.fetch(:limit, -1)
|
159
|
+
skip = options.fetch(:skip, -1)
|
160
|
+
order = options.fetch(:order, {})
|
161
|
+
|
162
|
+
# Load the query but don't return any field data since we don't need it
|
163
|
+
query = {
|
164
|
+
'fields' => [],
|
165
|
+
'query' => q_hash
|
166
|
+
}
|
167
|
+
|
168
|
+
# If we have terms that must match (such as user_id) set them
|
169
|
+
if terms.length > 0
|
170
|
+
if q_hash.include?('term')
|
171
|
+
q_hash['term'].merge! terms
|
172
|
+
else
|
173
|
+
if q_hash.include?('bool')
|
174
|
+
if !q_hash['bool'].include?('must')
|
175
|
+
q_hash['bool']['must'] = []
|
176
|
+
end
|
177
|
+
|
178
|
+
q_hash['bool']['must'] << {term: terms}
|
179
|
+
else
|
180
|
+
query['query'] = {
|
181
|
+
'bool' => {
|
182
|
+
'must' => [q_hash]
|
183
|
+
}
|
184
|
+
}
|
185
|
+
|
186
|
+
query['query']['bool']['must'] << {term: terms}
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# Set the limit
|
192
|
+
if limit > 0
|
193
|
+
query['size'] = limit
|
194
|
+
end
|
195
|
+
|
196
|
+
# Set the number of records to skip
|
197
|
+
if skip > 0
|
198
|
+
query['from'] = skip
|
199
|
+
end
|
200
|
+
|
201
|
+
# Set the sort order, sorting by _score last
|
202
|
+
if !query.include? 'sort'
|
203
|
+
query['sort'] = []
|
204
|
+
end
|
205
|
+
|
206
|
+
order.map do |k, v|
|
207
|
+
query['sort'] << {k => v}
|
208
|
+
end
|
209
|
+
|
210
|
+
if query['sort'].select { |x| x.keys.first == '_score' }.count == 0
|
211
|
+
query['sort'] << {'_score' => 'desc'}
|
212
|
+
end
|
213
|
+
|
214
|
+
query
|
215
|
+
end
|
216
|
+
private :prepare_query
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
data/lib/stretchie.rb
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
# ElasticSearch
|
4
|
+
# require 'elasticsearch'
|
5
|
+
|
6
|
+
# Stretchie Core
|
7
|
+
require 'stretchie/version'
|
8
|
+
|
9
|
+
# Lets put our Stretchie::Pants on
|
10
|
+
require 'stretchie/pants'
|
11
|
+
|
12
|
+
|
13
|
+
# Defaults
|
14
|
+
module Stretchie
|
15
|
+
|
16
|
+
# Set containing all registered models
|
17
|
+
@@models = Set.new
|
18
|
+
|
19
|
+
|
20
|
+
# Add a model to the set
|
21
|
+
def self.add_model model
|
22
|
+
@@models << model
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
# Returns a set containing all registered models
|
27
|
+
def self.models
|
28
|
+
@@models
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
# Create or update all indices across all registered models
|
33
|
+
def self.update_indices(*args)
|
34
|
+
result = true
|
35
|
+
|
36
|
+
if args.length > 0
|
37
|
+
args = args.map { |m| m.to_s.singularize.capitalize.constantize }
|
38
|
+
_models = models.select { |m| args.include? m }
|
39
|
+
else
|
40
|
+
_models = models
|
41
|
+
end
|
42
|
+
|
43
|
+
_models.map do |m|
|
44
|
+
create = m.__elasticsearch__.create_index!
|
45
|
+
if !create.nil?
|
46
|
+
result &= create['acknowledged']
|
47
|
+
else
|
48
|
+
m.mappings.to_hash.map do |map, body|
|
49
|
+
client = m.__elasticsearch__.client
|
50
|
+
update = client.indices.put_mapping index: m.index_name, type: map,
|
51
|
+
body: {map => body}
|
52
|
+
|
53
|
+
if update.kind_of?(Hash)
|
54
|
+
result &= update['acknowledged']
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
result
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
# Refresh all indices across all registered models
|
65
|
+
def self.refresh_indices(*args)
|
66
|
+
result = true
|
67
|
+
|
68
|
+
if args.length > 0
|
69
|
+
args = args.map { |m| m.to_s.singularize.capitalize.constantize }
|
70
|
+
_models = models.select { |m| args.include? m }
|
71
|
+
else
|
72
|
+
_models = models
|
73
|
+
end
|
74
|
+
|
75
|
+
_models.map do |m|
|
76
|
+
refresh = m.__elasticsearch__.refresh_index!
|
77
|
+
if refresh.kind_of?(Hash) && refresh.include?('_shards')
|
78
|
+
result &= refresh['_shards']['failed'] == 0
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
result
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
# Delete all indices across all registered models
|
87
|
+
def self.delete_indices(*args)
|
88
|
+
result = true
|
89
|
+
|
90
|
+
if args.length > 0
|
91
|
+
args = args.map { |m| m.to_s.singularize.capitalize.constantize }
|
92
|
+
_models = models.select { |m| args.include? m }
|
93
|
+
else
|
94
|
+
_models = models
|
95
|
+
end
|
96
|
+
|
97
|
+
_models.map do |m|
|
98
|
+
delete = m.__elasticsearch__.delete_index!
|
99
|
+
|
100
|
+
if !delete.nil?
|
101
|
+
result &= delete['acknowledged']
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
result
|
106
|
+
end
|
107
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start do
|
3
|
+
add_filter '/spec/'
|
4
|
+
end
|
5
|
+
|
6
|
+
ENV['RAILS_ENV'] ||= 'test'
|
7
|
+
|
8
|
+
require_relative '../lib/stretchie'
|
9
|
+
|
10
|
+
pwd = File.expand_path File.dirname(__FILE__)
|
11
|
+
Dir[File.join(pwd, 'support/**/*.rb')].each { |f| require_relative f }
|
12
|
+
|
13
|
+
RSpec.configure do |config|
|
14
|
+
if config.files_to_run.one?
|
15
|
+
config.default_formatter = 'doc'
|
16
|
+
end
|
17
|
+
|
18
|
+
config.order = :random
|
19
|
+
Kernel.srand config.seed
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,225 @@
|
|
1
|
+
#encoding: UTF-8
|
2
|
+
|
3
|
+
describe Stretchie::Pants do
|
4
|
+
|
5
|
+
before :each do
|
6
|
+
@user = User.create(name: 'Foo Bar', email: 'baz@baz.com', misc_id: 100)
|
7
|
+
@user.update_in_index
|
8
|
+
@user1 = User.create(name: 'Red Green', email: 'blue@white.com' )
|
9
|
+
@user1.update_in_index
|
10
|
+
@user2 = User.create(name: 'Red Purple', email: 'red@white.com' )
|
11
|
+
@user2.update_in_index
|
12
|
+
Stretchie.refresh_indices
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
context '.delete_from_index' do
|
17
|
+
it 'removes document from index' do
|
18
|
+
@user.delete_from_index
|
19
|
+
Stretchie.refresh_indices
|
20
|
+
results = User.search 'Foo'
|
21
|
+
results = results.records.to_a
|
22
|
+
expect(results.length).to eq(0)
|
23
|
+
expect(results).to eq([])
|
24
|
+
@user.delete_from_index
|
25
|
+
end
|
26
|
+
end
|
27
|
+
context '.update_in_index' do
|
28
|
+
it 'adds documents to the index' do
|
29
|
+
@user.delete_from_index
|
30
|
+
Stretchie.refresh_indices
|
31
|
+
results = User.search 'Foo'
|
32
|
+
results = results.records.to_a
|
33
|
+
expect(results.length).to eq(0)
|
34
|
+
expect(results).to eq([])
|
35
|
+
|
36
|
+
@user.update_in_index
|
37
|
+
Stretchie.refresh_indices
|
38
|
+
results = User.search 'Foo'
|
39
|
+
results = results.records.to_a
|
40
|
+
expect(results.length).to eq(1)
|
41
|
+
expect(results).to eq([@user])
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context '.search' do
|
46
|
+
|
47
|
+
it 'searches all fields' do
|
48
|
+
results = User.search 'Foo'
|
49
|
+
results = results.records.to_a
|
50
|
+
expect(results.length).to eq(1)
|
51
|
+
expect(results).to eq([@user])
|
52
|
+
|
53
|
+
results = User.search 'baz@baz.com'
|
54
|
+
results = results.records.to_a
|
55
|
+
expect(results.length).to eq(1)
|
56
|
+
expect(results).to eq([@user])
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'searches all fields with invalid query characters' do
|
60
|
+
results = User.search 'Foo"'
|
61
|
+
results = results.records.to_a
|
62
|
+
expect(results.length).to eq(1)
|
63
|
+
expect(results).to eq([@user])
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'searches specific fields' do
|
67
|
+
results = User.search 'Foo', fields: :name
|
68
|
+
results = results.records.to_a
|
69
|
+
expect(results.length).to eq(1)
|
70
|
+
expect(results).to eq([@user])
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'requires specific terms' do
|
74
|
+
results = User.search 'baz@baz.com', terms: {misc_id: 100}
|
75
|
+
results = results.records.to_a
|
76
|
+
expect(results.length).to eq(1)
|
77
|
+
expect(results).to eq([@user])
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'supports limit' do
|
81
|
+
results = User.search 'Red', limit: 1, order: {'name' => 'asc'}
|
82
|
+
results = results.records.to_a
|
83
|
+
expect(results.length).to eq(1)
|
84
|
+
expect(results).to eq([@user1])
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'supports skip' do
|
88
|
+
results = User.search 'Red', limit: 1, skip: 1, order: {'name' => 'asc'}
|
89
|
+
results = results.records.to_a
|
90
|
+
expect(results.length).to eq(1)
|
91
|
+
expect(results).to eq([@user2])
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'supports order' do
|
95
|
+
results = User.search 'Red', order: {'name' => 'asc'}
|
96
|
+
results = results.records.to_a
|
97
|
+
expect(results.length).to eq(2)
|
98
|
+
expect(results).to eq([@user1, @user2])
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
context '.field_search' do
|
104
|
+
it 'searches all fields' do
|
105
|
+
results = User.field_search :name, 'Foo'
|
106
|
+
results = results.records.to_a
|
107
|
+
expect(results.length).to eq(1)
|
108
|
+
expect(results).to eq([@user])
|
109
|
+
|
110
|
+
results = User.field_search :name, 'baz@baz.com'
|
111
|
+
results = results.records.to_a
|
112
|
+
expect(results.length).to eq(0)
|
113
|
+
expect(results).to eq([])
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'requires specific terms' do
|
117
|
+
results = User.field_search :name, 'Foo', terms: {misc_id: 100}
|
118
|
+
results = results.records.to_a
|
119
|
+
expect(results.length).to eq(1)
|
120
|
+
expect(results).to eq([@user])
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'supports limit' do
|
124
|
+
results = User.field_search :name, 'Red', limit: 1, order: {'name' => 'asc'}
|
125
|
+
results = results.records.to_a
|
126
|
+
expect(results.length).to eq(1)
|
127
|
+
expect(results).to eq([@user1])
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'supports skip' do
|
131
|
+
results = User.field_search :name, 'Red', limit: 1, skip: 1, order: {'name' => 'asc'}
|
132
|
+
results = results.records.to_a
|
133
|
+
expect(results.length).to eq(1)
|
134
|
+
expect(results).to eq([@user2])
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'supports order' do
|
138
|
+
results = User.field_search :name, 'Red', order: {'name' => 'asc'}
|
139
|
+
results = results.records.to_a
|
140
|
+
expect(results.length).to eq(2)
|
141
|
+
expect(results).to eq([@user1, @user2])
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
context '.query_search' do
|
147
|
+
it 'performs a custom search' do
|
148
|
+
query = {
|
149
|
+
'match' => {
|
150
|
+
'name' => 'Foo'
|
151
|
+
}
|
152
|
+
}
|
153
|
+
|
154
|
+
results = User.query_search query
|
155
|
+
results = results.records.to_a
|
156
|
+
expect(results.length).to eq(1)
|
157
|
+
expect(results).to eq([@user])
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'requires specific terms' do
|
161
|
+
query = {
|
162
|
+
'match' => {
|
163
|
+
'name' => 'Foo'
|
164
|
+
}
|
165
|
+
}
|
166
|
+
|
167
|
+
results = User.query_search query, terms: {misc_id: 100}
|
168
|
+
results = results.records.to_a
|
169
|
+
expect(results.length).to eq(1)
|
170
|
+
expect(results).to eq([@user])
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'requires additional terms' do
|
174
|
+
query = {
|
175
|
+
'term' => {
|
176
|
+
'name' => 'Foo'
|
177
|
+
}
|
178
|
+
}
|
179
|
+
|
180
|
+
results = User.query_search query, terms: {misc_id: 100}
|
181
|
+
results = results.records.to_a
|
182
|
+
expect(results.length).to eq(1)
|
183
|
+
expect(results).to eq([@user])
|
184
|
+
end
|
185
|
+
|
186
|
+
it 'supports limit' do
|
187
|
+
query = {
|
188
|
+
'match' => {
|
189
|
+
'name' => 'Red'
|
190
|
+
}
|
191
|
+
}
|
192
|
+
|
193
|
+
results = User.query_search query, limit: 1, order: {'name' => 'asc'}
|
194
|
+
results = results.records.to_a
|
195
|
+
expect(results.length).to eq(1)
|
196
|
+
expect(results).to eq([@user1])
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'supports skip' do
|
200
|
+
query = {
|
201
|
+
'match' => {
|
202
|
+
'name' => 'Red'
|
203
|
+
}
|
204
|
+
}
|
205
|
+
|
206
|
+
results = User.query_search query, limit: 1, skip: 1, order: {'name' => 'asc'}
|
207
|
+
results = results.records.to_a
|
208
|
+
expect(results.length).to eq(1)
|
209
|
+
expect(results).to eq([@user2])
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'supports order' do
|
213
|
+
query = {
|
214
|
+
'match' => {
|
215
|
+
'name' => 'Red'
|
216
|
+
}
|
217
|
+
}
|
218
|
+
|
219
|
+
results = User.query_search query, order: {'name' => 'asc'}
|
220
|
+
results = results.records.to_a
|
221
|
+
expect(results.length).to eq(2)
|
222
|
+
expect(results).to eq([@user1, @user2])
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
#encoding: UTF-8
|
2
|
+
|
3
|
+
describe Stretchie do
|
4
|
+
|
5
|
+
context '.update_indices' do
|
6
|
+
|
7
|
+
it 'creates all indices' do
|
8
|
+
Stretchie.delete_indices
|
9
|
+
indices = User.__elasticsearch__.client.indices.status['indices'].keys
|
10
|
+
expect(indices).not_to include(User.index_name)
|
11
|
+
result = Stretchie.update_indices
|
12
|
+
expect(result).to eq(true)
|
13
|
+
indices = User.__elasticsearch__.client.indices.status['indices'].keys
|
14
|
+
expect(indices).to include(User.index_name)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'creates specific indices' do
|
18
|
+
Stretchie.delete_indices
|
19
|
+
indices = User.__elasticsearch__.client.indices.status['indices'].keys
|
20
|
+
expect(indices).not_to include(User.index_name)
|
21
|
+
|
22
|
+
result = Stretchie.update_indices :users
|
23
|
+
expect(result).to eq(true)
|
24
|
+
indices = User.__elasticsearch__.client.indices.status['indices'].keys
|
25
|
+
expect(indices).to include(User.index_name)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'updates all indices' do
|
29
|
+
Stretchie.delete_indices
|
30
|
+
indices = User.__elasticsearch__.client.indices.status['indices'].keys
|
31
|
+
expect(indices).not_to include(User.index_name)
|
32
|
+
|
33
|
+
Stretchie.update_indices
|
34
|
+
result = Stretchie.update_indices
|
35
|
+
expect(result).to eq(true)
|
36
|
+
indices = User.__elasticsearch__.client.indices.status['indices'].keys
|
37
|
+
expect(indices).to include(User.index_name)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'updates specific indices' do
|
41
|
+
Stretchie.delete_indices :users
|
42
|
+
indices = User.__elasticsearch__.client.indices.status['indices'].keys
|
43
|
+
expect(indices).not_to include(User.index_name)
|
44
|
+
|
45
|
+
Stretchie.update_indices :users
|
46
|
+
result = Stretchie.update_indices :users
|
47
|
+
expect(result).to eq(true)
|
48
|
+
indices = User.__elasticsearch__.client.indices.status['indices'].keys
|
49
|
+
expect(indices).to include(User.index_name)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
context '.delete_indices' do
|
55
|
+
|
56
|
+
it 'deletes all indices' do
|
57
|
+
Stretchie.update_indices
|
58
|
+
Stretchie.delete_indices
|
59
|
+
indices = User.__elasticsearch__.client.indices.status['indices'].keys
|
60
|
+
expect(indices).not_to include(User.index_name)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'deletes specific indices' do
|
64
|
+
Stretchie.delete_indices :users
|
65
|
+
indices = User.__elasticsearch__.client.indices.status['indices'].keys
|
66
|
+
expect(indices).not_to include(User.index_name)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
context '.refresh_indices' do
|
72
|
+
|
73
|
+
it 'refreshes all indices' do
|
74
|
+
result = Stretchie.refresh_indices
|
75
|
+
expect(result).to eq(true)
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'refreshes specific indices' do
|
79
|
+
result = Stretchie.refresh_indices :users
|
80
|
+
expect(result).to eq(true)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: ':memory:'
|
5
|
+
ActiveRecord::Base.logger = Logger.new STDOUT
|
6
|
+
ActiveRecord::Base.logger.level = Logger::WARN
|
7
|
+
ActiveSupport::LogSubscriber.colorize_logging = false
|
8
|
+
|
9
|
+
|
10
|
+
ActiveRecord::Schema.define do
|
11
|
+
self.verbose = false
|
12
|
+
|
13
|
+
create_table :users do |t|
|
14
|
+
t.string :name
|
15
|
+
t.string :email
|
16
|
+
t.integer :misc_id
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
module Search
|
22
|
+
module User
|
23
|
+
extend ActiveSupport::Concern
|
24
|
+
include Stretchie::Pants
|
25
|
+
|
26
|
+
included do
|
27
|
+
settings index: { number_of_shards: 1 } do
|
28
|
+
mappings dynamic: 'false' do
|
29
|
+
indexes :name, analyzer: 'whitespace', index_options: 'offsets'
|
30
|
+
indexes :email
|
31
|
+
indexes :misc_id
|
32
|
+
indexes :sortable_name
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
def as_indexed_json(options={})
|
39
|
+
json = as_json(only: [:name, :email, :misc_id])
|
40
|
+
json['sortable_name'] = self.name.downcase
|
41
|
+
json
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
class User < ActiveRecord::Base
|
48
|
+
include Search::User
|
49
|
+
attr_accessor :name, :email, :misc_id
|
50
|
+
end
|
data/stretchie.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
|
6
|
+
require 'stretchie/version'
|
7
|
+
|
8
|
+
Gem::Specification.new do |spec|
|
9
|
+
spec.name = 'stretchie'
|
10
|
+
spec.version = Stretchie::VERSION
|
11
|
+
spec.authors = ['Adam Bregenzer']
|
12
|
+
spec.email = ['adam@bregenzer.net']
|
13
|
+
spec.summary = 'An ActiveModel concern for integrating ElasticSearch.'
|
14
|
+
spec.description = 'Comfortable searching pants for ActiveRecord Models. Stretchie simplifies using elastic search in your models and provides hooks to ease testing.'
|
15
|
+
spec.homepage = 'https://github.com/adambregenzer/stretchie'
|
16
|
+
spec.license = 'MIT'
|
17
|
+
|
18
|
+
spec.files = `git ls-files -z`.split("\x0")
|
19
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
20
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
21
|
+
spec.require_paths = ['lib']
|
22
|
+
|
23
|
+
spec.add_dependency 'rails', '~> 4.1', '>= 4.1.1'
|
24
|
+
spec.add_dependency 'elasticsearch-model', '~> 0.1', '>= 0.1.4'
|
25
|
+
|
26
|
+
spec.add_development_dependency 'bundler', '~> 1.6'
|
27
|
+
spec.add_development_dependency 'rake', '~> 10.3', '>= 10.3.2'
|
28
|
+
spec.add_development_dependency 'rspec', '~> 3.0', '>= 3.0.0'
|
29
|
+
spec.add_development_dependency 'simplecov', '~> 0.8', '>= 0.8.2'
|
30
|
+
spec.add_development_dependency 'sqlite3', '~> 1.3', '>= 1.3.9'
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,199 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: stretchie
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Adam Bregenzer
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-06-25 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.1'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 4.1.1
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '4.1'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 4.1.1
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: elasticsearch-model
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0.1'
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 0.1.4
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0.1'
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 0.1.4
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: bundler
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - "~>"
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '1.6'
|
60
|
+
type: :development
|
61
|
+
prerelease: false
|
62
|
+
version_requirements: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - "~>"
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '1.6'
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: rake
|
69
|
+
requirement: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - "~>"
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '10.3'
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 10.3.2
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '10.3'
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: 10.3.2
|
87
|
+
- !ruby/object:Gem::Dependency
|
88
|
+
name: rspec
|
89
|
+
requirement: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - "~>"
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '3.0'
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 3.0.0
|
97
|
+
type: :development
|
98
|
+
prerelease: false
|
99
|
+
version_requirements: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '3.0'
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: 3.0.0
|
107
|
+
- !ruby/object:Gem::Dependency
|
108
|
+
name: simplecov
|
109
|
+
requirement: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - "~>"
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0.8'
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: 0.8.2
|
117
|
+
type: :development
|
118
|
+
prerelease: false
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - "~>"
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0.8'
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: 0.8.2
|
127
|
+
- !ruby/object:Gem::Dependency
|
128
|
+
name: sqlite3
|
129
|
+
requirement: !ruby/object:Gem::Requirement
|
130
|
+
requirements:
|
131
|
+
- - "~>"
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '1.3'
|
134
|
+
- - ">="
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: 1.3.9
|
137
|
+
type: :development
|
138
|
+
prerelease: false
|
139
|
+
version_requirements: !ruby/object:Gem::Requirement
|
140
|
+
requirements:
|
141
|
+
- - "~>"
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: '1.3'
|
144
|
+
- - ">="
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: 1.3.9
|
147
|
+
description: Comfortable searching pants for ActiveRecord Models. Stretchie simplifies
|
148
|
+
using elastic search in your models and provides hooks to ease testing.
|
149
|
+
email:
|
150
|
+
- adam@bregenzer.net
|
151
|
+
executables: []
|
152
|
+
extensions: []
|
153
|
+
extra_rdoc_files: []
|
154
|
+
files:
|
155
|
+
- ".gitignore"
|
156
|
+
- ".rspec"
|
157
|
+
- Gemfile
|
158
|
+
- LICENSE.txt
|
159
|
+
- README.md
|
160
|
+
- Rakefile
|
161
|
+
- lib/stretchie.rb
|
162
|
+
- lib/stretchie/pants.rb
|
163
|
+
- lib/stretchie/version.rb
|
164
|
+
- spec/spec_helper.rb
|
165
|
+
- spec/stretchie_pants_spec.rb
|
166
|
+
- spec/stretchie_spec.rb
|
167
|
+
- spec/support/elastic_search.rb
|
168
|
+
- spec/support/model.rb
|
169
|
+
- stretchie.gemspec
|
170
|
+
homepage: https://github.com/adambregenzer/stretchie
|
171
|
+
licenses:
|
172
|
+
- MIT
|
173
|
+
metadata: {}
|
174
|
+
post_install_message:
|
175
|
+
rdoc_options: []
|
176
|
+
require_paths:
|
177
|
+
- lib
|
178
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
179
|
+
requirements:
|
180
|
+
- - ">="
|
181
|
+
- !ruby/object:Gem::Version
|
182
|
+
version: '0'
|
183
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - ">="
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '0'
|
188
|
+
requirements: []
|
189
|
+
rubyforge_project:
|
190
|
+
rubygems_version: 2.2.2
|
191
|
+
signing_key:
|
192
|
+
specification_version: 4
|
193
|
+
summary: An ActiveModel concern for integrating ElasticSearch.
|
194
|
+
test_files:
|
195
|
+
- spec/spec_helper.rb
|
196
|
+
- spec/stretchie_pants_spec.rb
|
197
|
+
- spec/stretchie_spec.rb
|
198
|
+
- spec/support/elastic_search.rb
|
199
|
+
- spec/support/model.rb
|