promiscuous 0.91.0 → 0.92.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/lib/promiscuous.rb +1 -1
  3. data/lib/promiscuous/amqp.rb +1 -1
  4. data/lib/promiscuous/amqp/fake.rb +0 -3
  5. data/lib/promiscuous/amqp/file.rb +81 -0
  6. data/lib/promiscuous/amqp/null.rb +0 -3
  7. data/lib/promiscuous/cli.rb +35 -25
  8. data/lib/promiscuous/config.rb +8 -3
  9. data/lib/promiscuous/error.rb +1 -2
  10. data/lib/promiscuous/key.rb +1 -12
  11. data/lib/promiscuous/mongoid.rb +7 -0
  12. data/lib/promiscuous/publisher/context/base.rb +4 -4
  13. data/lib/promiscuous/publisher/context/middleware.rb +2 -23
  14. data/lib/promiscuous/publisher/model/ephemeral.rb +5 -1
  15. data/lib/promiscuous/publisher/model/mock.rb +9 -7
  16. data/lib/promiscuous/publisher/model/mongoid.rb +3 -1
  17. data/lib/promiscuous/publisher/operation.rb +1 -1
  18. data/lib/promiscuous/publisher/operation/atomic.rb +44 -32
  19. data/lib/promiscuous/publisher/operation/base.rb +14 -9
  20. data/lib/promiscuous/publisher/operation/ephemeral.rb +14 -0
  21. data/lib/promiscuous/publisher/operation/mongoid.rb +4 -12
  22. data/lib/promiscuous/subscriber/message_processor/base.rb +17 -1
  23. data/lib/promiscuous/subscriber/message_processor/regular.rb +94 -48
  24. data/lib/promiscuous/subscriber/model/active_record.rb +25 -0
  25. data/lib/promiscuous/subscriber/model/base.rb +17 -13
  26. data/lib/promiscuous/subscriber/model/mongoid.rb +20 -1
  27. data/lib/promiscuous/subscriber/model/observer.rb +4 -0
  28. data/lib/promiscuous/subscriber/operation/base.rb +14 -16
  29. data/lib/promiscuous/subscriber/operation/bootstrap.rb +7 -1
  30. data/lib/promiscuous/subscriber/operation/regular.rb +6 -0
  31. data/lib/promiscuous/subscriber/worker.rb +6 -2
  32. data/lib/promiscuous/subscriber/worker/eventual_destroyer.rb +85 -0
  33. data/lib/promiscuous/subscriber/worker/message.rb +9 -15
  34. data/lib/promiscuous/subscriber/worker/message_synchronizer.rb +24 -78
  35. data/lib/promiscuous/subscriber/worker/runner.rb +6 -2
  36. data/lib/promiscuous/subscriber/worker/stats.rb +11 -7
  37. data/lib/promiscuous/version.rb +1 -1
  38. metadata +66 -63
  39. data/lib/promiscuous/error/already_processed.rb +0 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b84ccb396c096ef9d2164d4400356606f90c3954
4
- data.tar.gz: 31da27f6d1163623df3014dc15a0641d6e93c0c3
3
+ metadata.gz: d772f9b9786ef7ee6787030113bf9ec5934717d5
4
+ data.tar.gz: 163c9cb61d4b359f1e6766effef9baccdfd8c229
5
5
  SHA512:
6
- metadata.gz: 2cec057b926337d6940a8d3144b88ccc3dcf200a35dc74d88f69bf4ad388d034b6e68a04dd2f270f5c581382bef3ae5985b6e4ad9e7d9cfe44babf6cdacabd02
7
- data.tar.gz: d84a82ecdcb22041d72c08e8c9fb443e77c2c9a74c49589a6d209079df7055f1adc484ca12b32d6f75f9a04e8f66bcfbaefeff020eb47b38c6fecbe3e1bc0c0b
6
+ metadata.gz: 6cacc43f226549868f963abbad29e7ba7249a1bbc1c4eb3e0b8014275935ca37e7455a73b48e36d7c0c56906308ab334c104bbf136a7cd2c7345ee976161eead
7
+ data.tar.gz: 6f59e694ed2c89e2ebe9a6c73995c4b6e4987ee1f43d171f55f3783b9698c4974aff8ef684d22dd8e652956d943081b952f8bb313d492016629b9564a1447727
data/lib/promiscuous.rb CHANGED
@@ -13,7 +13,7 @@ module Promiscuous
13
13
  require_for 'rails', 'promiscuous/railtie'
14
14
  require_for 'resque', 'promiscuous/resque'
15
15
  require_for 'sidekiq', 'promiscuous/sidekiq'
16
-
16
+ require_for 'mongoid', 'promiscuous/mongoid'
17
17
 
18
18
  extend Promiscuous::Autoload
19
19
  autoload :Common, :Publisher, :Subscriber, :Observer, :Worker, :Ephemeral,
@@ -1,6 +1,6 @@
1
1
  module Promiscuous::AMQP
2
2
  extend Promiscuous::Autoload
3
- autoload :HotBunnies, :Bunny, :Null, :Fake
3
+ autoload :HotBunnies, :Bunny, :Null, :Fake, :File
4
4
 
5
5
  LIVE_EXCHANGE = 'promiscuous'
6
6
  BOOTSTRAP_EXCHANGE = 'promiscuous.bootstrap'
@@ -38,9 +38,6 @@ class Promiscuous::AMQP::Fake
38
38
  msg && JSON.parse(msg[:payload])
39
39
  end
40
40
 
41
- def open_queue(options={}, &block)
42
- end
43
-
44
41
  module Subscriber
45
42
  def subscribe(options={}, &block)
46
43
  end
@@ -0,0 +1,81 @@
1
+ class Promiscuous::AMQP::File
2
+ def connect
3
+ end
4
+
5
+ def disconnect
6
+ end
7
+
8
+ def connected?
9
+ true
10
+ end
11
+
12
+ def new_connection(options={})
13
+ end
14
+
15
+ def publish(options={})
16
+ options[:on_confirm].try(:call)
17
+ raise NotImplemented
18
+ end
19
+
20
+ module Subscriber
21
+ attr_accessor :lock, :prefetch_wait, :num_pending
22
+
23
+ def subscribe(options={}, &block)
24
+ file_name, worker_index, num_workers = Promiscuous::Config.subscriber_amqp_url.split(':')
25
+
26
+ worker_index = worker_index.to_i
27
+ num_workers = num_workers.to_i
28
+
29
+ file = File.open(file_name, 'r')
30
+
31
+ @prefetch = Promiscuous::Config.prefetch
32
+ @num_pending = 0
33
+ @lock = Mutex.new
34
+ @prefetch_wait = ConditionVariable.new
35
+
36
+ @thread = Thread.new do
37
+ file.each_with_index do |line, i|
38
+ if num_workers > 0
39
+ next if ((i+worker_index) % num_workers) != 0
40
+ end
41
+
42
+ return if @stop
43
+
44
+ @lock.synchronize do
45
+ @prefetch_wait.wait(@lock) until @num_pending < @prefetch
46
+ @num_pending += 1
47
+ end
48
+
49
+ block.call(Metadata.new(self), line.chomp)
50
+ end
51
+
52
+ @lock.synchronize do
53
+ @prefetch_wait.wait(@lock) until @num_pending == 0
54
+ end
55
+
56
+ # will shutdown the CLI gracefully
57
+ Process.kill("SIGTERM", Process.pid)
58
+ end
59
+ end
60
+
61
+ def recover
62
+ end
63
+
64
+ def disconnect
65
+ @stop = true
66
+ end
67
+
68
+ class Metadata
69
+ def initialize(sub)
70
+ @sub = sub
71
+ end
72
+
73
+ def ack
74
+ @sub.lock.synchronize do
75
+ @sub.num_pending -= 1
76
+ @sub.prefetch_wait.signal
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -12,7 +12,4 @@ class Promiscuous::AMQP::Null
12
12
  def publish(options={})
13
13
  options[:on_confirm].try(:call)
14
14
  end
15
-
16
- def open_queue(options={}, &block)
17
- end
18
15
  end
@@ -3,22 +3,28 @@ class Promiscuous::CLI
3
3
 
4
4
  def trap_debug_signals
5
5
  Signal.trap 'SIGUSR2' do
6
- Thread.list.each do |thread|
7
- print_status '----[ Threads ]----' + '-' * (100-19)
8
- if thread.backtrace
9
- print_status "Thread #{thread} #{thread['label']}"
10
- print_status thread.backtrace.join("\n")
11
- else
12
- print_status "Thread #{thread} #{thread['label']} -- no backtrace"
6
+ # Using a thread because we cannot acquire mutexes in a trap context in
7
+ # ruby 2.0
8
+ Thread.new do
9
+ Thread.list.each do |thread|
10
+ next if Thread.current == thread
11
+
12
+ print_status '----[ Threads ]----' + '-' * (100-19)
13
+ if thread.backtrace
14
+ print_status "Thread #{thread} #{thread['label']}"
15
+ print_status thread.backtrace.join("\n")
16
+ else
17
+ print_status "Thread #{thread} #{thread['label']} -- no backtrace"
18
+ end
13
19
  end
14
- end
15
20
 
16
- if @worker && @worker.respond_to?(:message_synchronizer)
17
- if blocked_messages = @worker.message_synchronizer.try(:blocked_messages)
18
- print_status '----[ Pending Dependencies ]----' + '-' * (100-32)
19
- blocked_messages.reverse_each { |msg| print_status msg }
21
+ if @worker && @worker.respond_to?(:message_synchronizer)
22
+ if blocked_messages = @worker.message_synchronizer.try(:blocked_messages)
23
+ print_status '----[ Pending Dependencies ]----' + '-' * (100-32)
24
+ blocked_messages.reverse_each { |msg| print_status msg }
25
+ end
26
+ print_status '-' * 100
20
27
  end
21
- print_status '-' * 100
22
28
  end
23
29
  end
24
30
  end
@@ -26,14 +32,18 @@ class Promiscuous::CLI
26
32
  def trap_exit_signals
27
33
  %w(SIGTERM SIGINT).each do |signal|
28
34
  Signal.trap(signal) do
29
- print_status "Exiting..."
30
- if @stop
31
- @worker.try(:show_stop_status)
32
- else
33
- @stop = true
34
- @worker.try(:stop)
35
- @worker = nil
36
- end
35
+ # Using a thread because we cannot acquire mutexes in a trap context in
36
+ # ruby 2.0
37
+ Thread.new do
38
+ print_status "Exiting..."
39
+ if @stop
40
+ @worker.try(:show_stop_status)
41
+ else
42
+ @stop = true
43
+ @worker.try(:stop)
44
+ @worker = nil
45
+ end
46
+ end.join
37
47
  end
38
48
  end
39
49
  end
@@ -144,12 +154,12 @@ class Promiscuous::CLI
144
154
  Promiscuous::Config.no_deps = true
145
155
  end
146
156
 
147
- opts.on "-l", "--require FILE", "File to require to load your app. Don't worry about it with rails" do |file|
148
- options[:require] = file
157
+ opts.on "-x", "--ignore-exceptions", "Ignore exceptions and continue to process messages" do
158
+ Promiscuous::Config.ignore_exceptions = true
149
159
  end
150
160
 
151
- opts.on "-r", "--recovery", "Run in recovery mode" do
152
- Promiscuous::Config.recovery = true
161
+ opts.on "-l", "--require FILE", "File to require to load your app. Don't worry about it with rails" do |file|
162
+ options[:require] = file
153
163
  end
154
164
 
155
165
  opts.on "-p", "--prefetch [NUM]", "Number of messages to prefetch" do |prefetch|
@@ -3,9 +3,10 @@ module Promiscuous::Config
3
3
  :publisher_amqp_url, :subscriber_amqp_url, :publisher_exchange,
4
4
  :subscriber_exchanges, :queue_name, :queue_options, :redis_url,
5
5
  :redis_urls, :redis_stats_url, :stats_interval,
6
- :socket_timeout, :heartbeat, :no_deps, :hash_size, :recovery,
6
+ :socket_timeout, :heartbeat, :no_deps, :hash_size,
7
7
  :prefetch, :recovery_timeout, :logger, :subscriber_threads,
8
- :version_field, :error_notifier, :recovery_on_boot
8
+ :version_field, :error_notifier, :recovery_on_boot,
9
+ :on_stats, :ignore_exceptions, :consistency, :max_retries, :generation
9
10
 
10
11
  def self.backend=(value)
11
12
  @@backend = value
@@ -53,7 +54,6 @@ module Promiscuous::Config
53
54
  self.heartbeat ||= 60
54
55
  self.no_deps ||= false
55
56
  self.hash_size ||= 2**20 # one million keys ~ 200Mb.
56
- self.recovery ||= false
57
57
  self.prefetch ||= self.bootstrap ? 10000000 : 1000
58
58
  self.recovery_timeout ||= 10
59
59
  self.logger ||= defined?(Rails) ? Rails.logger : Logger.new(STDERR).tap { |l| l.level = Logger::WARN }
@@ -61,6 +61,11 @@ module Promiscuous::Config
61
61
  self.error_notifier ||= proc {}
62
62
  self.version_field ||= '_v'
63
63
  self.recovery_on_boot = true if self.recovery_on_boot.nil?
64
+ self.on_stats ||= proc { |rate, latency| }
65
+ self.ignore_exceptions ||= false
66
+ self.consistency ||= :eventual
67
+ self.max_retries ||= 10
68
+ self.generation ||= 1
64
69
  end
65
70
 
66
71
  def self.configure(&block)
@@ -1,6 +1,5 @@
1
1
  module Promiscuous::Error
2
2
  extend Promiscuous::Autoload
3
3
  autoload :Base, :Connection, :Publisher, :Subscriber, :Recovery,
4
- :Dependency, :MissingContext, :AlreadyProcessed,
5
- :LockUnavailable, :LostLock
4
+ :Dependency, :MissingContext, :LockUnavailable, :LostLock
6
5
  end
@@ -1,21 +1,10 @@
1
1
  class Promiscuous::Key
2
- def initialize(role, nodes=[], no_join=nil)
2
+ def initialize(role, nodes=[])
3
3
  @role = role
4
4
  @nodes = nodes
5
- @no_join = no_join
6
5
  end
7
6
 
8
7
  def join(*nodes)
9
- # --- backward compatiblity code ---
10
- # TODO remove code
11
- if nodes == ['global', nil, nil]
12
- return self.class.new(@role, @nodes + nodes, :no_join)
13
- end
14
- if @no_join
15
- return self.class.new(@role, @nodes)
16
- end
17
- # --- backward compatiblity code ---
18
-
19
8
  self.class.new(@role, @nodes + nodes)
20
9
  end
21
10
 
@@ -0,0 +1,7 @@
1
+ class Moped::BSON::ObjectId
2
+ # No {"$oid": "123"}, it's horrible.
3
+ # TODO Document this shit.
4
+ def to_json(*args)
5
+ "\"#{to_s}\""
6
+ end
7
+ end
@@ -24,11 +24,11 @@ class Promiscuous::Publisher::Context::Base
24
24
  end
25
25
  end
26
26
 
27
- attr_accessor :name, :read_operations, :extra_dependencies, :current_user_id
27
+ attr_accessor :name, :read_operations, :extra_dependencies, :current_user
28
28
 
29
- def initialize(*args)
30
- @name = args[0].try(:to_s) || 'anonymous'
31
- @current_user_id = args[1]
29
+ def initialize(name=nil, options={})
30
+ @name = name.try(:to_s) || 'anonymous'
31
+ @current_user = options[:current_user]
32
32
  @read_operations = []
33
33
  @extra_dependencies = []
34
34
  @transaction_managers = {}
@@ -4,12 +4,8 @@ class Promiscuous::Publisher::Context::Middleware < Promiscuous::Publisher::Cont
4
4
 
5
5
  def process_action(*args)
6
6
  full_name = "#{self.class.controller_path}/#{self.action_name}"
7
- current_user_id = self.respond_to?(:current_user) ? self.current_user.try(:id) : nil
8
- Promiscuous::Publisher::Context::Middleware.with_context(full_name, current_user_id) { super }
9
- end
10
-
11
- def render(*args)
12
- Promiscuous::Publisher::Context::Middleware.without_context { super }
7
+ current_user = self.current_user if self.respond_to?(:current_user)
8
+ Promiscuous::Publisher::Context::Middleware.with_context(full_name, :current_user => current_user) { super }
13
9
  end
14
10
  end
15
11
 
@@ -26,23 +22,6 @@ class Promiscuous::Publisher::Context::Middleware < Promiscuous::Publisher::Cont
26
22
  Promiscuous.disabled = old_disabled
27
23
  end
28
24
 
29
- def self.without_context
30
- # This is different from the method without_promiscuous in convenience.rb
31
- # That's used for render() and things that are *not* supposed to write.
32
- # We actually force promiscuous to instrument queries, and make sure that
33
- # we don't do any writes we shouldn't.
34
- old_disabled, Promiscuous.disabled = Promiscuous.disabled?, false
35
- old_current, self.current = self.current, nil
36
- yield
37
- rescue Exception => e
38
- $promiscuous_last_exception = e if e.is_a? Promiscuous::Error::Base
39
- pretty_print_exception(e) unless e.is_a? ActionView::MissingTemplate
40
- raise e
41
- ensure
42
- self.current = old_current
43
- Promiscuous.disabled = old_disabled
44
- end
45
-
46
25
  def self.pretty_print_exception(e)
47
26
  return if $promiscuous_pretty_print_exception_once == :disable || ENV['RAILS_ENV'] == 'production'
48
27
  return if e.is_a?(SystemExit)
@@ -33,13 +33,17 @@ module Promiscuous::Publisher::Model::Ephemeral
33
33
  operation = :update unless self.new_record
34
34
  operation = :destroy if self.destroyed
35
35
 
36
- Promiscuous::Publisher::Operation::Atomic.new(:instance => self, :operation => operation).execute {}
36
+ save_operation(operation)
37
37
 
38
38
  self.new_record = false
39
39
  true
40
40
  end
41
41
  alias :save! :save
42
42
 
43
+ def save_operation(operation)
44
+ Promiscuous::Publisher::Operation::Ephemeral.new(:instance => self, :operation => operation).execute
45
+ end
46
+
43
47
  def update_attributes(attrs)
44
48
  attrs.each { |attr, value| __send__("#{attr}=", value) }
45
49
  save
@@ -22,15 +22,17 @@ module Promiscuous::Publisher::Model::Mock
22
22
  end
23
23
  end
24
24
 
25
- class PromiscuousMethods
26
- include Promiscuous::Publisher::Model::Base::PromiscuousMethodsBase
27
- include Promiscuous::Publisher::Model::Ephemeral::PromiscuousMethodsEphemeral
25
+ def save_operation(operation)
26
+ payload = nil
28
27
 
29
- def sync(options={}, &block)
30
- payload[:operations] = [self.payload.merge(:operation => options[:operation] || :update)]
31
- payload[:app] = self.class.mock_options[:from]
32
- Promiscuous::Subscriber::Worker::Message.new(MultiJson.dump(payload)).process
28
+ Promiscuous::Publisher::Context::Middleware.with_context("mocking #{self.class}") do
29
+ op = Promiscuous::Publisher::Operation::Ephemeral.new(:instance => self, :operation => operation)
30
+ # TODO FIX the mocks to populate app name, also we need to hook before the
31
+ # json dump.
32
+ payload = op.generate_payload
33
33
  end
34
+
35
+ Promiscuous::Subscriber::Worker::Message.new(payload).process
34
36
  end
35
37
 
36
38
  module ClassMethods
@@ -23,7 +23,9 @@ module Promiscuous::Publisher::Model::Mongoid
23
23
 
24
24
  def sync(options={}, &block)
25
25
  raise "Use promiscuous.sync on the parent instance" if @instance.embedded?
26
- super
26
+
27
+ # We can use the ephemeral because both are mongoid and ephemerals are atomic operations.
28
+ Promiscuous::Publisher::Operation::Ephemeral.new(:instance => @instance, :operation => :update).execute
27
29
  end
28
30
 
29
31
  def attribute(attr)
@@ -1,4 +1,4 @@
1
1
  module Promiscuous::Publisher::Operation
2
2
  extend Promiscuous::Autoload
3
- autoload :Base, :Transaction, :Atomic, :NonPersistent, :ProxyForQuery
3
+ autoload :Base, :Transaction, :Atomic, :NonPersistent, :ProxyForQuery, :Ephemeral
4
4
  end
@@ -35,10 +35,47 @@ class Promiscuous::Publisher::Operation::Atomic < Promiscuous::Publisher::Operat
35
35
  end
36
36
  end
37
37
 
38
- def execute_instrumented(query)
39
- raise if @instance.nil? # assert()
38
+ def do_database_query(query)
39
+ case operation
40
+ when :create
41
+ # We don't stash the version in the document as we can't have races
42
+ # on the same document.
43
+ when :update
44
+ increment_version_in_document
45
+ # We are now in the possession of an instance that matches the original
46
+ # selector. We need to make sure the db query will operate on it,
47
+ # instead of the original selector.
48
+ use_id_selector(:use_atomic_version_selector => true)
49
+ # We need to use an atomic versioned selector to make sure that
50
+ # if we lose the lock for a long period of time, we don't mess up
51
+ # the record. Perhaps the operation has been recovered a while ago.
52
+ when :destroy
53
+ use_id_selector
54
+ end
55
+
56
+ # The driver is responsible to set instance to the appropriate value.
57
+ query.call_and_remember_result(:instrumented)
58
+
59
+ if query.failed?
60
+ # If we get an network failure, we should retry later.
61
+ return if recoverable_failure?(query.exception)
62
+ @instance = nil
63
+ end
64
+ end
65
+
66
+ def yell_about_missing_instance
67
+ err = "Cannot find document. Database had a dataloss?. Proceeding anyways. #{@recovery_data}"
68
+ e = Promiscuous::Error::Recovery.new(err)
69
+ Promiscuous.warn "[recovery] #{e}"
70
+ Promiscuous::Config.error_notifier.call(e)
71
+ end
40
72
 
41
- unless self.recovering?
73
+ def execute_instrumented(query)
74
+ if recovering?
75
+ # The DB died or something. We cannot find our instance any more :(
76
+ # this is a problem, but we need to publish.
77
+ yell_about_missing_instance if @instance.nil?
78
+ else
42
79
  generate_read_dependencies
43
80
  acquire_op_lock
44
81
 
@@ -75,32 +112,7 @@ class Promiscuous::Publisher::Operation::Atomic < Promiscuous::Publisher::Operat
75
112
  # documents are missing on our side to be able to resend the destroy
76
113
  # message.
77
114
 
78
- case operation
79
- when :create
80
- # We don't stash the version in the document as we can't have races
81
- # on the same document.
82
- when :update
83
- stash_version_in_document(@committed_write_deps.first.version)
84
- # We are now in the possession of an instance that matches the original
85
- # selector. We need to make sure the db query will operate on it,
86
- # instead of the original selector.
87
- use_id_selector(:use_atomic_version_selector => true)
88
- # We need to use an atomic versioned selector to make sure that
89
- # if we lose the lock for a long period of time, we don't mess up
90
- # the record. Perhaps the operation has been recovered a while ago.
91
- when :destroy
92
- use_id_selector
93
- end
94
-
95
- # The driver is responsible to set instance to the appropriate value.
96
- query.call_and_remember_result(:instrumented)
97
-
98
- if query.failed?
99
- # If we get an network failure, we should retry later.
100
- return if recoverable_failure?(query.exception)
101
- @instance = nil
102
- end
103
-
115
+ do_database_query(query) unless @instance.nil?
104
116
  # We take a timestamp right after the write is performed because latency
105
117
  # measurements are performed on the subscriber.
106
118
  record_timestamp
@@ -141,6 +153,7 @@ class Promiscuous::Publisher::Operation::Atomic < Promiscuous::Publisher::Operat
141
153
  def fetch_instance
142
154
  # This method is overridden to use the original query selector.
143
155
  # Should return nil if the instance is not found.
156
+ @instance.reload if @instance.respond_to?(:reload)
144
157
  @instance
145
158
  end
146
159
 
@@ -148,9 +161,8 @@ class Promiscuous::Publisher::Operation::Atomic < Promiscuous::Publisher::Operat
148
161
  @instance = fetch_instance
149
162
  end
150
163
 
151
- def stash_version_in_document(version)
152
- # Overridden to update the query to set the version field with:
153
- # instance[Promiscuous::Config.version_field] = version
164
+ def increment_version_in_document
165
+ # Overridden to increment version field in the query
154
166
  end
155
167
 
156
168
  def use_id_selector(options={})