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 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 keep in sync with indextank with ease. This gem has a simple
5
- but powerful approach to integrating IndexTank to any orm. The gem
6
- also supports pagination using the +will_paginate/collection+.
7
-
8
- There are very few things needed to integrate Tanker to any orm.
9
- Basically the orm instance must respond to +id+ and any attributes
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 modesl
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 a fancier configuration depending on
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
- in the IndexTank Dashboard
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
- <% will_paginate(@topics) %>
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.0.0
1
+ 1.1.0
@@ -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
- @@configuration = new_configuration
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
@@ -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
- Tanker.configuration = {:url => config.index_tank_url }
9
+ setup_tanker_configuration
9
10
  end
10
-
11
+
11
12
  config.after_initialize do
12
- Tanker.configuration = {:url => config.index_tank_url }
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
@@ -11,22 +11,26 @@ module Tanker
11
11
 
12
12
  def clear_all_indexes
13
13
  get_available_indexes.each do |index_name|
14
- begin
15
- index = Tanker.api.get_index(index_name)
14
+ clear_index(index_name)
15
+ end
16
+ end
16
17
 
17
- if index.exists?
18
- puts "Deleting #{index_name} index"
19
- index.delete_index()
20
- end
21
- puts "Creating #{index_name} index"
22
- index.create_index()
23
- puts "Waiting for the index to be ready"
24
- while not index.running?
25
- sleep 0.5
26
- end
27
- rescue => e
28
- puts "There was an error clearing or creating the #{index_name} index: #{e.to_s}"
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
- query = "__any:(#{query.to_s}) __type:(#{models.map(&:name).join(' OR ')})"
100
- options = { :start => per_page * (page - 1), :len => per_page }.merge(options)
101
- results = index.search(query, options)
102
-
103
- @entries = WillPaginate::Collection.create(page, per_page) do |pager|
104
- # inject the result array into the paginated collection:
105
- pager.replace instantiate_results(results)
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
- unless pager.total_entries
108
- # the pager didn't manage to guess the total count, do it manually
109
- pager.total_entries = results["matches"]
110
- end
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 instantiate_results(index_result)
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, id = result["docid"].split(" ", 2)
128
+ model = result["__type"]
129
+ id = constantize(model).tanker_parse_doc_id(result)
122
130
  acc[model] ||= []
123
- acc[model] << id.to_i
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
- if 1 == id_map.size # check for simple case, just one model involved
128
- klass = constantize(id_map.keys.first)
129
- # eager-load and return just this model's records
130
- klass.find(id_map.values.flatten)
131
- else # complex case, multiple models involved
132
- id_map.each do |klass, ids|
133
- # replace the id list with an eager-loaded list of records for this model
134
- id_map[klass] = constantize(klass).find(ids)
135
- end
136
- # return them in order
137
- results.map do |result|
138
- model, id = result["docid"].split(" ", 2)
139
- id_map[model].detect {|record| id.to_i == record.id }
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
- Object.const_get(klass_name) :
147
- Object.const_missing(klass_name)
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
- self.tanker_config = ModelConfig.new(name, block)
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 = records.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
- attr_reader :index_name
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 = block if block
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] = self.class.name
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
- if tanker_variables
277
- options[:variables] = instance_exec(&tanker_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
- self.class.name + ' ' + self.id.to_s
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