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.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.rubocop.yml +46 -0
- data/.rubocop_todo.yml +56 -0
- data/.travis.yml +18 -0
- data/Appraisals +16 -0
- data/Gemfile +4 -0
- data/README.mkdn +257 -0
- data/Rakefile +175 -0
- data/TODO.txt +7 -0
- data/ar-octopus.gemspec +44 -0
- data/gemfiles/rails42.gemfile +7 -0
- data/gemfiles/rails5.gemfile +7 -0
- data/gemfiles/rails51.gemfile +7 -0
- data/gemfiles/rails52.gemfile +7 -0
- data/lib/ar-octopus.rb +1 -0
- data/lib/octopus/abstract_adapter.rb +33 -0
- data/lib/octopus/association.rb +14 -0
- data/lib/octopus/association_shard_tracking.rb +74 -0
- data/lib/octopus/collection_association.rb +17 -0
- data/lib/octopus/collection_proxy.rb +16 -0
- data/lib/octopus/exception.rb +4 -0
- data/lib/octopus/finder_methods.rb +8 -0
- data/lib/octopus/load_balancing/round_robin.rb +20 -0
- data/lib/octopus/load_balancing.rb +4 -0
- data/lib/octopus/log_subscriber.rb +26 -0
- data/lib/octopus/migration.rb +236 -0
- data/lib/octopus/model.rb +216 -0
- data/lib/octopus/persistence.rb +45 -0
- data/lib/octopus/proxy.rb +399 -0
- data/lib/octopus/proxy_config.rb +251 -0
- data/lib/octopus/query_cache_for_shards.rb +24 -0
- data/lib/octopus/railtie.rb +11 -0
- data/lib/octopus/relation_proxy.rb +74 -0
- data/lib/octopus/result_patch.rb +19 -0
- data/lib/octopus/scope_proxy.rb +68 -0
- data/lib/octopus/shard_tracking/attribute.rb +22 -0
- data/lib/octopus/shard_tracking/dynamic.rb +11 -0
- data/lib/octopus/shard_tracking.rb +46 -0
- data/lib/octopus/singular_association.rb +9 -0
- data/lib/octopus/slave_group.rb +13 -0
- data/lib/octopus/version.rb +3 -0
- data/lib/octopus.rb +209 -0
- data/lib/tasks/octopus.rake +16 -0
- data/sample_app/.gitignore +4 -0
- data/sample_app/.rspec +1 -0
- data/sample_app/Gemfile +20 -0
- data/sample_app/Gemfile.lock +155 -0
- data/sample_app/README +3 -0
- data/sample_app/README.rdoc +261 -0
- data/sample_app/Rakefile +7 -0
- data/sample_app/app/assets/images/rails.png +0 -0
- data/sample_app/app/assets/javascripts/application.js +15 -0
- data/sample_app/app/assets/stylesheets/application.css +13 -0
- data/sample_app/app/controllers/application_controller.rb +4 -0
- data/sample_app/app/helpers/application_helper.rb +2 -0
- data/sample_app/app/mailers/.gitkeep +0 -0
- data/sample_app/app/models/.gitkeep +0 -0
- data/sample_app/app/models/item.rb +3 -0
- data/sample_app/app/models/user.rb +3 -0
- data/sample_app/app/views/layouts/application.html.erb +14 -0
- data/sample_app/autotest/discover.rb +2 -0
- data/sample_app/config/application.rb +62 -0
- data/sample_app/config/boot.rb +6 -0
- data/sample_app/config/cucumber.yml +8 -0
- data/sample_app/config/database.yml +28 -0
- data/sample_app/config/environment.rb +5 -0
- data/sample_app/config/environments/development.rb +37 -0
- data/sample_app/config/environments/production.rb +67 -0
- data/sample_app/config/environments/test.rb +37 -0
- data/sample_app/config/initializers/backtrace_silencers.rb +7 -0
- data/sample_app/config/initializers/inflections.rb +15 -0
- data/sample_app/config/initializers/mime_types.rb +5 -0
- data/sample_app/config/initializers/secret_token.rb +7 -0
- data/sample_app/config/initializers/session_store.rb +8 -0
- data/sample_app/config/initializers/wrap_parameters.rb +14 -0
- data/sample_app/config/locales/en.yml +5 -0
- data/sample_app/config/routes.rb +58 -0
- data/sample_app/config/shards.yml +28 -0
- data/sample_app/config.ru +4 -0
- data/sample_app/db/migrate/20100720172715_create_users.rb +15 -0
- data/sample_app/db/migrate/20100720172730_create_items.rb +16 -0
- data/sample_app/db/migrate/20100720210335_create_sample_users.rb +11 -0
- data/sample_app/db/schema.rb +29 -0
- data/sample_app/db/seeds.rb +16 -0
- data/sample_app/doc/README_FOR_APP +2 -0
- data/sample_app/features/migrate.feature +45 -0
- data/sample_app/features/seed.feature +15 -0
- data/sample_app/features/step_definitions/seeds_steps.rb +13 -0
- data/sample_app/features/step_definitions/web_steps.rb +218 -0
- data/sample_app/features/support/database.rb +13 -0
- data/sample_app/features/support/env.rb +57 -0
- data/sample_app/features/support/paths.rb +33 -0
- data/sample_app/lib/assets/.gitkeep +0 -0
- data/sample_app/lib/tasks/.gitkeep +0 -0
- data/sample_app/lib/tasks/cucumber.rake +64 -0
- data/sample_app/log/.gitkeep +0 -0
- data/sample_app/public/404.html +26 -0
- data/sample_app/public/422.html +26 -0
- data/sample_app/public/500.html +26 -0
- data/sample_app/public/favicon.ico +0 -0
- data/sample_app/public/images/rails.png +0 -0
- data/sample_app/public/index.html +279 -0
- data/sample_app/public/javascripts/application.js +2 -0
- data/sample_app/public/javascripts/controls.js +965 -0
- data/sample_app/public/javascripts/dragdrop.js +974 -0
- data/sample_app/public/javascripts/effects.js +1123 -0
- data/sample_app/public/javascripts/prototype.js +4874 -0
- data/sample_app/public/javascripts/rails.js +118 -0
- data/sample_app/public/robots.txt +5 -0
- data/sample_app/public/stylesheets/.gitkeep +0 -0
- data/sample_app/script/cucumber +10 -0
- data/sample_app/script/rails +6 -0
- data/sample_app/spec/models/item_spec.rb +5 -0
- data/sample_app/spec/models/user_spec.rb +5 -0
- data/sample_app/spec/spec_helper.rb +27 -0
- data/sample_app/vendor/assets/javascripts/.gitkeep +0 -0
- data/sample_app/vendor/assets/stylesheets/.gitkeep +0 -0
- data/sample_app/vendor/plugins/.gitkeep +0 -0
- data/spec/config/shards.yml +231 -0
- data/spec/migrations/10_create_users_using_replication.rb +9 -0
- data/spec/migrations/11_add_field_in_all_slaves.rb +11 -0
- data/spec/migrations/12_create_users_using_block.rb +23 -0
- data/spec/migrations/13_create_users_using_block_and_using.rb +15 -0
- data/spec/migrations/14_create_users_on_shards_of_a_group_with_versions.rb +11 -0
- data/spec/migrations/15_create_user_on_shards_of_default_group_with_versions.rb +9 -0
- data/spec/migrations/1_create_users_on_master.rb +9 -0
- data/spec/migrations/2_create_users_on_canada.rb +11 -0
- data/spec/migrations/3_create_users_on_both_shards.rb +11 -0
- data/spec/migrations/4_create_users_on_shards_of_a_group.rb +11 -0
- data/spec/migrations/5_create_users_on_multiples_groups.rb +11 -0
- data/spec/migrations/6_raise_exception_with_invalid_shard_name.rb +11 -0
- data/spec/migrations/7_raise_exception_with_invalid_multiple_shard_names.rb +11 -0
- data/spec/migrations/8_raise_exception_with_invalid_group_name.rb +11 -0
- data/spec/migrations/9_raise_exception_with_multiple_invalid_group_names.rb +11 -0
- data/spec/octopus/association_shard_tracking_spec.rb +1036 -0
- data/spec/octopus/collection_proxy_spec.rb +16 -0
- data/spec/octopus/load_balancing/round_robin_spec.rb +15 -0
- data/spec/octopus/log_subscriber_spec.rb +19 -0
- data/spec/octopus/migration_spec.rb +151 -0
- data/spec/octopus/model_spec.rb +837 -0
- data/spec/octopus/octopus_spec.rb +123 -0
- data/spec/octopus/proxy_spec.rb +303 -0
- data/spec/octopus/query_cache_for_shards_spec.rb +40 -0
- data/spec/octopus/relation_proxy_spec.rb +132 -0
- data/spec/octopus/replicated_slave_grouped_spec.rb +91 -0
- data/spec/octopus/replication_spec.rb +196 -0
- data/spec/octopus/scope_proxy_spec.rb +97 -0
- data/spec/octopus/sharded_replicated_slave_grouped_spec.rb +55 -0
- data/spec/octopus/sharded_spec.rb +33 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/support/active_record/connection_adapters/modify_config_adapter.rb +15 -0
- data/spec/support/database_connection.rb +4 -0
- data/spec/support/database_models.rb +118 -0
- data/spec/support/octopus_helper.rb +66 -0
- data/spec/support/query_count.rb +17 -0
- data/spec/support/shared_contexts.rb +18 -0
- data/spec/tasks/octopus.rake_spec.rb +32 -0
- 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,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,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)
|