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,81 @@
1
+ require 'active_record/tasks/database_tasks'
2
+
3
+ module ActiveRecord
4
+ module Tasks
5
+ module DatabaseTasks
6
+ def create_all_turntable_cluster
7
+ each_local_turntable_cluster_configuration { |name, configuration|
8
+ puts "[turntable] *** executing to database: #{configuration['database']}"
9
+ create configuration
10
+ }
11
+ end
12
+
13
+ def drop_all_turntable_cluster
14
+ each_local_turntable_cluster_configuration { |name, configuration|
15
+ puts "[turntable] *** executing to database: #{configuration['database']}"
16
+ drop configuration
17
+ }
18
+ end
19
+
20
+ def create_current_turntable_cluster(environment = env)
21
+ each_current_turntable_cluster_configuration(true, environment) { |name, configuration|
22
+ puts "[turntable] *** executing to database: #{configuration['database']}"
23
+ create configuration
24
+ }
25
+ ActiveRecord::Base.establish_connection environment.to_sym
26
+ end
27
+
28
+ def drop_current_turntable_cluster(environment = env)
29
+ each_current_turntable_cluster_configuration(true, environment) { |name, configuration|
30
+ puts "[turntable] *** executing to database: #{configuration['database']}"
31
+ drop configuration
32
+ }
33
+ end
34
+
35
+ def each_current_turntable_cluster_connected(with_test = false, environment = env)
36
+ each_current_turntable_cluster_configuration(with_test, environment) do |name, configuration|
37
+ ActiveRecord::Base.clear_active_connections!
38
+ ActiveRecord::Base.establish_connection(configuration)
39
+ ActiveRecord::Migration.current_shard = name
40
+ yield(name, configuration)
41
+ end
42
+ ActiveRecord::Base.clear_active_connections!
43
+ ActiveRecord::Base.establish_connection environment.to_sym
44
+ end
45
+
46
+ def each_current_turntable_cluster_configuration(with_test = false, environment = env)
47
+ environments = [environment]
48
+ environments << 'test' if with_test and environment == 'development'
49
+
50
+ current_turntable_cluster_configurations(*environments).each do |name, configuration|
51
+ yield(name, configuration) unless configuration['database'].blank?
52
+ end
53
+ end
54
+
55
+ def each_local_turntable_cluster_configuration
56
+ ActiveRecord::Base.configurations.keys.each do |k|
57
+ current_turntable_cluster_configurations(k).each do |name, configuration|
58
+ next if configuration['database'].blank?
59
+
60
+ if local_database?(configuration)
61
+ yield(name,configuration)
62
+ else
63
+ $stderr.puts "This task only modifies local databases. #{configuration['database']} is on a remote host."
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ def current_turntable_cluster_configurations(*environments)
70
+ configurations = []
71
+ environments.each do |environ|
72
+ config = ActiveRecord::Base.configurations[environ]
73
+ %w(shards seq).each do |name|
74
+ configurations += config[name].to_a
75
+ end
76
+ end
77
+ configurations
78
+ end
79
+ end
80
+ end
81
+ end
@@ -3,56 +3,52 @@
3
3
  #
4
4
  require 'active_record/fixtures'
5
5
  module ActiveRecord
6
- class Fixtures
7
- def self.create_fixtures(fixtures_directory, table_names, class_names = {})
8
- table_names = [table_names].flatten.map { |n| n.to_s }
9
- table_names.each { |n|
10
- class_names[n.tr('/', '_').to_sym] = n.classify if n.include?('/')
11
- }
6
+ class FixtureSet
7
+ def self.create_fixtures(fixtures_directory, table_names, class_names = {}, config = ActiveRecord::Base)
8
+ fixture_set_names = Array(fixture_set_names).map(&:to_s)
9
+ class_names = class_names.stringify_keys
12
10
 
13
11
  # FIXME: Apparently JK uses this.
14
12
  connection = block_given? ? yield : ActiveRecord::Base.connection
15
13
 
16
- files_to_read = table_names.reject { |table_name|
17
- fixture_is_cached?(connection, table_name)
14
+ files_to_read = fixture_set_names.reject { |fs_name|
15
+ fixture_is_cached?(connection, fs_name)
18
16
  }
19
17
 
20
18
  unless files_to_read.empty?
21
19
  connection.disable_referential_integrity do
22
20
  fixtures_map = {}
23
21
 
24
- fixture_files = files_to_read.map do |path|
25
- table_name = path.tr '/', '_'
26
-
27
- fixtures_map[path] = ActiveRecord::Fixtures.new(
22
+ fixture_sets = files_to_read.map do |fs_name|
23
+ fixtures_map[fs_name] = new( # ActiveRecord::FixtureSet.new
28
24
  connection,
29
- table_name,
30
- class_names[table_name.to_sym] || table_name.classify,
31
- ::File.join(fixtures_directory, path))
25
+ fs_name,
26
+ class_names[fs_name] || default_fixture_model_name(fs_name),
27
+ ::File.join(fixtures_directory, fs_name))
32
28
  end
33
29
 
34
30
  all_loaded_fixtures.update(fixtures_map)
35
31
 
36
32
  ActiveRecord::Turntable::Base.force_transaction_all_shards!(:requires_new => true) do
37
- fixture_files.each do |ff|
38
- conn = ff.model_class.respond_to?(:connection) ? ff.model_class.connection : connection
39
- table_rows = ff.table_rows
33
+ fixture_sets.each do |fs|
34
+ conn = fs.model_class.respond_to?(:connection) ? fs.model_class.connection : connection
35
+ table_rows = fs.table_rows
40
36
 
41
37
  table_rows.keys.each do |table|
42
38
  conn.delete "DELETE FROM #{conn.quote_table_name(table)}", 'Fixture Delete'
43
39
  end
44
40
 
45
- table_rows.each do |table_name,rows|
41
+ table_rows.each do |fixture_set_name, rows|
46
42
  rows.each do |row|
47
- conn.insert_fixture(row, table_name)
43
+ conn.insert_fixture(row, fixture_set_name)
48
44
  end
49
45
  end
50
46
  end
51
47
 
52
48
  # Cap primary key sequences to max(pk).
53
49
  if connection.respond_to?(:reset_pk_sequence!)
54
- table_names.each do |table_name|
55
- connection.reset_pk_sequence!(table_name.tr('/', '_'))
50
+ fixture_sets.each do |fs|
51
+ connection.reset_pk_sequence!(fs.table_name)
56
52
  end
57
53
  end
58
54
  end
@@ -60,13 +56,12 @@ module ActiveRecord
60
56
  cache_fixtures(connection, fixtures_map)
61
57
  end
62
58
  end
63
- cached_fixtures(connection, table_names)
59
+ cached_fixtures(connection, fixture_set_names)
64
60
  end
65
-
66
61
  end
67
62
 
68
63
  module TestFixtures
69
- def setup_fixtures
64
+ def setup_fixtures(config = ActiveRecord::Base)
70
65
  return unless !ActiveRecord::Base.configurations.blank?
71
66
 
72
67
  if pre_loaded_fixtures && !use_transactional_fixtures
@@ -82,50 +77,67 @@ module ActiveRecord
82
77
  if @@already_loaded_fixtures[self.class]
83
78
  @loaded_fixtures = @@already_loaded_fixtures[self.class]
84
79
  else
85
- @loaded_fixtures = load_fixtures
80
+ @loaded_fixtures = turntable_load_fixtures(config)
86
81
  @@already_loaded_fixtures[self.class] = @loaded_fixtures
87
82
  end
88
83
  ActiveRecord::Base.force_connect_all_shards!
89
84
  @fixture_connections = enlist_fixture_connections
90
85
  @fixture_connections.each do |connection|
91
- connection.increment_open_transactions
92
- connection.transaction_joinable = false
93
- connection.begin_db_transaction
86
+ connection.begin_transaction joinable: false
94
87
  end
95
- # Load fixtures for every test.
88
+ # Load fixtures for every test.
96
89
  else
97
90
  ActiveRecord::Fixtures.reset_cache
98
91
  @@already_loaded_fixtures[self.class] = nil
99
- @loaded_fixtures = load_fixtures
92
+ @loaded_fixtures = turntable_load_fixtures(config)
100
93
  end
101
94
 
102
95
  # Instantiate fixtures for every test if requested.
103
- instantiate_fixtures if use_instantiated_fixtures
96
+ turntable_instantiate_fixtures(config) if use_instantiated_fixtures
104
97
  end
105
98
 
106
99
  def enlist_fixture_connections
107
- ActiveRecord::Base.connection_handler.connection_pools.values.map(&:connection) +
100
+ ActiveRecord::Base.connection_handler.connection_pool_list.map(&:connection) +
108
101
  ActiveRecord::Base.turntable_connections.values.map(&:connection)
109
102
  end
110
103
 
111
104
  def teardown_fixtures
112
- return unless defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
113
-
114
- unless run_in_transaction?
115
- ActiveRecord::Fixtures.reset_cache
116
- end
105
+ return if ActiveRecord::Base.configurations.blank?
117
106
 
118
107
  # Rollback changes if a transaction is active.
119
108
  if run_in_transaction?
120
109
  @fixture_connections.each do |connection|
121
- if connection.open_transactions != 0
122
- connection.rollback_db_transaction
123
- connection.decrement_open_transactions
124
- end
110
+ connection.rollback_transaction if connection.transaction_open?
125
111
  end
126
112
  @fixture_connections.clear
113
+ else
114
+ ActiveRecord::FixtureSet.reset_cache
127
115
  end
116
+
128
117
  ActiveRecord::Base.clear_active_connections!
129
118
  end
119
+
120
+ private
121
+
122
+ def turntable_load_fixtures(config)
123
+ if ActiveRecord::Turntable.rails41_later?
124
+ load_fixtures(config)
125
+ elsif ActiveRecord::Turntable.rails4?
126
+ load_fixtures
127
+ else
128
+ raise NotImplementedError
129
+ end
130
+ end
131
+
132
+ def turntable_instantiate_fixtures(config)
133
+ if ActiveRecord::Turntable.rails41_later?
134
+ instantiate_fixtures(config)
135
+ elsif ActiveRecord::Turntable.rails4?
136
+ instantiate_fixtures
137
+ else
138
+ raise NotImplementedError
139
+ end
140
+ end
141
+
130
142
  end
131
143
  end
@@ -0,0 +1,101 @@
1
+ module ActiveRecord::Turntable::ActiveRecordExt
2
+ module LockingOptimistic
3
+
4
+ ::ActiveRecord::Locking::Optimistic.class_eval do
5
+
6
+ ar_version = ActiveRecord::VERSION::STRING
7
+ if ar_version < "4.1"
8
+ method_name = ar_version =~ /\A4\.0\.[0-5]\z/ ? "update_record" : "_update_record"
9
+
10
+ class_eval <<-EOD
11
+ def #{method_name}(attribute_names = @attributes.keys) #:nodoc:
12
+ return super unless locking_enabled?
13
+ return 0 if attribute_names.empty?
14
+
15
+ klass = self.class
16
+ lock_col = self.class.locking_column
17
+ previous_lock_value = send(lock_col).to_i
18
+ increment_lock
19
+
20
+ attribute_names += [lock_col]
21
+ attribute_names.uniq!
22
+
23
+ begin
24
+ relation = self.class.unscoped
25
+
26
+ condition_scope = relation.where(
27
+ relation.table[self.class.primary_key].eq(id).and(
28
+ relation.table[lock_col].eq(self.class.quote_value(previous_lock_value, column_for_attribute(lock_col)))
29
+ )
30
+ )
31
+ if klass.turntable_enabled? and klass.primary_key != klass.turntable_shard_key.to_s
32
+ condition_scope = condition_scope.where(klass.turntable_shard_key => self.send(turntable_shard_key))
33
+ end
34
+ stmt = condition_scope.arel.compile_update(arel_attributes_with_values_for_update(attribute_names))
35
+
36
+ affected_rows = self.class.connection.update stmt
37
+
38
+ unless affected_rows == 1
39
+ raise ActiveRecord::StaleObjectError.new(self, "update")
40
+ end
41
+
42
+ affected_rows
43
+
44
+ # If something went wrong, revert the version.
45
+ rescue Exception
46
+ send(lock_col + '=', previous_lock_value)
47
+ raise
48
+ end
49
+ end
50
+ EOD
51
+ else
52
+ method_name = ar_version =~ /\A4\.1\.[01]\z/ ? "update_record" : "_update_record"
53
+
54
+ class_eval <<-EOD
55
+ def _update_record(attribute_names = @attributes.keys) #:nodoc:
56
+ return super unless locking_enabled?
57
+ return 0 if attribute_names.empty?
58
+
59
+ klass = self.class
60
+ lock_col = self.class.locking_column
61
+ previous_lock_value = send(lock_col).to_i
62
+ increment_lock
63
+
64
+ attribute_names += [lock_col]
65
+ attribute_names.uniq!
66
+
67
+ begin
68
+ relation = self.class.unscoped
69
+
70
+ condition_scope = relation.where(
71
+ relation.table[self.class.primary_key].eq(id).and(
72
+ relation.table[lock_col].eq(self.class.quote_value(previous_lock_value, column_for_attribute(lock_col)))
73
+ )
74
+ )
75
+ if klass.turntable_enabled? and klass.primary_key != klass.turntable_shard_key.to_s
76
+ condition_scope = condition_scope.where(klass.turntable_shard_key => self.send(turntable_shard_key))
77
+ end
78
+ stmt = condition_scope.arel.compile_update(
79
+ arel_attributes_with_values_for_update(attribute_names),
80
+ self.class.primary_key
81
+ )
82
+
83
+ affected_rows = self.class.connection.update stmt
84
+
85
+ unless affected_rows == 1
86
+ raise ActiveRecord::StaleObjectError.new(self, "update")
87
+ end
88
+
89
+ affected_rows
90
+
91
+ # If something went wrong, revert the version.
92
+ rescue Exception
93
+ send(lock_col + '=', previous_lock_value)
94
+ raise
95
+ end
96
+ end
97
+ EOD
98
+ end
99
+ end
100
+ end
101
+ end
@@ -1,64 +1,46 @@
1
+ require 'active_record/log_subscriber'
2
+
1
3
  module ActiveRecord::Turntable
2
4
  module ActiveRecordExt
3
5
  module LogSubscriber
4
6
  extend ActiveSupport::Concern
5
7
 
6
8
  included do
7
- if ActiveRecord::VERSION::STRING < '3.1'
8
- def sql(event)
9
- self.class.runtime += event.duration
10
- return unless logger.debug?
11
-
12
- name = '%s (%.1fms)' % [event.payload[:name], event.duration]
13
- shard = '[Shard: %s]' % (event.payload[:turntable_shard_name] ? event.payload[:turntable_shard_name] : nil)
14
- sql = event.payload[:sql].squeeze(' ')
15
-
16
- if odd?
17
- name = color(name, ActiveRecord::LogSubscriber::CYAN, true)
18
- shard = color(shard, ActiveRecord::LogSubscriber::CYAN, true)
19
- sql = color(sql, nil, true)
20
- else
21
- name = color(name, ActiveRecord::LogSubscriber::MAGENTA, true)
22
- shard = color(shard, ActiveRecord::LogSubscriber::MAGENTA, true)
23
- end
24
-
25
- debug " #{name} #{shard} #{sql}"
26
- end
9
+ alias_method_chain :sql, :turntable
10
+ end
27
11
 
28
- else
29
- def sql(event)
30
- self.class.runtime += event.duration
31
- return unless logger.debug?
12
+ protected
32
13
 
33
- payload = event.payload
14
+ def sql_with_turntable(event)
15
+ self.class.runtime += event.duration
16
+ return unless logger.debug?
34
17
 
35
- return if 'SCHEMA' == payload[:name]
18
+ payload = event.payload
36
19
 
37
- name = '%s (%.1fms)' % [payload[:name], event.duration]
38
- shard = '[Shard: %s]' % (event.payload[:turntable_shard_name] ? event.payload[:turntable_shard_name] : nil)
39
- sql = payload[:sql].squeeze(' ')
40
- binds = nil
20
+ return if ActiveRecord::LogSubscriber::IGNORE_PAYLOAD_NAMES.include?(payload[:name])
41
21
 
42
- unless (payload[:binds] || []).empty?
43
- binds = " " + payload[:binds].map { |col,v|
44
- [col.name, v]
45
- }.inspect
46
- end
22
+ name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
23
+ shard = '[Shard: %s]' % (event.payload[:turntable_shard_name] ? event.payload[:turntable_shard_name] : nil)
24
+ sql = payload[:sql].squeeze(' ')
25
+ binds = nil
47
26
 
48
- if odd?
49
- name = color(name, ActiveRecord::LogSubscriber::CYAN, true)
50
- shard = color(shard, ActiveRecord::LogSubscriber::CYAN, true)
51
- sql = color(sql, nil, true)
52
- else
53
- name = color(name, ActiveRecord::LogSubscriber::MAGENTA, true)
54
- shard = color(shard, ActiveRecord::LogSubscriber::MAGENTA, true)
55
- end
27
+ unless (payload[:binds] || []).empty?
28
+ binds = " " + payload[:binds].map { |col,v|
29
+ render_bind(col, v)
30
+ }.inspect
31
+ end
56
32
 
57
- debug " #{name} #{shard} #{sql}#{binds}"
58
- end
33
+ if odd?
34
+ name = color(name, ActiveRecord::LogSubscriber::CYAN, true)
35
+ shard = color(shard, ActiveRecord::LogSubscriber::CYAN, true)
36
+ sql = color(sql, nil, true)
37
+ else
38
+ name = color(name, ActiveRecord::LogSubscriber::MAGENTA, true)
39
+ shard = color(shard, ActiveRecord::LogSubscriber::MAGENTA, true)
59
40
  end
41
+
42
+ debug " #{name} #{shard} #{sql}#{binds}"
60
43
  end
61
44
  end
62
45
  end
63
46
  end
64
-
@@ -0,0 +1,7 @@
1
+ require 'active_record/migration'
2
+
3
+ module ActiveRecord
4
+ class MigrationProxy
5
+ delegate :target_shard?, to: :migration
6
+ end
7
+ end
@@ -5,118 +5,120 @@ module ActiveRecord::Turntable::ActiveRecordExt
5
5
  clear_aggregation_cache
6
6
  clear_association_cache
7
7
 
8
- block = lambda {
9
- fresh_object = self.class.unscoped {
10
- finder_scope = if turntable_enabled? and self.class.primary_key != self.class.turntable_shard_key.to_s
11
- self.class.where(self.class.turntable_shard_key => self.send(turntable_shard_key))
12
- else
13
- self.class
14
- end
15
- finder_scope.find(self.id, options)
16
- }
17
- @attributes.update(fresh_object.instance_variable_get('@attributes'))
18
- }
19
-
20
- if defined?(::ActiveRecord::IdentityMap)
21
- ::ActiveRecord::IdentityMap.without(&block)
22
- else
23
- block.call
24
- end
8
+ finder_scope = if turntable_enabled? and self.class.primary_key != self.class.turntable_shard_key.to_s
9
+ self.class.unscoped.where(self.class.turntable_shard_key => self.send(turntable_shard_key))
10
+ else
11
+ self.class.unscoped
12
+ end
13
+
14
+ fresh_object =
15
+ if options && options[:lock]
16
+ finder_scope.lock.find(id)
17
+ else
18
+ finder_scope.find(id)
19
+ end
20
+
21
+ @attributes.update(fresh_object.instance_variable_get('@attributes'))
25
22
 
26
- @attributes_cache = {}
23
+ @column_types = self.class.column_types
24
+ @column_types_override = fresh_object.instance_variable_get('@column_types_override')
25
+ @attributes_cache = {}
27
26
  self
28
27
  end
29
- end
30
28
 
31
- if ActiveRecord::VERSION::STRING < '3.1'
32
- ::ActiveRecord::Persistence.class_eval do
33
- def destroy
34
- klass = self.class
35
- if persisted?
36
- condition_scope = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id))
37
- if klass.turntable_enabled? and klass.primary_key != klass.turntable_shard_key.to_s
38
- condition_scope = condition_scope.where(klass.turntable_shard_key => self.send(turntable_shard_key))
39
- end
40
- condition_scope.delete_all
41
- end
29
+ def touch(name = nil)
30
+ raise ActiveRecordError, "can not touch on a new record object" unless persisted?
42
31
 
43
- @destroyed = true
44
- freeze
45
- end
32
+ attributes = timestamp_attributes_for_update_in_model
33
+ attributes << name if name
46
34
 
47
- private
35
+ unless attributes.empty?
36
+ current_time = current_time_from_proper_timezone
37
+ changes = {}
48
38
 
49
- # overrides ActiveRecord::Persistence's original method so that
50
- def update(attribute_names = @attributes.keys)
51
- klass = self.class
52
- attributes_with_values = arel_attributes_values(false, false, attribute_names)
53
- return 0 if attributes_with_values.empty?
54
- condition_scope = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id))
55
- if klass.turntable_enabled? and klass.primary_key != klass.turntable_shard_key.to_s
56
- condition_scope = condition_scope.where(klass.turntable_shard_key => self.send(turntable_shard_key))
39
+ attributes.each do |column|
40
+ column = column.to_s
41
+ changes[column] = write_attribute(column, current_time)
57
42
  end
58
- condition_scope.arel.update(attributes_with_values)
43
+
44
+ changes[self.class.locking_column] = increment_lock if locking_enabled?
45
+
46
+ @changed_attributes.except!(*changes.keys)
47
+ primary_key = self.class.primary_key
48
+
49
+ finder_scope = if turntable_enabled? and primary_key != self.class.turntable_shard_key.to_s
50
+ self.class.unscoped.where(self.class.turntable_shard_key => self.send(turntable_shard_key))
51
+ else
52
+ self.class.unscoped
53
+ end
54
+
55
+ finder_scope.where(primary_key => self[primary_key]).update_all(changes) == 1
59
56
  end
60
57
  end
61
58
 
62
- else
63
- ::ActiveRecord::Persistence.class_eval do
64
- def destroy
65
- klass = self.class
66
- destroy_associations
67
-
68
- if persisted?
69
- ActiveRecord::IdentityMap.remove(self) if defined?(ActiveRecord::IdentityMap) and ActiveRecord::IdentityMap.enabled?
70
- pk = klass.primary_key
71
- column = klass.columns_hash[pk]
72
- substitute = connection.substitute_at(column, 0)
73
-
74
- relation = klass.unscoped.where(klass.arel_table[pk].eq(substitute))
75
- if klass.turntable_enabled? and klass.primary_key != klass.turntable_shard_key.to_s
76
- relation = relation.where(klass.turntable_shard_key => self.send(turntable_shard_key))
77
- end
78
- relation.bind_values = [[column, id]]
79
- relation.delete_all
80
- end
59
+ private
81
60
 
82
- @destroyed = true
83
- freeze
84
- end
61
+ def relation_for_destroy
62
+ pk = self.class.primary_key
63
+ column = self.class.columns_hash[pk]
64
+ substitute = self.class.connection.substitute_at(column, 0)
65
+ klass = self.class
85
66
 
86
- def destroy_without_callbacks
87
- klass = self.class
88
- destroy_associations
67
+ relation = self.class.unscoped.where(
68
+ self.class.arel_table[pk].eq(substitute))
69
+ if klass.turntable_enabled? and klass.primary_key != klass.turntable_shard_key.to_s
70
+ relation = relation.where(klass.turntable_shard_key => self.send(turntable_shard_key))
71
+ end
89
72
 
90
- if persisted?
91
- ActiveRecord::IdentityMap.remove(self) if defined?(ActiveRecord::IdentityMap) and ActiveRecord::IdentityMap.enabled?
92
- pk = klass.primary_key
93
- column = klass.columns_hash[pk]
94
- substitute = connection.substitute_at(column, 0)
73
+ relation.bind_values = [[column, id]]
74
+ relation
75
+ end
95
76
 
96
- relation = klass.unscoped.where(klass.arel_table[pk].eq(substitute))
97
- if klass.turntable_enabled? and klass.primary_key != klass.turntable_shard_key.to_s
98
- relation = relation.where(klass.turntable_shard_key => self.send(turntable_shard_key))
77
+ ar_version = ActiveRecord::VERSION::STRING
78
+ if ar_version < "4.1"
79
+ method_name = ar_version =~ /\A4\.0\.[0-5]\z/ ? "update_record" : "_update_record"
80
+ class_eval <<-EOD
81
+ def #{method_name}(attribute_names = @attributes.keys)
82
+ attributes_with_values = arel_attributes_with_values_for_update(attribute_names)
83
+ if attributes_with_values.empty?
84
+ 0
85
+ else
86
+ klass = self.class
87
+ column_hash = klass.connection.schema_cache.columns_hash klass.table_name
88
+ db_columns_with_values = attributes_with_values.map { |attr,value|
89
+ real_column = column_hash[attr.name]
90
+ [real_column, value]
91
+ }
92
+ bind_attrs = attributes_with_values.dup
93
+ bind_attrs.keys.each_with_index do |column, i|
94
+ real_column = db_columns_with_values[i].first
95
+ bind_attrs[column] = klass.connection.substitute_at(real_column, i)
96
+ end
97
+ condition_scope = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id_was || id))
98
+ if klass.turntable_enabled? and klass.primary_key != klass.turntable_shard_key.to_s
99
+ condition_scope = condition_scope.where(klass.turntable_shard_key => self.send(turntable_shard_key))
100
+ end
101
+ stmt = condition_scope.arel.compile_update(bind_attrs)
102
+ klass.connection.update stmt, 'SQL', db_columns_with_values
99
103
  end
100
- relation.bind_values = [[column, id]]
101
- relation.delete_all
102
104
  end
103
-
104
- @destroyed = true
105
- freeze
106
- end
107
-
108
- private
109
- def update(attribute_names = @attributes.keys)
110
- attributes_with_values = arel_attributes_values(false, false, attribute_names)
111
- return 0 if attributes_with_values.empty?
112
- klass = self.class
113
- condition_scope = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id))
114
- if klass.turntable_enabled? and klass.primary_key != klass.turntable_shard_key.to_s
115
- condition_scope = condition_scope.where(klass.turntable_shard_key => self.send(turntable_shard_key))
105
+ EOD
106
+ else
107
+ method_name = ar_version =~ /\A4\.1\.[01]\z/ ? "update_record" : "_update_record"
108
+ class_eval <<-EOD
109
+ def #{method_name}(attribute_names = @attributes.keys)
110
+ klass = self.class
111
+ attributes_values = arel_attributes_with_values_for_update(attribute_names)
112
+ if attributes_values.empty?
113
+ 0
114
+ else
115
+ scope = if klass.turntable_enabled? and klass.primary_key != klass.turntable_shard_key.to_s
116
+ klass.unscoped.where(klass.turntable_shard_key => self.send(turntable_shard_key))
117
+ end
118
+ klass.unscoped.#{method_name} attributes_values, id, id_was, scope
119
+ end
116
120
  end
117
- stmt = condition_scope.arel.compile_update(attributes_with_values)
118
- klass.connection.update stmt.to_sql
119
- end
121
+ EOD
120
122
  end
121
123
  end
122
124
  end