tanker 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +7 -2
- data/README.rdoc +134 -12
- data/VERSION +1 -1
- data/lib/tanker/configuration.rb +4 -5
- data/lib/tanker/paginated_array.rb +29 -0
- data/lib/tanker/railtie.rb +15 -6
- data/lib/tanker/utilities.rb +18 -14
- data/lib/tanker.rb +159 -45
- data/spec/integration_spec.rb +183 -0
- data/spec/integration_spec_conf.rb.example +1 -0
- data/spec/spec_helper.rb +15 -5
- data/spec/tanker_spec.rb +381 -51
- data/spec/utilities_spec.rb +18 -9
- data/tanker.gemspec +15 -5
- metadata +31 -5
data/Gemfile
CHANGED
@@ -1,10 +1,15 @@
|
|
1
1
|
source "http://rubygems.org"
|
2
2
|
|
3
3
|
gem 'jeweler'
|
4
|
+
gem 'json', '>= 1.5.1'
|
4
5
|
gem 'will_paginate', '>= 2.3.15'
|
6
|
+
gem 'kaminari'
|
5
7
|
|
6
|
-
group :test do
|
7
|
-
gem 'rspec', '>= 2.5.0'
|
8
|
+
group :test do
|
9
|
+
gem 'rspec', '>= 2.5.0'
|
10
|
+
gem 'ruby-debug19'
|
11
|
+
gem 'activerecord', '>= 3.0.7'
|
12
|
+
gem 'sqlite3'
|
8
13
|
end
|
9
14
|
|
10
15
|
|
data/README.rdoc
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
= Tanker - IndexTank integration to your favorite orm
|
2
2
|
|
3
|
-
IndexTank is a great search indexing service, this gem tries to make
|
4
|
-
any orm
|
5
|
-
|
6
|
-
also supports pagination using
|
7
|
-
|
8
|
-
|
9
|
-
|
3
|
+
IndexTank is a great search indexing service, this gem tries to make
|
4
|
+
that any orm stays in sync with IndexTank easily. It has a simple
|
5
|
+
yet powerful approach to integrate IndexTank with any ORM and
|
6
|
+
also supports pagination using {WillPaginate}[https://github.com/mislav/will_paginate] (default)
|
7
|
+
or {Kaminari}[https://github.com/amatsuda/kaminari].
|
8
|
+
|
9
|
+
Very little is needed to make it work,
|
10
|
+
basically, all you need is that the orm instance respond to +id+, +all+, +find+ and any attributes
|
10
11
|
you wish to index.
|
11
12
|
|
12
13
|
== Installation
|
@@ -25,13 +26,25 @@ If you're using Rails, config/initializers/tanker.rb is a good place for this:
|
|
25
26
|
|
26
27
|
YourAppName::Application.config.index_tank_url = 'http://:xxxxxxxxx@xxxxx.api.indextank.com'
|
27
28
|
|
28
|
-
If you are not using rails you can put this somewhere before you load your
|
29
|
+
If you are not using rails you can put this somewhere before you load your models
|
29
30
|
|
30
31
|
Tanker.configuration = {:url => 'http://:xxxxxxxxx@xxxxx.api.indextank.com' }
|
31
32
|
|
32
|
-
You would probably want to have
|
33
|
+
You would probably want to have fancier configuration depending on
|
33
34
|
your environment. Be sure to copy and paste the correct url provided
|
34
|
-
|
35
|
+
by the IndexTank Dashboard
|
36
|
+
|
37
|
+
=== Pagination
|
38
|
+
|
39
|
+
WillPaginate is used by default and you don't have to add any extra settings to use that. If you
|
40
|
+
want to use Kaminari you have to add the following:
|
41
|
+
|
42
|
+
YourAppName::Application.config.index_tank_url = 'http://...'
|
43
|
+
YourAppName::Application.config.tanker_pagination_backend = :kaminari
|
44
|
+
|
45
|
+
or
|
46
|
+
|
47
|
+
Tanker.configuration = {:url => 'http://...', :pagination_backend => :kaminari }
|
35
48
|
|
36
49
|
== Example
|
37
50
|
|
@@ -117,8 +130,39 @@ Search with query variables
|
|
117
130
|
|
118
131
|
Paginate Results
|
119
132
|
|
120
|
-
|
133
|
+
<%= will_paginate @topics %> for WillPaginate
|
134
|
+
<%= paginate @topics %> for Kaminari
|
135
|
+
|
136
|
+
Disable pagination of search results
|
137
|
+
|
138
|
+
@topics = Topic.search_tank('keyword', :paginate => false)
|
139
|
+
|
140
|
+
== Fetching attributes and snippets
|
141
|
+
|
142
|
+
As of version 1.1 Tanker supports fetching attributes and snippets directly from the Index. If you use either of this search options
|
143
|
+
your Model instances will be instanced from the attributes coming from the index not by making another trip to the database.
|
144
|
+
|
145
|
+
This can represent a major speed increase for your queries but it also means that you will only have available the attributes
|
146
|
+
you indexed in your model. There are edge cases that are not tested so please help us have this feature be super stable and
|
147
|
+
have everyone bennefit from the speed this entails.
|
148
|
+
|
149
|
+
Example:
|
150
|
+
|
151
|
+
@very_long_sting = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
|
152
|
+
Product.create(:name => @very_long_sting, :href => 'http://google.com' )
|
153
|
+
|
154
|
+
@products = Product.search_tank('quis exercitation', :snippets => [:name], :fetch => [:href])
|
155
|
+
@products[0].name_snippet
|
156
|
+
# => 'Ut enim ad minim veniam, <b>quis</b> nostrud <b>exercitation</b> ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla'
|
157
|
+
@products[0].href
|
158
|
+
# => 'http://google.com'
|
159
|
+
@products[0].any_other_attribute
|
160
|
+
# => nil
|
161
|
+
|
162
|
+
Notice that to get the snippeted value that Index Tank sends you get it via the '_snippet' method generated on the fly.
|
121
163
|
|
164
|
+
The attributes that are requested to be fetched are set as attributes as new instances of your model objects and no Database Query is executed. NEAT!
|
165
|
+
|
122
166
|
== Geospatial Search Example
|
123
167
|
|
124
168
|
IndexTank and the Tanker gem support native geographic calculations. All you need is to define two document variables with your latitude and your longitude
|
@@ -177,7 +221,85 @@ IndexTank and the Tanker gem support native geographic calculations. All you nee
|
|
177
221
|
:function => 1,
|
178
222
|
:filter_functions => {2 => [['*', 50]]})
|
179
223
|
|
224
|
+
== Extend your index definitions
|
225
|
+
|
226
|
+
If you have a bunch of models with a lot of overlapping indexed fields,
|
227
|
+
variables, or categories, you might want to abstract those out into a module
|
228
|
+
that you can include and then extend in the including classes. Something like:
|
229
|
+
|
230
|
+
module TankerDefaults
|
231
|
+
def self.included(base)
|
232
|
+
base.send(:include, ::Tanker) # include the actual Tanker module
|
233
|
+
|
234
|
+
# provide a default index name
|
235
|
+
base.tankit 'my_index' do
|
236
|
+
# index some common fields
|
237
|
+
indexes :tag_list
|
238
|
+
|
239
|
+
# set some common variables
|
240
|
+
variables do
|
241
|
+
{
|
242
|
+
0 => view_count
|
243
|
+
1 => foo
|
244
|
+
}
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
class SuperModel
|
251
|
+
include TankerDefaults
|
252
|
+
|
253
|
+
# no need to respecify the index if it's the same
|
254
|
+
# (but you can override it)
|
255
|
+
tankit do
|
256
|
+
# `indexes :tag_list` is inherited
|
257
|
+
indexes :name
|
258
|
+
indexes :boyfriend_names do
|
259
|
+
boyfriends.map(&:name)
|
260
|
+
end
|
261
|
+
|
262
|
+
# set some more specific variables
|
263
|
+
variables do
|
264
|
+
{
|
265
|
+
# `0 => view_count` is inherited
|
266
|
+
1 => iq, # overwrites "foo"
|
267
|
+
2 => endorsements.count # adds new variables
|
268
|
+
}
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
You currently can't remove previously defined stuff, though.
|
274
|
+
|
275
|
+
=== Single table inheritance
|
276
|
+
|
277
|
+
If you are using tanker with STI models and want the different models to be indexed
|
278
|
+
as the same type, you can provide the base model name with an +:as+ option to +tankit+.
|
279
|
+
|
280
|
+
module TankerPetDefaults
|
281
|
+
def self.included(base)
|
282
|
+
base.send(:include, ::Tanker)
|
283
|
+
|
284
|
+
base.tankit 'my_index', :as => 'Pet' do
|
285
|
+
indexes :skills
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
class Pet < ActiveRecord::Base
|
291
|
+
end
|
292
|
+
|
293
|
+
class Cat < Pet
|
294
|
+
include TankerPetDefaults
|
295
|
+
end
|
296
|
+
|
297
|
+
class Monkey < Pet
|
298
|
+
include TankerPetDefaults
|
299
|
+
end
|
180
300
|
|
301
|
+
# Search using the base model
|
302
|
+
Pet.search('stealth')
|
181
303
|
|
182
304
|
== Reindex your data
|
183
305
|
|
@@ -187,7 +309,7 @@ If you are using rails 3 there are of couple of rake tasks included in your proj
|
|
187
309
|
|
188
310
|
This task deletes all your indexes and recreates empty indexes, if you have not created
|
189
311
|
the indexes in the Index Tank interface this task creates them for you provided
|
190
|
-
you have enough indexes in your account.
|
312
|
+
you have enough available indexes in your account.
|
191
313
|
|
192
314
|
rake tanker:reindex
|
193
315
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.1.0
|
data/lib/tanker/configuration.rb
CHANGED
@@ -1,15 +1,14 @@
|
|
1
1
|
module Tanker
|
2
|
-
|
3
2
|
module Configuration
|
4
|
-
|
5
3
|
def configuration
|
6
4
|
@@configuration || raise(NotConfigured, "Please configure Tanker. Set Tanker.configuration = {:url => ''}")
|
7
5
|
end
|
8
6
|
|
9
7
|
def configuration=(new_configuration)
|
10
|
-
|
8
|
+
# the default pagination backend is WillPaginate
|
9
|
+
@@configuration = new_configuration.tap do |_config|
|
10
|
+
_config.replace({ :pagination_backend => :will_paginate }.merge(_config)) if _config.is_a?(Hash)
|
11
|
+
end
|
11
12
|
end
|
12
|
-
|
13
13
|
end
|
14
|
-
|
15
14
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
unless defined?(Kaminari)
|
2
|
+
raise(Tanker::BadConfiguration, "Tanker: Please add 'kaminari' to your Gemfile to use kaminari pagination backend")
|
3
|
+
end
|
4
|
+
|
5
|
+
module Tanker
|
6
|
+
class KaminariPaginatedArray < Array
|
7
|
+
include ::Kaminari::ConfigurationMethods::ClassMethods
|
8
|
+
include ::Kaminari::PageScopeMethods
|
9
|
+
|
10
|
+
attr_reader :limit_value, :offset_value, :total_count
|
11
|
+
|
12
|
+
def initialize(original_array, limit_val = default_per_page, offset_val, total_count)
|
13
|
+
@limit_value, @offset_value, @total_count = limit_val, offset_val, total_count
|
14
|
+
super(original_array)
|
15
|
+
end
|
16
|
+
|
17
|
+
def page(num = 1)
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def limit(num)
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def current_page
|
26
|
+
offset_value+1
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/tanker/railtie.rb
CHANGED
@@ -2,18 +2,27 @@ require 'rails'
|
|
2
2
|
|
3
3
|
module Tanker
|
4
4
|
class Railtie < Rails::Railtie
|
5
|
-
config.index_tank_url =
|
6
|
-
|
5
|
+
config.index_tank_url = nil
|
6
|
+
config.tanker_pagination_backend = nil
|
7
|
+
|
7
8
|
initializer "tanker.boot" do
|
8
|
-
|
9
|
+
setup_tanker_configuration
|
9
10
|
end
|
10
|
-
|
11
|
+
|
11
12
|
config.after_initialize do
|
12
|
-
|
13
|
+
setup_tanker_configuration
|
13
14
|
end
|
14
15
|
|
15
16
|
rake_tasks do
|
16
17
|
load "tanker/tasks/tanker.rake"
|
17
18
|
end
|
19
|
+
|
20
|
+
private
|
21
|
+
def setup_tanker_configuration
|
22
|
+
Tanker.configuration = {}.tap do |_new_conf|
|
23
|
+
_new_conf[:url] = config.index_tank_url if config.index_tank_url
|
24
|
+
_new_conf[:pagination_backend] = config.tanker_pagination_backend if config.tanker_pagination_backend
|
25
|
+
end
|
26
|
+
end
|
18
27
|
end
|
19
|
-
end
|
28
|
+
end
|
data/lib/tanker/utilities.rb
CHANGED
@@ -11,22 +11,26 @@ module Tanker
|
|
11
11
|
|
12
12
|
def clear_all_indexes
|
13
13
|
get_available_indexes.each do |index_name|
|
14
|
-
|
15
|
-
|
14
|
+
clear_index(index_name)
|
15
|
+
end
|
16
|
+
end
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
index
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
18
|
+
def clear_index(index_name)
|
19
|
+
begin
|
20
|
+
index = Tanker.api.get_index(index_name)
|
21
|
+
|
22
|
+
if index.exists?
|
23
|
+
puts "Deleting #{index_name} index"
|
24
|
+
index.delete_index()
|
25
|
+
end
|
26
|
+
puts "Creating #{index_name} index"
|
27
|
+
index.create_index()
|
28
|
+
puts "Waiting for the index to be ready"
|
29
|
+
while not index.running?
|
30
|
+
sleep 0.5
|
29
31
|
end
|
32
|
+
rescue => e
|
33
|
+
puts "There was an error clearing or creating the #{index_name} index: #{e.to_s}"
|
30
34
|
end
|
31
35
|
end
|
32
36
|
|
data/lib/tanker.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
begin
|
3
2
|
require "rubygems"
|
4
3
|
require "bundler"
|
@@ -12,7 +11,6 @@ require 'tanker/configuration'
|
|
12
11
|
require 'tanker/utilities'
|
13
12
|
require 'will_paginate/collection'
|
14
13
|
|
15
|
-
|
16
14
|
if defined? Rails
|
17
15
|
begin
|
18
16
|
require 'tanker/railtie'
|
@@ -23,11 +21,15 @@ end
|
|
23
21
|
module Tanker
|
24
22
|
|
25
23
|
class NotConfigured < StandardError; end
|
24
|
+
class BadConfiguration < StandardError; end
|
26
25
|
class NoBlockGiven < StandardError; end
|
26
|
+
class NoIndexName < StandardError; end
|
27
27
|
|
28
28
|
autoload :Configuration, 'tanker/configuration'
|
29
29
|
extend Configuration
|
30
30
|
|
31
|
+
autoload :KaminariPaginatedArray, 'tanker/paginated_array'
|
32
|
+
|
31
33
|
class << self
|
32
34
|
attr_reader :included_in
|
33
35
|
|
@@ -36,11 +38,12 @@ module Tanker
|
|
36
38
|
end
|
37
39
|
|
38
40
|
def included(klass)
|
41
|
+
configuration # raises error if not defined
|
42
|
+
|
39
43
|
@included_in ||= []
|
40
44
|
@included_in << klass
|
41
45
|
@included_in.uniq!
|
42
46
|
|
43
|
-
configuration # raises error if not defined
|
44
47
|
klass.send :include, InstanceMethods
|
45
48
|
klass.extend ClassMethods
|
46
49
|
|
@@ -62,16 +65,16 @@ module Tanker
|
|
62
65
|
def search(models, query, options = {})
|
63
66
|
ids = []
|
64
67
|
models = [models].flatten.uniq
|
65
|
-
page = (options.delete(:page) || 1).to_i
|
66
|
-
per_page = (options.delete(:per_page) || models.first.per_page).to_i
|
67
68
|
index = models.first.tanker_index
|
68
69
|
query = query.join(' ') if Array === query
|
70
|
+
snippets = options.delete(:snippets)
|
71
|
+
fetch = options.delete(:fetch)
|
72
|
+
paginate = extract_setup_paginate_options(options, :page => 1, :per_page => models.first.per_page)
|
69
73
|
|
70
74
|
if (index_names = models.map(&:tanker_config).map(&:index_name).uniq).size > 1
|
71
75
|
raise "You can't search across multiple indexes in one call (#{index_names.inspect})"
|
72
76
|
end
|
73
77
|
|
74
|
-
|
75
78
|
# move conditions into the query body
|
76
79
|
if conditions = options.delete(:conditions)
|
77
80
|
conditions.each do |field, value|
|
@@ -96,55 +99,127 @@ module Tanker
|
|
96
99
|
end
|
97
100
|
end
|
98
101
|
|
99
|
-
|
100
|
-
options
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
102
|
+
# fetch values from index tank or just the type and id to instace results localy
|
103
|
+
options[:fetch] = "__type,__id"
|
104
|
+
options[:fetch] += ",#{fetch.join(',')}" if fetch
|
105
|
+
options[:snippet] = snippets.join(',') if snippets
|
106
|
+
|
107
|
+
search_on_fields = models.map{|model| model.tanker_config.indexes.map{|arr| arr[0]}.uniq}.flatten.uniq.join(":(#{query.to_s}) OR ")
|
108
|
+
query = "#{search_on_fields}:(#{query.to_s}) __type:(#{models.map(&:name).map {|name| "\"#{name.split('::').join(' ')}\"" }.join(' OR ')})"
|
106
109
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
110
|
+
options = { :start => paginate[:per_page] * (paginate[:page] - 1), :len => paginate[:per_page] }.merge(options) if paginate
|
111
|
+
results = index.search(query, options)
|
112
|
+
|
113
|
+
instantiated_results = if (fetch || snippets)
|
114
|
+
instantiate_results_from_results(results, fetch, snippets)
|
115
|
+
else
|
116
|
+
instantiate_results_from_db(results)
|
111
117
|
end
|
118
|
+
paginate === false ? instantiated_results : paginate_results(instantiated_results, paginate, results['matches'])
|
112
119
|
end
|
113
120
|
|
114
121
|
protected
|
115
122
|
|
116
|
-
def
|
123
|
+
def instantiate_results_from_db(index_result)
|
117
124
|
results = index_result['results']
|
118
125
|
return [] if results.empty?
|
119
126
|
|
120
127
|
id_map = results.inject({}) do |acc, result|
|
121
|
-
model
|
128
|
+
model = result["__type"]
|
129
|
+
id = constantize(model).tanker_parse_doc_id(result)
|
122
130
|
acc[model] ||= []
|
123
|
-
acc[model] << id
|
131
|
+
acc[model] << id
|
124
132
|
acc
|
125
133
|
end
|
134
|
+
|
135
|
+
id_map.each do |klass, ids|
|
136
|
+
# replace the id list with an eager-loaded list of records for this model
|
137
|
+
id_map[klass] = constantize(klass).find(ids)
|
138
|
+
end
|
139
|
+
# return them in order
|
140
|
+
results.map do |result|
|
141
|
+
model, id = result["__type"], result["__id"]
|
142
|
+
id_map[model].detect {|record| id == record.id.to_s }
|
143
|
+
end
|
144
|
+
end
|
126
145
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
146
|
+
def paginate_results(results, pagination_options, total_hits)
|
147
|
+
case Tanker.configuration[:pagination_backend]
|
148
|
+
when :will_paginate
|
149
|
+
WillPaginate::Collection.create(pagination_options[:page],
|
150
|
+
pagination_options[:per_page],
|
151
|
+
total_hits) { |pager| pager.replace results }
|
152
|
+
when :kaminari
|
153
|
+
Tanker::KaminariPaginatedArray.new(results,
|
154
|
+
pagination_options[:per_page],
|
155
|
+
pagination_options[:page]-1,
|
156
|
+
total_hits)
|
157
|
+
else
|
158
|
+
raise(BadConfiguration, "Unknown pagination backend")
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def instantiate_results_from_results(index_result, fetch = false, snippets = false)
|
163
|
+
results = index_result['results']
|
164
|
+
return [] if results.empty?
|
165
|
+
instances = []
|
166
|
+
id_map = results.inject({}) do |acc, result|
|
167
|
+
model = result["__type"]
|
168
|
+
instance = constantize(model).new()
|
169
|
+
result.each do |key, value|
|
170
|
+
case key
|
171
|
+
when /snippet/
|
172
|
+
# create snippet reader attribute (method)
|
173
|
+
instance.create_snippet_attribute(key, value)
|
174
|
+
when '__id'
|
175
|
+
# assign id attribute to the model
|
176
|
+
instance.id = value
|
177
|
+
when '__type', 'docid'
|
178
|
+
# do nothing
|
179
|
+
else
|
180
|
+
#assign attributes that are fetched if they match attributes in the model
|
181
|
+
if instance.respond_to?("#{key}=".to_sym)
|
182
|
+
instance.send("#{key}=", value)
|
183
|
+
end
|
184
|
+
end
|
140
185
|
end
|
186
|
+
instances << instance
|
141
187
|
end
|
188
|
+
instances
|
142
189
|
end
|
143
190
|
|
144
191
|
def constantize(klass_name)
|
145
192
|
Object.const_defined?(klass_name) ?
|
146
|
-
|
147
|
-
|
193
|
+
Object.const_get(klass_name) :
|
194
|
+
Object.const_missing(klass_name)
|
195
|
+
end
|
196
|
+
|
197
|
+
# borrowed from Rails' ActiveSupport::Inflector
|
198
|
+
def constantize(camel_cased_word)
|
199
|
+
names = camel_cased_word.split('::')
|
200
|
+
names.shift if names.empty? || names.first.empty?
|
201
|
+
|
202
|
+
constant = Object
|
203
|
+
names.each do |name|
|
204
|
+
constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
|
205
|
+
end
|
206
|
+
constant
|
207
|
+
end
|
208
|
+
|
209
|
+
def extract_setup_paginate_options(options, defaults)
|
210
|
+
# extract
|
211
|
+
paginate_options = if options[:paginate] or options[:paginate] === false
|
212
|
+
options.delete(:paginate)
|
213
|
+
else
|
214
|
+
{ :page => options.delete(:page), :per_page => options.delete(:per_page) }
|
215
|
+
end
|
216
|
+
# setup defaults and ensure we got integer values
|
217
|
+
unless paginate_options === false
|
218
|
+
paginate_options[:page] = defaults[:page] unless paginate_options[:page]
|
219
|
+
paginate_options[:per_page] = defaults[:per_page] unless paginate_options[:per_page]
|
220
|
+
paginate_options.each { |key, value| paginate_options[key] = value.to_i }
|
221
|
+
end
|
222
|
+
paginate_options
|
148
223
|
end
|
149
224
|
end
|
150
225
|
|
@@ -154,9 +229,25 @@ module Tanker
|
|
154
229
|
|
155
230
|
attr_accessor :tanker_config
|
156
231
|
|
157
|
-
def tankit(name, &block)
|
232
|
+
def tankit(name = nil, options = {}, &block)
|
158
233
|
if block_given?
|
159
|
-
|
234
|
+
raise(NoIndexName, 'Please provide an index name') if name.nil? && self.tanker_config.nil?
|
235
|
+
|
236
|
+
self.tanker_config ||= ModelConfig.new(name, options, Proc.new)
|
237
|
+
name ||= self.tanker_config.index_name
|
238
|
+
|
239
|
+
self.tanker_config.index_name = name
|
240
|
+
|
241
|
+
config = ModelConfig.new(name, block)
|
242
|
+
config.indexes.each do |key, value|
|
243
|
+
self.tanker_config.indexes << [key, value]
|
244
|
+
end
|
245
|
+
|
246
|
+
unless config.variables.empty?
|
247
|
+
self.tanker_config.variables do
|
248
|
+
instance_exec &config.variables.first
|
249
|
+
end
|
250
|
+
end
|
160
251
|
else
|
161
252
|
raise(NoBlockGiven, 'Please provide a block')
|
162
253
|
end
|
@@ -176,11 +267,12 @@ module Tanker
|
|
176
267
|
batches = []
|
177
268
|
options[:batch_size] ||= 200
|
178
269
|
records = options[:scope] ? send(options[:scope]).all : all
|
179
|
-
record_size =
|
270
|
+
record_size = 0
|
180
271
|
|
181
272
|
records.each_with_index do |model_instance, idx|
|
182
273
|
batch_num = idx / options[:batch_size]
|
183
274
|
(batches[batch_num] ||= []) << model_instance
|
275
|
+
record_size += 1
|
184
276
|
end
|
185
277
|
|
186
278
|
timer = Time.now
|
@@ -190,14 +282,21 @@ module Tanker
|
|
190
282
|
end
|
191
283
|
puts "Indexed #{record_size} #{self} records in #{Time.now - timer} seconds"
|
192
284
|
end
|
285
|
+
|
286
|
+
def tanker_parse_doc_id(result)
|
287
|
+
result['docid'].split(' ').last
|
288
|
+
end
|
193
289
|
end
|
194
290
|
|
195
291
|
class ModelConfig
|
196
|
-
|
292
|
+
attr_accessor :index_name
|
293
|
+
attr_accessor :options
|
197
294
|
|
198
|
-
def initialize(index_name, block)
|
295
|
+
def initialize(index_name, options = {}, block)
|
199
296
|
@index_name = index_name
|
297
|
+
@options = options
|
200
298
|
@indexes = []
|
299
|
+
@variables = []
|
201
300
|
@functions = {}
|
202
301
|
instance_exec &block
|
203
302
|
end
|
@@ -208,7 +307,7 @@ module Tanker
|
|
208
307
|
end
|
209
308
|
|
210
309
|
def variables(&block)
|
211
|
-
@variables
|
310
|
+
@variables << block if block
|
212
311
|
@variables
|
213
312
|
end
|
214
313
|
|
@@ -265,16 +364,27 @@ module Tanker
|
|
265
364
|
end
|
266
365
|
|
267
366
|
data[:__any] = data.values.sort_by{|v| v.to_s}.join " . "
|
268
|
-
data[:__type] =
|
367
|
+
data[:__type] = type_name
|
368
|
+
data[:__id] = self.id
|
269
369
|
|
270
370
|
data
|
271
371
|
end
|
272
372
|
|
373
|
+
#dynamically create a snippet read attribute (method)
|
374
|
+
def create_snippet_attribute(key, value)
|
375
|
+
# the method name should something_snippet not snippet_something as the api returns it
|
376
|
+
self.class.send(:define_method, "#{key.match(/snippet_(\w+)/)[1]}_snippet") do
|
377
|
+
value
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
273
381
|
def tanker_index_options
|
274
382
|
options = {}
|
275
383
|
|
276
|
-
|
277
|
-
options[:variables] =
|
384
|
+
unless tanker_variables.empty?
|
385
|
+
options[:variables] = tanker_variables.inject({}) do |hash, variables|
|
386
|
+
hash.merge(instance_exec(&variables))
|
387
|
+
end
|
278
388
|
end
|
279
389
|
|
280
390
|
options
|
@@ -282,7 +392,11 @@ module Tanker
|
|
282
392
|
|
283
393
|
# create a unique index based on the model name and unique id
|
284
394
|
def it_doc_id
|
285
|
-
|
395
|
+
type_name + ' ' + self.id.to_s
|
396
|
+
end
|
397
|
+
|
398
|
+
def type_name
|
399
|
+
tanker_config.options[:as] || self.class.name
|
286
400
|
end
|
287
401
|
end
|
288
402
|
end
|