ar-octopus-ruby-3 0.11.2

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 (160) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +46 -0
  5. data/.rubocop_todo.yml +56 -0
  6. data/.travis.yml +18 -0
  7. data/Appraisals +16 -0
  8. data/Gemfile +4 -0
  9. data/README.mkdn +257 -0
  10. data/Rakefile +175 -0
  11. data/TODO.txt +7 -0
  12. data/ar-octopus.gemspec +44 -0
  13. data/gemfiles/rails42.gemfile +7 -0
  14. data/gemfiles/rails5.gemfile +7 -0
  15. data/gemfiles/rails51.gemfile +7 -0
  16. data/gemfiles/rails52.gemfile +7 -0
  17. data/lib/ar-octopus.rb +1 -0
  18. data/lib/octopus/abstract_adapter.rb +33 -0
  19. data/lib/octopus/association.rb +14 -0
  20. data/lib/octopus/association_shard_tracking.rb +74 -0
  21. data/lib/octopus/collection_association.rb +17 -0
  22. data/lib/octopus/collection_proxy.rb +16 -0
  23. data/lib/octopus/exception.rb +4 -0
  24. data/lib/octopus/finder_methods.rb +8 -0
  25. data/lib/octopus/load_balancing/round_robin.rb +20 -0
  26. data/lib/octopus/load_balancing.rb +4 -0
  27. data/lib/octopus/log_subscriber.rb +26 -0
  28. data/lib/octopus/migration.rb +236 -0
  29. data/lib/octopus/model.rb +216 -0
  30. data/lib/octopus/persistence.rb +45 -0
  31. data/lib/octopus/proxy.rb +399 -0
  32. data/lib/octopus/proxy_config.rb +251 -0
  33. data/lib/octopus/query_cache_for_shards.rb +24 -0
  34. data/lib/octopus/railtie.rb +11 -0
  35. data/lib/octopus/relation_proxy.rb +74 -0
  36. data/lib/octopus/result_patch.rb +19 -0
  37. data/lib/octopus/scope_proxy.rb +68 -0
  38. data/lib/octopus/shard_tracking/attribute.rb +22 -0
  39. data/lib/octopus/shard_tracking/dynamic.rb +11 -0
  40. data/lib/octopus/shard_tracking.rb +46 -0
  41. data/lib/octopus/singular_association.rb +9 -0
  42. data/lib/octopus/slave_group.rb +13 -0
  43. data/lib/octopus/version.rb +3 -0
  44. data/lib/octopus.rb +209 -0
  45. data/lib/tasks/octopus.rake +16 -0
  46. data/sample_app/.gitignore +4 -0
  47. data/sample_app/.rspec +1 -0
  48. data/sample_app/Gemfile +20 -0
  49. data/sample_app/Gemfile.lock +155 -0
  50. data/sample_app/README +3 -0
  51. data/sample_app/README.rdoc +261 -0
  52. data/sample_app/Rakefile +7 -0
  53. data/sample_app/app/assets/images/rails.png +0 -0
  54. data/sample_app/app/assets/javascripts/application.js +15 -0
  55. data/sample_app/app/assets/stylesheets/application.css +13 -0
  56. data/sample_app/app/controllers/application_controller.rb +4 -0
  57. data/sample_app/app/helpers/application_helper.rb +2 -0
  58. data/sample_app/app/mailers/.gitkeep +0 -0
  59. data/sample_app/app/models/.gitkeep +0 -0
  60. data/sample_app/app/models/item.rb +3 -0
  61. data/sample_app/app/models/user.rb +3 -0
  62. data/sample_app/app/views/layouts/application.html.erb +14 -0
  63. data/sample_app/autotest/discover.rb +2 -0
  64. data/sample_app/config/application.rb +62 -0
  65. data/sample_app/config/boot.rb +6 -0
  66. data/sample_app/config/cucumber.yml +8 -0
  67. data/sample_app/config/database.yml +28 -0
  68. data/sample_app/config/environment.rb +5 -0
  69. data/sample_app/config/environments/development.rb +37 -0
  70. data/sample_app/config/environments/production.rb +67 -0
  71. data/sample_app/config/environments/test.rb +37 -0
  72. data/sample_app/config/initializers/backtrace_silencers.rb +7 -0
  73. data/sample_app/config/initializers/inflections.rb +15 -0
  74. data/sample_app/config/initializers/mime_types.rb +5 -0
  75. data/sample_app/config/initializers/secret_token.rb +7 -0
  76. data/sample_app/config/initializers/session_store.rb +8 -0
  77. data/sample_app/config/initializers/wrap_parameters.rb +14 -0
  78. data/sample_app/config/locales/en.yml +5 -0
  79. data/sample_app/config/routes.rb +58 -0
  80. data/sample_app/config/shards.yml +28 -0
  81. data/sample_app/config.ru +4 -0
  82. data/sample_app/db/migrate/20100720172715_create_users.rb +15 -0
  83. data/sample_app/db/migrate/20100720172730_create_items.rb +16 -0
  84. data/sample_app/db/migrate/20100720210335_create_sample_users.rb +11 -0
  85. data/sample_app/db/schema.rb +29 -0
  86. data/sample_app/db/seeds.rb +16 -0
  87. data/sample_app/doc/README_FOR_APP +2 -0
  88. data/sample_app/features/migrate.feature +45 -0
  89. data/sample_app/features/seed.feature +15 -0
  90. data/sample_app/features/step_definitions/seeds_steps.rb +13 -0
  91. data/sample_app/features/step_definitions/web_steps.rb +218 -0
  92. data/sample_app/features/support/database.rb +13 -0
  93. data/sample_app/features/support/env.rb +57 -0
  94. data/sample_app/features/support/paths.rb +33 -0
  95. data/sample_app/lib/assets/.gitkeep +0 -0
  96. data/sample_app/lib/tasks/.gitkeep +0 -0
  97. data/sample_app/lib/tasks/cucumber.rake +64 -0
  98. data/sample_app/log/.gitkeep +0 -0
  99. data/sample_app/public/404.html +26 -0
  100. data/sample_app/public/422.html +26 -0
  101. data/sample_app/public/500.html +26 -0
  102. data/sample_app/public/favicon.ico +0 -0
  103. data/sample_app/public/images/rails.png +0 -0
  104. data/sample_app/public/index.html +279 -0
  105. data/sample_app/public/javascripts/application.js +2 -0
  106. data/sample_app/public/javascripts/controls.js +965 -0
  107. data/sample_app/public/javascripts/dragdrop.js +974 -0
  108. data/sample_app/public/javascripts/effects.js +1123 -0
  109. data/sample_app/public/javascripts/prototype.js +4874 -0
  110. data/sample_app/public/javascripts/rails.js +118 -0
  111. data/sample_app/public/robots.txt +5 -0
  112. data/sample_app/public/stylesheets/.gitkeep +0 -0
  113. data/sample_app/script/cucumber +10 -0
  114. data/sample_app/script/rails +6 -0
  115. data/sample_app/spec/models/item_spec.rb +5 -0
  116. data/sample_app/spec/models/user_spec.rb +5 -0
  117. data/sample_app/spec/spec_helper.rb +27 -0
  118. data/sample_app/vendor/assets/javascripts/.gitkeep +0 -0
  119. data/sample_app/vendor/assets/stylesheets/.gitkeep +0 -0
  120. data/sample_app/vendor/plugins/.gitkeep +0 -0
  121. data/spec/config/shards.yml +231 -0
  122. data/spec/migrations/10_create_users_using_replication.rb +9 -0
  123. data/spec/migrations/11_add_field_in_all_slaves.rb +11 -0
  124. data/spec/migrations/12_create_users_using_block.rb +23 -0
  125. data/spec/migrations/13_create_users_using_block_and_using.rb +15 -0
  126. data/spec/migrations/14_create_users_on_shards_of_a_group_with_versions.rb +11 -0
  127. data/spec/migrations/15_create_user_on_shards_of_default_group_with_versions.rb +9 -0
  128. data/spec/migrations/1_create_users_on_master.rb +9 -0
  129. data/spec/migrations/2_create_users_on_canada.rb +11 -0
  130. data/spec/migrations/3_create_users_on_both_shards.rb +11 -0
  131. data/spec/migrations/4_create_users_on_shards_of_a_group.rb +11 -0
  132. data/spec/migrations/5_create_users_on_multiples_groups.rb +11 -0
  133. data/spec/migrations/6_raise_exception_with_invalid_shard_name.rb +11 -0
  134. data/spec/migrations/7_raise_exception_with_invalid_multiple_shard_names.rb +11 -0
  135. data/spec/migrations/8_raise_exception_with_invalid_group_name.rb +11 -0
  136. data/spec/migrations/9_raise_exception_with_multiple_invalid_group_names.rb +11 -0
  137. data/spec/octopus/association_shard_tracking_spec.rb +1036 -0
  138. data/spec/octopus/collection_proxy_spec.rb +16 -0
  139. data/spec/octopus/load_balancing/round_robin_spec.rb +15 -0
  140. data/spec/octopus/log_subscriber_spec.rb +19 -0
  141. data/spec/octopus/migration_spec.rb +151 -0
  142. data/spec/octopus/model_spec.rb +837 -0
  143. data/spec/octopus/octopus_spec.rb +123 -0
  144. data/spec/octopus/proxy_spec.rb +303 -0
  145. data/spec/octopus/query_cache_for_shards_spec.rb +40 -0
  146. data/spec/octopus/relation_proxy_spec.rb +132 -0
  147. data/spec/octopus/replicated_slave_grouped_spec.rb +91 -0
  148. data/spec/octopus/replication_spec.rb +196 -0
  149. data/spec/octopus/scope_proxy_spec.rb +97 -0
  150. data/spec/octopus/sharded_replicated_slave_grouped_spec.rb +55 -0
  151. data/spec/octopus/sharded_spec.rb +33 -0
  152. data/spec/spec_helper.rb +18 -0
  153. data/spec/support/active_record/connection_adapters/modify_config_adapter.rb +15 -0
  154. data/spec/support/database_connection.rb +4 -0
  155. data/spec/support/database_models.rb +118 -0
  156. data/spec/support/octopus_helper.rb +66 -0
  157. data/spec/support/query_count.rb +17 -0
  158. data/spec/support/shared_contexts.rb +18 -0
  159. data/spec/tasks/octopus.rake_spec.rb +32 -0
  160. metadata +351 -0
data/lib/ar-octopus.rb ADDED
@@ -0,0 +1 @@
1
+ require 'octopus'
@@ -0,0 +1,33 @@
1
+ # Implementation courtesy of db-charmer.
2
+ module Octopus
3
+ module AbstractAdapter
4
+ module OctopusShard
5
+ class InstrumenterDecorator < ActiveSupport::ProxyObject
6
+ def initialize(adapter, instrumenter)
7
+ @adapter = adapter
8
+ @instrumenter = instrumenter
9
+ end
10
+
11
+ def instrument(name, payload = {}, &block)
12
+ payload[:octopus_shard] ||= @adapter.octopus_shard
13
+ @instrumenter.instrument(name, payload, &block)
14
+ end
15
+
16
+ def method_missing(meth, *args, &block)
17
+ @instrumenter.send(meth, *args, &block)
18
+ end
19
+ end
20
+
21
+ def octopus_shard
22
+ @config[:octopus_shard]
23
+ end
24
+
25
+ def initialize(*args)
26
+ super
27
+ @instrumenter = InstrumenterDecorator.new(self, @instrumenter)
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ ActiveRecord::ConnectionAdapters::AbstractAdapter.send(:prepend, Octopus::AbstractAdapter::OctopusShard)
@@ -0,0 +1,14 @@
1
+ module Octopus
2
+ module Association
3
+ def self.included(base)
4
+ base.send(:include, Octopus::ShardTracking::Dynamic)
5
+ base.sharded_methods :target_scope
6
+ end
7
+
8
+ def current_shard
9
+ owner.current_shard
10
+ end
11
+ end
12
+ end
13
+
14
+ ActiveRecord::Associations::Association.send(:include, Octopus::Association)
@@ -0,0 +1,74 @@
1
+ module Octopus
2
+ module AssociationShardTracking
3
+ class MismatchedShards < StandardError
4
+ attr_reader :record, :current_shard
5
+
6
+ def initialize(record, current_shard)
7
+ @record = record
8
+ @current_shard = current_shard
9
+ end
10
+
11
+ def message
12
+ [
13
+ "Association Error: Records are from different shards",
14
+ "Record: #{record.inspect}",
15
+ "Current Shard: #{current_shard.inspect}",
16
+ "Current Record Shard: #{record.current_shard.inspect}",
17
+ ].join(" ")
18
+ end
19
+ end
20
+
21
+ def self.extended(base)
22
+ base.send(:include, InstanceMethods)
23
+ end
24
+
25
+ module InstanceMethods
26
+ def connection_on_association=(record)
27
+ return unless ::Octopus.enabled?
28
+ return if !self.class.connection.respond_to?(:current_shard) || !self.respond_to?(:current_shard)
29
+
30
+ if !record.current_shard.nil? && !current_shard.nil? && record.current_shard.to_s != current_shard.to_s
31
+ raise MismatchedShards.new(record, current_shard)
32
+ end
33
+
34
+ record.current_shard = self.class.connection.current_shard = current_shard if should_set_current_shard?
35
+ end
36
+ end
37
+
38
+ def has_many(name, scope = nil, **options, &extension)
39
+ if options == {} && scope.is_a?(Hash)
40
+ default_octopus_opts(scope)
41
+ else
42
+ default_octopus_opts(options)
43
+ end
44
+ super
45
+ end
46
+
47
+ def has_and_belongs_to_many(association_id, scope = nil, options = {}, &extension)
48
+ assign_octopus_opts(scope, options)
49
+ super
50
+ end
51
+
52
+ def default_octopus_opts(options)
53
+ options[:before_add] = [ :connection_on_association=, options[:before_add] ].compact.flatten
54
+ options[:before_remove] = [ :connection_on_association=, options[:before_remove] ].compact.flatten
55
+ end
56
+
57
+ def assign_octopus_opts(scope, options)
58
+ if options == {} && scope.is_a?(Hash)
59
+ default_octopus_opts(scope)
60
+ else
61
+ default_octopus_opts(options)
62
+ end
63
+ end
64
+
65
+ if Octopus.atleast_rails51?
66
+ def has_and_belongs_to_many(association_id, scope = nil, **options, &extension)
67
+ assign_octopus_opts(scope, options)
68
+ super
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ ActiveRecord::Base.extend(Octopus::AssociationShardTracking)
@@ -0,0 +1,17 @@
1
+ module Octopus
2
+ module CollectionAssociation
3
+ def self.included(base)
4
+ if Octopus.atleast_rails51?
5
+ base.sharded_methods :reader, :writer, :ids_reader, :ids_writer, :create, :create!,
6
+ :build, :include?,
7
+ :load_target, :reload, :size, :select
8
+ else
9
+ base.sharded_methods :reader, :writer, :ids_reader, :ids_writer, :create, :create!,
10
+ :build, :any?, :count, :empty?, :first, :include?, :last, :length,
11
+ :load_target, :many?, :reload, :size, :select, :uniq
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ ActiveRecord::Associations::CollectionAssociation.send(:include, Octopus::CollectionAssociation)
@@ -0,0 +1,16 @@
1
+ module Octopus
2
+ module CollectionProxy
3
+ def self.included(base)
4
+ base.send(:include, Octopus::ShardTracking::Dynamic)
5
+ base.sharded_methods :any?, :build, :count, :create, :create!, :concat, :delete, :delete_all,
6
+ :destroy, :destroy_all, :empty?, :find, :first, :include?, :last, :length,
7
+ :many?, :pluck, :replace, :select, :size, :sum, :to_a, :uniq
8
+ end
9
+
10
+ def current_shard
11
+ @association.owner.current_shard
12
+ end
13
+ end
14
+ end
15
+
16
+ ActiveRecord::Associations::CollectionProxy.send(:include, Octopus::CollectionProxy)
@@ -0,0 +1,4 @@
1
+ module Octopus
2
+ class Exception < ::Exception
3
+ end
4
+ end
@@ -0,0 +1,8 @@
1
+ # find_nth / find_nth! must be public here to allow Octopus to call
2
+ # them on the scope proxy object
3
+ if Octopus.rails42? || Octopus.rails50?
4
+ module ActiveRecord::FinderMethods
5
+ public :find_nth
6
+ public :find_nth!
7
+ end
8
+ end
@@ -0,0 +1,20 @@
1
+ require 'octopus/load_balancing'
2
+
3
+ # The round-robin load balancing of slaves belonging to the same shard.
4
+ # It is a pool that contains slaves which queries are distributed to.
5
+ module Octopus
6
+ module LoadBalancing
7
+ class RoundRobin
8
+ def initialize(slaves_list)
9
+ raise Octopus::Exception.new("No slaves available") if slaves_list.empty?
10
+ @slaves_list = slaves_list
11
+ @slave_index = 0
12
+ end
13
+
14
+ # Returns the next available slave in the pool
15
+ def next(options)
16
+ @slaves_list[@slave_index = (@slave_index + 1) % @slaves_list.length]
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,4 @@
1
+ module Octopus
2
+ module LoadBalancing
3
+ end
4
+ end
@@ -0,0 +1,26 @@
1
+ # Implementation courtesy of db-charmer.
2
+ module Octopus
3
+ module LogSubscriber
4
+ def self.included(base)
5
+ base.send(:attr_accessor, :octopus_shard)
6
+
7
+ base.send :alias_method, :sql_without_octopus_shard, :sql
8
+ base.send :alias_method, :sql, :sql_with_octopus_shard
9
+
10
+ base.send :alias_method, :debug_without_octopus_shard, :debug
11
+ base.send :alias_method, :debug, :debug_with_octopus_shard
12
+ end
13
+
14
+ def sql_with_octopus_shard(event)
15
+ self.octopus_shard = event.payload[:octopus_shard]
16
+ sql_without_octopus_shard(event)
17
+ end
18
+
19
+ def debug_with_octopus_shard(msg)
20
+ conn = octopus_shard ? color("[Shard: #{octopus_shard}]", ActiveSupport::LogSubscriber::GREEN, true) : ''
21
+ debug_without_octopus_shard(conn + msg)
22
+ end
23
+ end
24
+ end
25
+
26
+ ActiveRecord::LogSubscriber.send(:include, Octopus::LogSubscriber)
@@ -0,0 +1,236 @@
1
+ require 'set'
2
+ require 'active_support/core_ext/module/aliasing'
3
+ require 'active_support/core_ext/array/wrap'
4
+
5
+ module Octopus
6
+ module Migration
7
+ module InstanceOrClassMethods
8
+ def announce_with_octopus(message)
9
+ announce_without_octopus("#{message} - #{current_shard}")
10
+ end
11
+
12
+ def current_shard
13
+ "Shard: #{connection.current_shard}" if connection.respond_to?(:current_shard)
14
+ end
15
+ end
16
+
17
+ include InstanceOrClassMethods
18
+
19
+ def self.included(base)
20
+ base.extend(ClassMethods)
21
+
22
+ base.send :alias_method, :announce_without_octopus, :announce
23
+ base.send :alias_method, :announce, :announce_with_octopus
24
+
25
+ base.class_attribute :current_shard, :current_group, :current_group_specified, :instance_reader => false, :instance_writer => false
26
+ end
27
+
28
+ module ClassMethods
29
+ def using(*args)
30
+ return self unless connection.is_a?(Octopus::Proxy)
31
+
32
+ self.current_shard = args
33
+ self
34
+ end
35
+
36
+ def using_group(*groups)
37
+ return self unless connection.is_a?(Octopus::Proxy)
38
+
39
+ self.current_group = groups
40
+ self.current_group_specified = true
41
+ self
42
+ end
43
+
44
+ def shards
45
+ shards = Set.new
46
+
47
+ if (groups = (current_group_specified ? current_group : Octopus.config[:default_migration_group]))
48
+ Array.wrap(groups).each do |group|
49
+ group_shards = connection.shards_for_group(group)
50
+ shards.merge(group_shards) if group_shards
51
+ end
52
+ elsif (shard = current_shard)
53
+ shards.merge(Array.wrap(shard))
54
+ end
55
+
56
+ shards.to_a.presence || [Octopus.master_shard]
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ module Octopus
63
+ module Migrator
64
+ def self.included(base)
65
+ unless Octopus.atleast_rails52?
66
+ base.extend(ClassMethods)
67
+
68
+ base.class_eval do
69
+ class << self
70
+ alias_method :migrate_without_octopus, :migrate
71
+ alias_method :migrate, :migrate_with_octopus
72
+
73
+ alias_method :up_without_octopus, :up
74
+ alias_method :up, :up_with_octopus
75
+
76
+ alias_method :down_without_octopus, :down
77
+ alias_method :down, :down_with_octopus
78
+
79
+ alias_method :run_without_octopus, :run
80
+ alias_method :run, :run_with_octopus
81
+
82
+ alias_method :rollback_without_octopus, :rollback
83
+ alias_method :rollback, :rollback_with_octopus
84
+ end
85
+ end
86
+ end
87
+
88
+ base.send :alias_method, :run_without_octopus, :run
89
+ base.send :alias_method, :run, :run_with_octopus
90
+
91
+ base.send :alias_method, :migrate_without_octopus, :migrate
92
+ base.send :alias_method, :migrate, :migrate_with_octopus
93
+
94
+ base.send :alias_method, :migrations_without_octopus, :migrations
95
+ base.send :alias_method, :migrations, :migrations_with_octopus
96
+ end
97
+ if Octopus.atleast_rails52?
98
+ ### Post RAILS 5.2 Migration methods
99
+
100
+ def run_with_octopus(&block)
101
+ return run_without_octopus(&block) unless connection.is_a?(Octopus::Proxy)
102
+ shards = migrations.map(&:shards).flatten.map(&:to_s)
103
+ connection.send_queries_to_multiple_shards(shards) do
104
+ run_without_octopus(&block)
105
+ end
106
+ rescue ActiveRecord::UnknownMigrationVersionError => e
107
+ raise unless migrations(true).detect { |m| m.version == e.version }
108
+ end
109
+
110
+ def migrate_with_octopus(&block)
111
+ return migrate_without_octopus(&block) unless connection.is_a?(Octopus::Proxy)
112
+ shards = migrations.map(&:shards).flatten.map(&:to_s)
113
+ connection.send_queries_to_multiple_shards(shards) do
114
+ migrate_without_octopus(&block)
115
+ end
116
+ rescue ActiveRecord::UnknownMigrationVersionError => e
117
+ raise unless migrations(true).detect { |m| m.version == e.version }
118
+ end
119
+
120
+ def migrations_with_octopus(shard_agnostic = true)
121
+ migrations = migrations_without_octopus
122
+ return migrations if !connection.is_a?(Octopus::Proxy) || shard_agnostic
123
+
124
+ migrations.select { |m| m.shards.include?(connection.current_shard.to_sym) }
125
+ end
126
+
127
+ private
128
+
129
+ def connection
130
+ ActiveRecord::Base.connection
131
+ end
132
+
133
+ else
134
+ ### Pre RAILS 5.2 Migration methods
135
+
136
+ def run_with_octopus(&block)
137
+ run_without_octopus(&block)
138
+ rescue ActiveRecord::UnknownMigrationVersionError => e
139
+ raise unless migrations(true).detect { |m| m.version == e.version }
140
+ end
141
+
142
+ def migrate_with_octopus(&block)
143
+ migrate_without_octopus(&block)
144
+ rescue ActiveRecord::UnknownMigrationVersionError => e
145
+ raise unless migrations(true).detect { |m| m.version == e.version }
146
+ end
147
+
148
+ def migrations_with_octopus(shard_agnostic = false)
149
+ connection = ActiveRecord::Base.connection
150
+ migrations = migrations_without_octopus
151
+ return migrations if !connection.is_a?(Octopus::Proxy) || shard_agnostic
152
+
153
+ migrations.select { |m| m.shards.include?(connection.current_shard.to_sym) }
154
+ end
155
+
156
+ module ClassMethods
157
+ def migrate_with_octopus(migrations_paths, target_version = nil, &block)
158
+ return migrate_without_octopus(migrations_paths, target_version, &block) unless connection.is_a?(Octopus::Proxy)
159
+
160
+ connection.send_queries_to_multiple_shards(connection.shard_names) do
161
+ migrate_without_octopus(migrations_paths, target_version, &block)
162
+ end
163
+ end
164
+
165
+ def up_with_octopus(migrations_paths, target_version = nil, &block)
166
+ return up_without_octopus(migrations_paths, target_version, &block) unless connection.is_a?(Octopus::Proxy)
167
+ return up_without_octopus(migrations_paths, target_version, &block) unless connection.current_shard.to_s == Octopus.master_shard.to_s
168
+
169
+ connection.send_queries_to_multiple_shards(connection.shard_names) do
170
+ up_without_octopus(migrations_paths, target_version, &block)
171
+ end
172
+ end
173
+
174
+ def down_with_octopus(migrations_paths, target_version = nil, &block)
175
+ return down_without_octopus(migrations_paths, target_version, &block) unless connection.is_a?(Octopus::Proxy)
176
+ return down_without_octopus(migrations_paths, target_version, &block) unless connection.current_shard.to_s == Octopus.master_shard.to_s
177
+
178
+ connection.send_queries_to_multiple_shards(connection.shard_names) do
179
+ down_without_octopus(migrations_paths, target_version, &block)
180
+ end
181
+ end
182
+
183
+ def run_with_octopus(direction, migrations_paths, target_version)
184
+ return run_without_octopus(direction, migrations_paths, target_version) unless connection.is_a?(Octopus::Proxy)
185
+
186
+ connection.send_queries_to_multiple_shards(connection.shard_names) do
187
+ run_without_octopus(direction, migrations_paths, target_version)
188
+ end
189
+ end
190
+
191
+ def rollback_with_octopus(migrations_paths, steps = 1)
192
+ return rollback_without_octopus(migrations_paths, steps) unless connection.is_a?(Octopus::Proxy)
193
+
194
+ connection.send_queries_to_multiple_shards(connection.shard_names) do
195
+ rollback_without_octopus(migrations_paths, steps)
196
+ end
197
+ end
198
+
199
+ private
200
+
201
+ def connection
202
+ ActiveRecord::Base.connection
203
+ end
204
+ end
205
+ end
206
+ end
207
+ end
208
+
209
+ module Octopus
210
+ module MigrationProxy
211
+ def shards
212
+ migration.class.shards
213
+ end
214
+ end
215
+ end
216
+
217
+ module Octopus
218
+ module UnknownMigrationVersionError
219
+ def self.included(base)
220
+ base.send :alias_method, :initialize_without_octopus, :initialize
221
+ base.send :alias_method, :initialize, :initialize_with_octopus
222
+
223
+ base.send(:attr_accessor, :version)
224
+ end
225
+
226
+ def initialize_with_octopus(version)
227
+ @version = version
228
+ initialize_without_octopus(version)
229
+ end
230
+ end
231
+ end
232
+
233
+ ActiveRecord::Migration.send(:include, Octopus::Migration)
234
+ ActiveRecord::Migrator.send(:include, Octopus::Migrator)
235
+ ActiveRecord::MigrationProxy.send(:include, Octopus::MigrationProxy)
236
+ ActiveRecord::UnknownMigrationVersionError.send(:include, Octopus::UnknownMigrationVersionError)
@@ -0,0 +1,216 @@
1
+ require 'active_support/deprecation'
2
+
3
+ module Octopus
4
+ module Model
5
+ def self.extended(base)
6
+ base.send(:include, Octopus::ShardTracking::Attribute)
7
+ base.send(:include, InstanceMethods)
8
+ base.extend(ClassMethods)
9
+ end
10
+
11
+ module SharedMethods
12
+ def using(shard)
13
+ if block_given?
14
+ raise Octopus::Exception, <<-EOF
15
+ #{name}.using is not allowed to receive a block, it works just like a regular scope.
16
+
17
+ If you are trying to scope everything to a specific shard, use Octopus.using instead.
18
+ EOF
19
+ end
20
+
21
+ if Octopus.enabled?
22
+ Octopus::ScopeProxy.new(shard, self)
23
+ else
24
+ self
25
+ end
26
+ end
27
+ end
28
+
29
+ module InstanceMethods
30
+ include SharedMethods
31
+
32
+ def self.included(base)
33
+ base.send(:alias_method, :equality_without_octopus, :==)
34
+ base.send(:alias_method, :==, :equality_with_octopus)
35
+ base.send(:alias_method, :eql?, :==)
36
+ base.send(:alias_method, :perform_validations_without_octopus, :perform_validations)
37
+ base.send(:alias_method, :perform_validations, :perform_validations_with_octopus)
38
+ end
39
+
40
+ def set_current_shard
41
+ return unless Octopus.enabled?
42
+ shard = self.class.connection_proxy.current_shard
43
+ self.current_shard = shard if self.class.allowed_shard?(shard)
44
+ end
45
+
46
+ def init_with(coder)
47
+ obj = super
48
+
49
+ return obj unless Octopus.enabled?
50
+ return obj if obj.class.connection_proxy.current_model_replicated?
51
+
52
+ current_shard_value = coder['attributes']['current_shard'].value if coder['attributes']['current_shard'].present? && coder['attributes']['current_shard'].value.present?
53
+
54
+ coder['attributes'].send(:attributes).send(:values).delete('current_shard')
55
+ coder['attributes'].send(:attributes).send(:delegate_hash).delete('current_shard')
56
+
57
+ obj.current_shard = current_shard_value if current_shard_value.present?
58
+ obj
59
+ end
60
+
61
+ def should_set_current_shard?
62
+ self.respond_to?(:current_shard) && !current_shard.nil?
63
+ end
64
+
65
+ def equality_with_octopus(comparison_object)
66
+ equality_without_octopus(comparison_object) && comparison_object.current_shard.to_s == current_shard.to_s
67
+ end
68
+
69
+ def perform_validations_with_octopus(*args)
70
+ if Octopus.enabled? && should_set_current_shard?
71
+ Octopus.using(current_shard) do
72
+ perform_validations_without_octopus(*args)
73
+ end
74
+ else
75
+ perform_validations_without_octopus(*args)
76
+ end
77
+ end
78
+ end
79
+
80
+ module ClassMethods
81
+ include SharedMethods
82
+
83
+ def self.extended(base)
84
+ base.class_attribute(:replicated)
85
+ base.class_attribute(:sharded)
86
+ base.class_attribute(:allowed_shards)
87
+ base.hijack_methods
88
+ end
89
+
90
+ def replicated_model
91
+ self.replicated = true
92
+ end
93
+
94
+ def sharded_model
95
+ self.sharded = true
96
+ end
97
+
98
+ def allow_shard(*shards)
99
+ self.allowed_shards ||= []
100
+ self.allowed_shards += shards
101
+ end
102
+
103
+ def hijack_methods
104
+ after_initialize :set_current_shard
105
+
106
+ around_save :run_on_shard, :unless => lambda { self.class.custom_octopus_connection }
107
+
108
+ class_attribute :custom_octopus_connection
109
+
110
+ class << self
111
+ attr_accessor :custom_octopus_table_name
112
+
113
+ alias_method :connection_without_octopus, :connection
114
+ alias_method :connection, :connection_with_octopus
115
+
116
+ alias_method :connection_pool_without_octopus, :connection_pool
117
+ alias_method :connection_pool, :connection_pool_with_octopus
118
+
119
+ alias_method :clear_all_connections_without_octopus!, :clear_all_connections!
120
+ alias_method :clear_all_connections!, :clear_all_connections_with_octopus!
121
+
122
+ alias_method :clear_active_connections_without_octopus!, :clear_active_connections!
123
+ alias_method :clear_active_connections!, :clear_active_connections_with_octopus!
124
+
125
+ alias_method :connected_without_octopus?, :connected?
126
+ alias_method :connected?, :connected_with_octopus?
127
+
128
+ def table_name=(value = nil)
129
+ self.custom_octopus_table_name = true
130
+ super
131
+ end
132
+ end
133
+ end
134
+
135
+ def connection_proxy
136
+ ActiveRecord::Base.class_variable_defined?(:@@connection_proxy) &&
137
+ ActiveRecord::Base.class_variable_get(:@@connection_proxy) ||
138
+ ActiveRecord::Base.class_variable_set(:@@connection_proxy, Octopus::Proxy.new)
139
+ end
140
+
141
+ def should_use_normal_connection?
142
+ if !Octopus.enabled?
143
+ true
144
+ elsif custom_octopus_connection
145
+ !connection_proxy.block || !allowed_shard?(connection_proxy.current_shard)
146
+ end
147
+ end
148
+
149
+ def allowed_shard?(shard)
150
+ if custom_octopus_connection
151
+ allowed_shards && shard && (allowed_shards.include?(shard.to_s) || allowed_shards.include?(shard.to_sym))
152
+ else
153
+ true
154
+ end
155
+ end
156
+
157
+ def connection_with_octopus
158
+ if should_use_normal_connection?
159
+ connection_without_octopus
160
+ else
161
+ connection_proxy.current_model = self
162
+ connection_proxy
163
+ end
164
+ end
165
+
166
+ def connection_pool_with_octopus
167
+ if should_use_normal_connection?
168
+ connection_pool_without_octopus
169
+ else
170
+ connection_proxy.connection_pool
171
+ end
172
+ end
173
+
174
+ def clear_active_connections_with_octopus!
175
+ if should_use_normal_connection?
176
+ clear_active_connections_without_octopus!
177
+ else
178
+ connection_proxy.clear_active_connections!
179
+ end
180
+ end
181
+
182
+ def clear_all_connections_with_octopus!
183
+ if should_use_normal_connection?
184
+ clear_all_connections_without_octopus!
185
+ else
186
+ connection_proxy.clear_all_connections!
187
+ end
188
+ end
189
+
190
+ def connected_with_octopus?
191
+ if should_use_normal_connection?
192
+ connected_without_octopus?
193
+ else
194
+ connection_proxy.connected?
195
+ end
196
+ end
197
+
198
+ def set_table_name_with_octopus(value = nil, &block)
199
+ self.custom_octopus_table_name = true
200
+ set_table_name_without_octopus(value, &block)
201
+ end
202
+
203
+ def octopus_establish_connection(spec = ENV['DATABASE_URL'])
204
+ self.custom_octopus_connection = true if spec
205
+ establish_connection(spec)
206
+ end
207
+
208
+ def octopus_set_table_name(value = nil)
209
+ ActiveSupport::Deprecation.warn 'Calling `octopus_set_table_name` is deprecated and will be removed in Octopus 1.0.', caller
210
+ set_table_name(value)
211
+ end
212
+ end
213
+ end
214
+ end
215
+
216
+ ActiveRecord::Base.extend(Octopus::Model)