stretchie 0.0.1
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 +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
|