activerecord-turntable 1.1.2 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (144) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -0
  3. data/.travis.yml +3 -5
  4. data/CHANGELOG.md +70 -0
  5. data/Guardfile +7 -5
  6. data/README.md +490 -0
  7. data/Rakefile +37 -22
  8. data/activerecord-turntable.gemspec +37 -34
  9. data/gemfiles/rails4_0.gemfile +6 -0
  10. data/gemfiles/rails4_1.gemfile +6 -0
  11. data/lib/active_record/turntable/active_record_ext/abstract_adapter.rb +14 -29
  12. data/lib/active_record/turntable/active_record_ext/activerecord_import_ext.rb +45 -0
  13. data/lib/active_record/turntable/active_record_ext/acts_as_archive_extension.rb +21 -0
  14. data/lib/active_record/turntable/active_record_ext/association.rb +85 -0
  15. data/lib/active_record/turntable/active_record_ext/association_preloader.rb +37 -0
  16. data/lib/active_record/turntable/active_record_ext/clever_load.rb +33 -76
  17. data/lib/active_record/turntable/active_record_ext/connection_handler_extension.rb +31 -0
  18. data/lib/active_record/turntable/active_record_ext/database_tasks.rb +81 -0
  19. data/lib/active_record/turntable/active_record_ext/fixtures.rb +54 -42
  20. data/lib/active_record/turntable/active_record_ext/locking_optimistic.rb +101 -0
  21. data/lib/active_record/turntable/active_record_ext/log_subscriber.rb +28 -46
  22. data/lib/active_record/turntable/active_record_ext/migration_proxy.rb +7 -0
  23. data/lib/active_record/turntable/active_record_ext/persistence.rb +96 -94
  24. data/lib/active_record/turntable/active_record_ext/relation.rb +31 -0
  25. data/lib/active_record/turntable/active_record_ext/schema_dumper.rb +18 -28
  26. data/lib/active_record/turntable/active_record_ext/transactions.rb +9 -3
  27. data/lib/active_record/turntable/active_record_ext.rb +26 -11
  28. data/lib/active_record/turntable/algorithm/base.rb +1 -1
  29. data/lib/active_record/turntable/algorithm/range_bsearch_algorithm.rb +1 -1
  30. data/lib/active_record/turntable/algorithm.rb +7 -3
  31. data/lib/active_record/turntable/base.rb +67 -14
  32. data/lib/active_record/turntable/cluster.rb +46 -2
  33. data/lib/active_record/turntable/config.rb +1 -1
  34. data/lib/active_record/turntable/connection_proxy/mixable.rb +7 -29
  35. data/lib/active_record/turntable/connection_proxy.rb +61 -72
  36. data/lib/active_record/turntable/error.rb +5 -6
  37. data/lib/active_record/turntable/helpers.rb +5 -1
  38. data/lib/active_record/turntable/migration.rb +9 -49
  39. data/lib/active_record/turntable/mixer/fader/calculate_shards_sum_result.rb +13 -2
  40. data/lib/active_record/turntable/mixer/fader/select_shards_merge_result.rb +17 -6
  41. data/lib/active_record/turntable/mixer/fader/specified_shard.rb +3 -1
  42. data/lib/active_record/turntable/mixer/fader.rb +12 -10
  43. data/lib/active_record/turntable/mixer.rb +59 -29
  44. data/lib/active_record/turntable/plugin.rb +6 -0
  45. data/lib/active_record/turntable/pool_proxy.rb +12 -19
  46. data/lib/active_record/turntable/rack/query_cache.rb +20 -23
  47. data/lib/active_record/turntable/rack.rb +4 -2
  48. data/lib/active_record/turntable/railtie.rb +4 -3
  49. data/lib/active_record/turntable/railties/databases.rake +81 -122
  50. data/lib/active_record/turntable/seq_shard.rb +1 -1
  51. data/lib/active_record/turntable/sequencer/api.rb +1 -1
  52. data/lib/active_record/turntable/sequencer/barrage.rb +28 -0
  53. data/lib/active_record/turntable/sequencer.rb +27 -9
  54. data/lib/active_record/turntable/shard.rb +2 -2
  55. data/lib/active_record/turntable/sql_tree_patch.rb +1 -1
  56. data/lib/active_record/turntable/version.rb +1 -1
  57. data/lib/active_record/turntable.rb +26 -16
  58. data/lib/generators/templates/turntable.yml +9 -7
  59. data/spec/active_record/turntable/active_record_ext/association_preloader_spec.rb +78 -0
  60. data/spec/active_record/turntable/active_record_ext/association_spec.rb +72 -0
  61. data/spec/active_record/turntable/active_record_ext/clever_load_spec.rb +25 -46
  62. data/spec/active_record/turntable/active_record_ext/locking_optimistic_spec.rb +28 -0
  63. data/spec/active_record/turntable/active_record_ext/persistence_spec.rb +46 -25
  64. data/spec/active_record/turntable/algorithm/range_algorithm_spec.rb +4 -4
  65. data/spec/active_record/turntable/algorithm/range_bsearch_algorithm_spec.rb +35 -0
  66. data/spec/active_record/turntable/algorithm_spec.rb +28 -12
  67. data/spec/active_record/turntable/base_spec.rb +1 -1
  68. data/spec/active_record/turntable/cluster_spec.rb +27 -5
  69. data/spec/active_record/turntable/config_spec.rb +2 -2
  70. data/spec/active_record/turntable/connection_proxy_spec.rb +112 -45
  71. data/spec/active_record/turntable/finder_spec.rb +24 -11
  72. data/spec/active_record/turntable/mixer_spec.rb +21 -21
  73. data/spec/active_record/turntable/rack/query_cache_spec.rb +19 -0
  74. data/spec/active_record/turntable/sequencer/api_spec.rb +38 -0
  75. data/spec/active_record/turntable/sequencer/barrage_spec.rb +22 -0
  76. data/spec/active_record/turntable/sequencer/mysql_spec.rb +22 -0
  77. data/spec/active_record/turntable/shard_spec.rb +1 -1
  78. data/spec/active_record/turntable/transaction_spec.rb +35 -0
  79. data/spec/active_record/turntable_spec.rb +4 -4
  80. data/spec/config/database.yml +24 -34
  81. data/spec/config/turntable.yml +18 -1
  82. data/spec/fabricators/turntable_fabricator.rb +0 -2
  83. data/spec/models/card.rb +3 -0
  84. data/spec/models/cards_user.rb +10 -0
  85. data/spec/models/cards_users_histories.rb +7 -0
  86. data/spec/models/events_users_history.rb +7 -0
  87. data/spec/models/user.rb +7 -0
  88. data/spec/models/user_status.rb +6 -0
  89. data/spec/spec_helper.rb +10 -4
  90. data/spec/support/matchers/be_saved_to.rb +6 -0
  91. data/spec/support/turntable_helper.rb +29 -0
  92. metadata +124 -74
  93. data/README.rdoc +0 -294
  94. data/gemfiles/rails3_0.gemfile +0 -7
  95. data/gemfiles/rails3_1.gemfile +0 -6
  96. data/gemfiles/rails3_2.gemfile +0 -6
  97. data/lib/active_record/turntable/compatible.rb +0 -19
  98. data/sample_app/.gitignore +0 -16
  99. data/sample_app/Gemfile +0 -41
  100. data/sample_app/README.rdoc +0 -261
  101. data/sample_app/Rakefile +0 -7
  102. data/sample_app/app/assets/images/rails.png +0 -0
  103. data/sample_app/app/assets/javascripts/application.js +0 -15
  104. data/sample_app/app/assets/stylesheets/application.css +0 -13
  105. data/sample_app/app/controllers/application_controller.rb +0 -3
  106. data/sample_app/app/helpers/application_helper.rb +0 -2
  107. data/sample_app/app/mailers/.gitkeep +0 -0
  108. data/sample_app/app/models/.gitkeep +0 -0
  109. data/sample_app/app/models/user.rb +0 -4
  110. data/sample_app/app/views/layouts/application.html.erb +0 -14
  111. data/sample_app/config/application.rb +0 -65
  112. data/sample_app/config/boot.rb +0 -6
  113. data/sample_app/config/database.yml +0 -70
  114. data/sample_app/config/environment.rb +0 -5
  115. data/sample_app/config/environments/development.rb +0 -37
  116. data/sample_app/config/environments/production.rb +0 -67
  117. data/sample_app/config/environments/test.rb +0 -37
  118. data/sample_app/config/initializers/backtrace_silencers.rb +0 -7
  119. data/sample_app/config/initializers/inflections.rb +0 -15
  120. data/sample_app/config/initializers/mime_types.rb +0 -5
  121. data/sample_app/config/initializers/secret_token.rb +0 -7
  122. data/sample_app/config/initializers/session_store.rb +0 -8
  123. data/sample_app/config/initializers/wrap_parameters.rb +0 -14
  124. data/sample_app/config/locales/en.yml +0 -5
  125. data/sample_app/config/routes.rb +0 -58
  126. data/sample_app/config/turntable.yml +0 -64
  127. data/sample_app/config.ru +0 -4
  128. data/sample_app/db/migrate/20120316073058_create_users.rb +0 -11
  129. data/sample_app/db/seeds.rb +0 -7
  130. data/sample_app/lib/assets/.gitkeep +0 -0
  131. data/sample_app/lib/tasks/.gitkeep +0 -0
  132. data/sample_app/log/.gitkeep +0 -0
  133. data/sample_app/public/404.html +0 -26
  134. data/sample_app/public/422.html +0 -26
  135. data/sample_app/public/500.html +0 -25
  136. data/sample_app/public/favicon.ico +0 -0
  137. data/sample_app/public/index.html +0 -241
  138. data/sample_app/public/robots.txt +0 -5
  139. data/sample_app/script/rails +0 -6
  140. data/sample_app/vendor/assets/javascripts/.gitkeep +0 -0
  141. data/sample_app/vendor/assets/stylesheets/.gitkeep +0 -0
  142. data/sample_app/vendor/plugins/.gitkeep +0 -0
  143. data/spec/test_models.rb +0 -27
  144. data/spec/turntable_helper.rb +0 -29
@@ -0,0 +1,31 @@
1
+ module ActiveRecord::Turntable
2
+ module ActiveRecordExt
3
+ module Relation
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ version = ActiveRecord::VERSION::STRING
8
+ if version >= '4.1'
9
+ if version < '4.1.2'
10
+ alias_method :_update_record_without_turntable, :update_record
11
+ alias_method :update_record, :_update_record_with_turntable
12
+ else
13
+ alias_method_chain :_update_record, :turntable
14
+ end
15
+ end
16
+ end
17
+
18
+ def _update_record_with_turntable(values, id, id_was, turntable_scope = nil) # :nodoc:
19
+ substitutes, binds = substitute_values values
20
+ condition_scope = @klass.unscoped.where(@klass.arel_table[@klass.primary_key].eq(id_was || id))
21
+ condition_scope = condition_scope.merge(turntable_scope) if turntable_scope
22
+ um = condition_scope.arel.compile_update(substitutes, @klass.primary_key)
23
+
24
+ @klass.connection.update(
25
+ um,
26
+ 'SQL',
27
+ binds)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -22,51 +22,41 @@ module ActiveRecord::Turntable
22
22
  pk = @connection.primary_key(table)
23
23
  end
24
24
 
25
- # turntable sequencer dump
26
25
  if table =~ /\A(.*)_id_seq\z/
27
- tbl.print " create_sequence_for #{$1.inspect}"
26
+ tbl.print " create_sequence_for #{remove_prefix_and_suffix($1).inspect}"
28
27
  else
29
- tbl.print " create_table #{table.inspect}"
28
+ tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
30
29
  end
31
-
32
- if columns.detect { |c| c.name == pk }
30
+ pkcol = columns.detect { |c| c.name == pk }
31
+ if pkcol
33
32
  if pk != 'id'
34
- tbl.print %Q(, :primary_key => "#{pk}")
33
+ tbl.print %Q(, primary_key: "#{pk}")
34
+ elsif pkcol.sql_type == 'uuid'
35
+ tbl.print ", id: :uuid"
36
+ tbl.print %Q(, default: "#{pkcol.default_function}") if pkcol.default_function
35
37
  end
36
38
  else
37
- tbl.print ", :id => false"
39
+ tbl.print ", id: false"
38
40
  end
39
- tbl.print ", :force => true"
41
+ tbl.print ", force: true"
40
42
  tbl.puts " do |t|"
41
43
 
42
44
  # then dump all non-primary key columns
43
45
  column_specs = columns.map do |column|
44
- raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" if @types[column.type].nil?
46
+ raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type)
45
47
  next if column.name == pk
46
- spec = {}
47
- spec[:name] = column.name.inspect
48
-
49
- # AR has an optimisation which handles zero-scale decimals as integers. This
50
- # code ensures that the dumper still dumps the column as a decimal.
51
- spec[:type] = if column.type == :integer && [/^numeric/, /^decimal/].any? { |e| e.match(column.sql_type) }
52
- 'decimal'
53
- else
54
- column.type.to_s
55
- end
56
- spec[:limit] = column.limit.inspect if column.limit != @types[column.type][:limit] && spec[:type] != 'decimal'
57
- spec[:precision] = column.precision.inspect if column.precision
58
- spec[:scale] = column.scale.inspect if column.scale
59
- spec[:null] = 'false' unless column.null
60
- spec[:default] = default_string(column.default) if column.has_default?
61
- (spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k.inspect} => ")}
62
- spec
48
+ @connection.column_spec(column, @types)
63
49
  end.compact
64
50
 
65
51
  # find all migration keys used in this table
66
- keys = [:name, :limit, :precision, :scale, :default, :null] & column_specs.map{ |k| k.keys }.flatten
52
+ keys = @connection.migration_keys
67
53
 
68
54
  # figure out the lengths for each column based on above keys
69
- lengths = keys.map{ |key| column_specs.map{ |spec| spec[key] ? spec[key].length + 2 : 0 }.max }
55
+ lengths = keys.map { |key|
56
+ column_specs.map { |spec|
57
+ spec[key] ? spec[key].length + 2 : 0
58
+ }.max
59
+ }
70
60
 
71
61
  # the string we're going to sprintf our values against, with standardized column widths
72
62
  format_string = lengths.map{ |len| "%-#{len}s" }
@@ -5,12 +5,18 @@ module ActiveRecord::Turntable
5
5
  if self.class.turntable_enabled?
6
6
  status = nil
7
7
  if self.new_record? and self.turntable_shard_key.to_s == self.class.primary_key and
8
- self.id.nil? and connection.prefetch_primary_key?(self.class.table_name)
9
- self.id = connection.next_sequence_value(self.class.sequence_name)
8
+ self.id.nil? and self.class.connection.prefetch_primary_key?(self.class.table_name)
9
+ self.id = self.class.connection.next_sequence_value(self.class.sequence_name)
10
10
  end
11
11
  self.class.connection.shards_transaction([self.turntable_shard]) do
12
12
  add_to_transaction
13
- status = yield
13
+ begin
14
+ status = yield
15
+ rescue ActiveRecord::Rollback
16
+ @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
17
+ status = nil
18
+ end
19
+
14
20
  raise ActiveRecord::Rollback unless status
15
21
  end
16
22
  status
@@ -1,14 +1,22 @@
1
1
  module ActiveRecord::Turntable
2
2
  module ActiveRecordExt
3
3
  extend ActiveSupport::Concern
4
+ extend ActiveSupport::Autoload
4
5
 
5
- autoload :AbstractAdapter, 'active_record/turntable/active_record_ext/abstract_adapter'
6
- autoload :CleverLoad, 'active_record/turntable/active_record_ext/clever_load'
7
- autoload :LogSubscriber, 'active_record/turntable/active_record_ext/log_subscriber'
8
- autoload :Persistence, 'active_record/turntable/active_record_ext/persistence'
9
- autoload :SchemaDumper, 'active_record/turntable/active_record_ext/schema_dumper'
10
- autoload :Sequencer, 'active_record/turntable/active_record_ext/sequencer'
11
- autoload :Transactions, 'active_record/turntable/active_record_ext/transactions'
6
+ eager_autoload do
7
+ autoload :AbstractAdapter
8
+ autoload :CleverLoad
9
+ autoload :ConnectionHandlerExtension
10
+ autoload :LogSubscriber
11
+ autoload :Persistence
12
+ autoload :SchemaDumper
13
+ autoload :Sequencer
14
+ autoload :Relation
15
+ autoload :Transactions
16
+ autoload :AssociationPreloader
17
+ autoload :Association
18
+ autoload :LockingOptimistic
19
+ end
12
20
 
13
21
  included do
14
22
  include Transactions
@@ -16,11 +24,18 @@ module ActiveRecord::Turntable
16
24
  ActiveRecord::ConnectionAdapters::AbstractAdapter.send(:include, AbstractAdapter)
17
25
  ActiveRecord::LogSubscriber.send(:include, LogSubscriber)
18
26
  ActiveRecord::Persistence.send(:include, Persistence)
19
- ActiveRecord::Relation.send(:include, CleverLoad)
20
- ActiveRecord::VERSION::STRING < '3.1' ?
21
- ActiveRecord::Migration.extend(ActiveRecord::Turntable::Migration) :
22
- ActiveRecord::Migration.send(:include, ActiveRecord::Turntable::Migration)
27
+ ActiveRecord::Locking::Optimistic.send(:include, LockingOptimistic)
28
+ ActiveRecord::Relation.send(:include, CleverLoad, Relation)
29
+ ActiveRecord::Migration.send(:include, ActiveRecord::Turntable::Migration)
30
+ ActiveRecord::ConnectionAdapters::ConnectionHandler.instance_exec do
31
+ include ConnectionHandlerExtension
32
+ end
33
+ ActiveRecord::Associations::Preloader::Association.send(:include, AssociationPreloader)
34
+ ActiveRecord::Associations::Association.send(:include, Association)
23
35
  require 'active_record/turntable/active_record_ext/fixtures'
36
+ require 'active_record/turntable/active_record_ext/migration_proxy'
37
+ require 'active_record/turntable/active_record_ext/activerecord_import_ext'
38
+ require 'active_record/turntable/active_record_ext/acts_as_archive_extension'
24
39
  end
25
40
  end
26
41
  end
@@ -5,7 +5,7 @@ module ActiveRecord::Turntable::Algorithm
5
5
  end
6
6
 
7
7
  def calculate(key)
8
- raise ActiveRecord::Turntable::NotImplementedError, "not implemented"
8
+ raise NotImplementedError, "not implemented"
9
9
  end
10
10
  end
11
11
  end
@@ -15,7 +15,7 @@ module ActiveRecord::Turntable::Algorithm
15
15
  end
16
16
 
17
17
  def calculate_idx(key)
18
- @config["shards"].bsearch_lower_boundary { |h|
18
+ @config["shards"].bsearch_upper_boundary { |h|
19
19
  h["less_than"] <=> key
20
20
  }
21
21
  end
@@ -1,7 +1,11 @@
1
1
  module ActiveRecord::Turntable
2
2
  module Algorithm
3
- autoload :Base, "active_record/turntable/algorithm/base"
4
- autoload :RangeAlgorithm, "active_record/turntable/algorithm/range_algorithm"
5
- autoload :RangeBsearchAlgorithm, "active_record/turntable/algorithm/range_bsearch_algorithm"
3
+ extend ActiveSupport::Autoload
4
+
5
+ eager_autoload do
6
+ autoload :Base
7
+ autoload :RangeAlgorithm
8
+ autoload :RangeBsearchAlgorithm
9
+ end
6
10
  end
7
11
  end
@@ -3,15 +3,15 @@ module ActiveRecord::Turntable
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
- include Compatible
7
- class_attribute :turntable_connections,
6
+ class_attribute :turntable_connections, :turntable_clusters,
8
7
  :turntable_enabled, :turntable_sequencer_enabled
9
8
 
10
9
  self.turntable_connections = {}
10
+ self.turntable_clusters = Hash.new {|h,k| h[k]={}}
11
11
  self.turntable_enabled = false
12
12
  self.turntable_sequencer_enabled = false
13
13
  class << self
14
- delegate :shards_transaction, :to => :connection
14
+ delegate :shards_transaction, :with_all, :to => :connection
15
15
  end
16
16
  end
17
17
 
@@ -28,7 +28,10 @@ module ActiveRecord::Turntable
28
28
  turntable_config[:clusters][cluster_name],
29
29
  options
30
30
  )
31
+ self.turntable_clusters[cluster_name][self] = turntable_cluster
32
+
31
33
  turntable_replace_connection_pool
34
+ turntable_define_cluster_methods(cluster_name)
32
35
  end
33
36
 
34
37
  def force_transaction_all_shards!(options={}, &block)
@@ -51,7 +54,8 @@ module ActiveRecord::Turntable
51
54
 
52
55
  def force_connect_all_shards!
53
56
  conf = configurations[Rails.env]
54
- shards = conf["shards"]
57
+ shards = {}
58
+ shards = shards.merge(conf["shards"]) if conf["shards"]
55
59
  shards = shards.merge(conf["seq"]) if conf["seq"]
56
60
  shards.each do |name, config|
57
61
  turntable_connections[name] ||=
@@ -62,12 +66,9 @@ module ActiveRecord::Turntable
62
66
  def turntable_replace_connection_pool
63
67
  ch = connection_handler
64
68
  cp = turntable_cluster.connection_proxy
65
- if ActiveRecord::VERSION::STRING >= '3.2.0'
66
- ch.connection_pools[cp.spec] = PoolProxy.new(cp)
67
- ch.instance_variable_get(:@class_to_pool)[name] = ch.connection_pools[cp.spec]
68
- else
69
- ch.connection_pools[name] = PoolProxy.new(cp)
70
- end
69
+ pp = PoolProxy.new(cp)
70
+ ch.class_to_pool.clear if defined?(ch.class_to_pool)
71
+ ch.send(:class_to_pool)[name] = ch.send(:owner_to_pool)[name] = pp
71
72
  end
72
73
 
73
74
  def spec_for(config)
@@ -77,7 +78,7 @@ module ActiveRecord::Turntable
77
78
  raise "Please install the #{config['adapter']} adapter: `gem install activerecord-#{config['adapter']}-adapter` (#{e})"
78
79
  end
79
80
  adapter_method = "#{config['adapter']}_connection"
80
- ActiveRecord::Base::ConnectionSpecification.new(config, adapter_method)
81
+ ActiveRecord::ConnectionAdapters::ConnectionSpecification.new(config, adapter_method)
81
82
  end
82
83
 
83
84
  def clear_all_connections!
@@ -86,10 +87,11 @@ module ActiveRecord::Turntable
86
87
  end
87
88
  end
88
89
 
89
- def sequencer
90
+ def sequencer(sequence_name, *args)
90
91
  class_attribute :turntable_sequencer
92
+
91
93
  self.turntable_sequencer_enabled = true
92
- self.turntable_sequencer = ActiveRecord::Turntable::Sequencer.build(self)
94
+ self.turntable_sequencer = ActiveRecord::Turntable::Sequencer.build(self, sequence_name, *args)
93
95
  end
94
96
 
95
97
  def turntable_enabled?
@@ -117,6 +119,53 @@ module ActiveRecord::Turntable
117
119
  }
118
120
  self.connection.with_recursive_shards(shard.name, *klasses, &block)
119
121
  end
122
+
123
+ def with_shard(any_shard)
124
+ shard = case any_shard
125
+ when Numeric
126
+ turntable_cluster.shard_for(any_shard)
127
+ when ActiveRecord::Base
128
+ turntable_cluster.shard_for(any_shard.send(any_shard.turntable_shard_key))
129
+ else
130
+ shard_or_key
131
+ end
132
+ connection.with_shard(shard) { yield }
133
+ end
134
+
135
+ private
136
+
137
+ def turntable_define_cluster_methods(cluster_name)
138
+ turntable_define_cluster_class_methods(cluster_name)
139
+ end
140
+
141
+ def turntable_define_cluster_class_methods(cluster_name)
142
+ (class << ActiveRecord::Base; self; end).class_eval <<-EOD
143
+ unless respond_to?(:#{cluster_name}_transaction)
144
+ def #{cluster_name}_transaction(shards = [], options = {})
145
+ cluster = turntable_clusters[#{cluster_name.inspect}].values.first
146
+ cluster.shards_transaction(shards, options) { yield }
147
+ end
148
+ end
149
+
150
+ unless respond_to?(:all_cluster_transaction)
151
+ def all_cluster_transaction(options = {})
152
+ clusters = turntable_clusters.values.map { |v| v.values.first }
153
+ recursive_cluster_transaction(clusters) { yield }
154
+ end
155
+
156
+ def recursive_cluster_transaction(clusters, options = {}, &block)
157
+ current_cluster = clusters.shift
158
+ current_cluster.shards_transaction do
159
+ if clusters.present?
160
+ recursive_cluster_transaction(clusters, options, &block)
161
+ else
162
+ yield
163
+ end
164
+ end
165
+ end
166
+ end
167
+ EOD
168
+ end
120
169
  end
121
170
 
122
171
  def shards_transaction(options = {}, &block)
@@ -124,7 +173,11 @@ module ActiveRecord::Turntable
124
173
  end
125
174
 
126
175
  def turntable_shard
127
- turntable_cluster.select_shard(self.send(turntable_shard_key))
176
+ turntable_cluster.shard_for(self.send(turntable_shard_key))
177
+ end
178
+
179
+ def with_shard(shard)
180
+ self.class.connection.with_shard(shard) { yield }
128
181
  end
129
182
  end
130
183
  end
@@ -1,3 +1,5 @@
1
+ require 'active_support/core_ext/hash/indifferent_access'
2
+
1
3
  module ActiveRecord::Turntable
2
4
  class Cluster
3
5
 
@@ -16,7 +18,7 @@ module ActiveRecord::Turntable
16
18
  @master_shard = MasterShard.new(klass)
17
19
 
18
20
  # setup sequencer
19
- if (seq = (@options[:seq] || @config[:seq]))
21
+ if (seq = (@options[:seq] || @config[:seq])) && seq[:type] == :mysql
20
22
  @seq_shard = SeqShard.new(seq)
21
23
  end
22
24
 
@@ -53,13 +55,55 @@ module ActiveRecord::Turntable
53
55
  @connection_proxy
54
56
  end
55
57
 
56
- def select_shard(key)
58
+ def shard_for(key)
57
59
  @shards[@algorithm.calculate(key)]
58
60
  rescue
59
61
  raise ActiveRecord::Turntable::CannotSpecifyShardError,
60
62
  "[#{klass}] cannot select_shard for key:#{key}"
61
63
  end
62
64
 
65
+ def select_shard(key)
66
+ ActiveSupport::Deprecation.warn "Cluster#select_shard is deprecated, use shard_for() instead.", caller
67
+ shard_for(key)
68
+ end
69
+
70
+ def shards_transaction(shards = [], options = {}, in_recursion = false, &block)
71
+ unless in_recursion
72
+ shards = Array.wrap(shards).dup
73
+ if shards.blank?
74
+ shards = @shards.values.dup
75
+ end
76
+ end
77
+ shard_or_object = shards.shift
78
+ shard = to_shard(shard_or_object)
79
+ if shards.present?
80
+ shard.connection.transaction(options) do
81
+ shards_transaction(shards, options, true, &block)
82
+ end
83
+ else
84
+ shard.connection.transaction(options) do
85
+ block.call
86
+ end
87
+ end
88
+ end
89
+
90
+ def to_shard(shard_or_object)
91
+ case shard_or_object
92
+ when ActiveRecord::Turntable::Shard
93
+ shard_or_object
94
+ when ActiveRecord::Base
95
+ shard_or_object.turntable_shard
96
+ when Numeric, String
97
+ shard_for(shard_or_object)
98
+ when Symbol
99
+ shards[shard_or_object]
100
+ else
101
+ binding.pry
102
+ raise ActiveRecord::Turntable::TurntableError,
103
+ "transaction cannot call to object: #{shard_or_object}"
104
+ end
105
+ end
106
+
63
107
  def weighted_shards(key = nil)
64
108
  key ||= @klass.current_sequence
65
109
  Hash[@algorithm.calculate_used_shards_with_weight(key).map do |k,v|
@@ -18,7 +18,7 @@ module ActiveRecord::Turntable
18
18
  end
19
19
 
20
20
  def load!(config_file, env)
21
- @config = YAML.load_file(config_file).with_indifferent_access[env]
21
+ @config = YAML.load(ERB.new(IO.read(config_file)).result).with_indifferent_access[env]
22
22
  end
23
23
  end
24
24
  end
@@ -3,36 +3,14 @@ module ActiveRecord::Turntable
3
3
  module Mixable
4
4
  extend ActiveSupport::Concern
5
5
 
6
- included do
7
- if ActiveRecord::VERSION::STRING < '3.1'
8
- include Rails30
9
- else
10
- include Rails3x
11
- end
12
- end
13
-
14
- module Rails3x
15
- METHODS_REGEXP = /\A(insert|select|update|delete|exec_)/
16
- EXCLUDE_QUERY_REGEXP = /\A\s*SHOW/i
17
- QUERY_REGEXP = /\A\s*(INSERT|DELETE|UPDATE|SELECT)/i
18
-
19
- def mixable?(method, *args)
20
- (method.to_s =~ METHODS_REGEXP &&
21
- args.first !~ EXCLUDE_QUERY_REGEXP) ||
22
- (method.to_s == 'execute' && args.first =~ QUERY_REGEXP)
23
- end
24
- end
25
-
26
- module Rails30
27
- METHODS_REGEXP = /\A(insert|select|update|delete)/
28
- EXCLUDE_QUERY_REGEXP = /\A\s*SHOW/i
29
- QUERY_REGEXP = /\A\s*(INSERT|DELETE|UPDATE|SELECT)/i
6
+ METHODS_REGEXP = /\A(insert|select|update|delete|exec_)/
7
+ EXCLUDE_QUERY_REGEXP = /\A\s*SHOW/i
8
+ QUERY_REGEXP = /\A\s*(INSERT|DELETE|UPDATE|SELECT)/i
30
9
 
31
- def mixable?(method, *args)
32
- (method.to_s =~ METHODS_REGEXP &&
33
- args.first !~ EXCLUDE_QUERY_REGEXP) ||
34
- (method.to_s == 'execute' && args.first =~ QUERY_REGEXP)
35
- end
10
+ def mixable?(method, *args)
11
+ (method.to_s =~ METHODS_REGEXP &&
12
+ args.first !~ EXCLUDE_QUERY_REGEXP) ||
13
+ (method.to_s == 'execute' && args.first =~ QUERY_REGEXP)
36
14
  end
37
15
  end
38
16
  end