misha-ar-octopus 0.8.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (157) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +46 -0
  5. data/.rubocop_todo.yml +56 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +18 -0
  8. data/Appraisals +16 -0
  9. data/Gemfile +4 -0
  10. data/README.mkdn +242 -0
  11. data/Rakefile +172 -0
  12. data/TODO.txt +7 -0
  13. data/ar-octopus.gemspec +42 -0
  14. data/gemfiles/rails32.gemfile +7 -0
  15. data/gemfiles/rails4.gemfile +7 -0
  16. data/gemfiles/rails41.gemfile +7 -0
  17. data/gemfiles/rails42.gemfile +7 -0
  18. data/init.rb +1 -0
  19. data/lib/ar-octopus.rb +1 -0
  20. data/lib/octopus.rb +181 -0
  21. data/lib/octopus/abstract_adapter.rb +35 -0
  22. data/lib/octopus/association.rb +13 -0
  23. data/lib/octopus/association_shard_tracking.rb +114 -0
  24. data/lib/octopus/collection_association.rb +11 -0
  25. data/lib/octopus/collection_proxy.rb +16 -0
  26. data/lib/octopus/exception.rb +4 -0
  27. data/lib/octopus/has_and_belongs_to_many_association.rb +9 -0
  28. data/lib/octopus/load_balancing.rb +4 -0
  29. data/lib/octopus/load_balancing/round_robin.rb +20 -0
  30. data/lib/octopus/log_subscriber.rb +22 -0
  31. data/lib/octopus/migration.rb +168 -0
  32. data/lib/octopus/model.rb +210 -0
  33. data/lib/octopus/persistence.rb +39 -0
  34. data/lib/octopus/proxy.rb +534 -0
  35. data/lib/octopus/railtie.rb +11 -0
  36. data/lib/octopus/relation_proxy.rb +35 -0
  37. data/lib/octopus/scope_proxy.rb +61 -0
  38. data/lib/octopus/shard_tracking.rb +41 -0
  39. data/lib/octopus/shard_tracking/attribute.rb +22 -0
  40. data/lib/octopus/shard_tracking/dynamic.rb +11 -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/tasks/octopus.rake +16 -0
  45. data/rails/init.rb +1 -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/README +3 -0
  50. data/sample_app/README.rdoc +261 -0
  51. data/sample_app/Rakefile +7 -0
  52. data/sample_app/app/assets/images/rails.png +0 -0
  53. data/sample_app/app/assets/javascripts/application.js +15 -0
  54. data/sample_app/app/assets/stylesheets/application.css +13 -0
  55. data/sample_app/app/controllers/application_controller.rb +4 -0
  56. data/sample_app/app/helpers/application_helper.rb +2 -0
  57. data/sample_app/app/mailers/.gitkeep +0 -0
  58. data/sample_app/app/models/.gitkeep +0 -0
  59. data/sample_app/app/models/item.rb +3 -0
  60. data/sample_app/app/models/user.rb +3 -0
  61. data/sample_app/app/views/layouts/application.html.erb +14 -0
  62. data/sample_app/autotest/discover.rb +2 -0
  63. data/sample_app/config.ru +4 -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/db/migrate/20100720172715_create_users.rb +15 -0
  82. data/sample_app/db/migrate/20100720172730_create_items.rb +16 -0
  83. data/sample_app/db/migrate/20100720210335_create_sample_users.rb +11 -0
  84. data/sample_app/db/schema.rb +29 -0
  85. data/sample_app/db/seeds.rb +16 -0
  86. data/sample_app/doc/README_FOR_APP +2 -0
  87. data/sample_app/features/migrate.feature +45 -0
  88. data/sample_app/features/seed.feature +15 -0
  89. data/sample_app/features/step_definitions/seeds_steps.rb +13 -0
  90. data/sample_app/features/step_definitions/web_steps.rb +218 -0
  91. data/sample_app/features/support/database.rb +13 -0
  92. data/sample_app/features/support/env.rb +57 -0
  93. data/sample_app/features/support/paths.rb +33 -0
  94. data/sample_app/lib/assets/.gitkeep +0 -0
  95. data/sample_app/lib/tasks/.gitkeep +0 -0
  96. data/sample_app/lib/tasks/cucumber.rake +64 -0
  97. data/sample_app/log/.gitkeep +0 -0
  98. data/sample_app/public/404.html +26 -0
  99. data/sample_app/public/422.html +26 -0
  100. data/sample_app/public/500.html +26 -0
  101. data/sample_app/public/favicon.ico +0 -0
  102. data/sample_app/public/images/rails.png +0 -0
  103. data/sample_app/public/index.html +279 -0
  104. data/sample_app/public/javascripts/application.js +2 -0
  105. data/sample_app/public/javascripts/controls.js +965 -0
  106. data/sample_app/public/javascripts/dragdrop.js +974 -0
  107. data/sample_app/public/javascripts/effects.js +1123 -0
  108. data/sample_app/public/javascripts/prototype.js +4874 -0
  109. data/sample_app/public/javascripts/rails.js +118 -0
  110. data/sample_app/public/robots.txt +5 -0
  111. data/sample_app/public/stylesheets/.gitkeep +0 -0
  112. data/sample_app/script/cucumber +10 -0
  113. data/sample_app/script/rails +6 -0
  114. data/sample_app/spec/models/item_spec.rb +5 -0
  115. data/sample_app/spec/models/user_spec.rb +5 -0
  116. data/sample_app/spec/spec_helper.rb +27 -0
  117. data/sample_app/vendor/assets/javascripts/.gitkeep +0 -0
  118. data/sample_app/vendor/assets/stylesheets/.gitkeep +0 -0
  119. data/sample_app/vendor/plugins/.gitkeep +0 -0
  120. data/spec/config/shards.yml +217 -0
  121. data/spec/migrations/10_create_users_using_replication.rb +9 -0
  122. data/spec/migrations/11_add_field_in_all_slaves.rb +11 -0
  123. data/spec/migrations/12_create_users_using_block.rb +23 -0
  124. data/spec/migrations/13_create_users_using_block_and_using.rb +15 -0
  125. data/spec/migrations/14_create_users_on_shards_of_a_group_with_versions.rb +11 -0
  126. data/spec/migrations/15_create_user_on_shards_of_default_group_with_versions.rb +9 -0
  127. data/spec/migrations/1_create_users_on_master.rb +9 -0
  128. data/spec/migrations/2_create_users_on_canada.rb +11 -0
  129. data/spec/migrations/3_create_users_on_both_shards.rb +11 -0
  130. data/spec/migrations/4_create_users_on_shards_of_a_group.rb +11 -0
  131. data/spec/migrations/5_create_users_on_multiples_groups.rb +11 -0
  132. data/spec/migrations/6_raise_exception_with_invalid_shard_name.rb +11 -0
  133. data/spec/migrations/7_raise_exception_with_invalid_multiple_shard_names.rb +11 -0
  134. data/spec/migrations/8_raise_exception_with_invalid_group_name.rb +11 -0
  135. data/spec/migrations/9_raise_exception_with_multiple_invalid_group_names.rb +11 -0
  136. data/spec/octopus/association_shard_tracking_spec.rb +714 -0
  137. data/spec/octopus/collection_proxy_spec.rb +16 -0
  138. data/spec/octopus/log_subscriber_spec.rb +19 -0
  139. data/spec/octopus/migration_spec.rb +115 -0
  140. data/spec/octopus/model_spec.rb +693 -0
  141. data/spec/octopus/octopus_spec.rb +123 -0
  142. data/spec/octopus/proxy_spec.rb +307 -0
  143. data/spec/octopus/relation_proxy_spec.rb +93 -0
  144. data/spec/octopus/replicated_slave_grouped_spec.rb +91 -0
  145. data/spec/octopus/replication_spec.rb +137 -0
  146. data/spec/octopus/scope_proxy_spec.rb +63 -0
  147. data/spec/octopus/sharded_replicated_slave_grouped_spec.rb +55 -0
  148. data/spec/octopus/sharded_spec.rb +33 -0
  149. data/spec/spec_helper.rb +16 -0
  150. data/spec/support/active_record/connection_adapters/modify_config_adapter.rb +15 -0
  151. data/spec/support/database_connection.rb +4 -0
  152. data/spec/support/database_models.rb +118 -0
  153. data/spec/support/octopus_helper.rb +55 -0
  154. data/spec/support/query_count.rb +17 -0
  155. data/spec/support/shared_contexts.rb +18 -0
  156. data/spec/tasks/octopus.rake_spec.rb +32 -0
  157. metadata +388 -0
data/TODO.txt ADDED
@@ -0,0 +1,7 @@
1
+ - Find the best way to move data along shards.
2
+ - Improve documentation.
3
+ - Automatic configuration file for shards.
4
+ - Support specifics cases of sharding/replication.
5
+ - Basic algorithm to do load balancing between slaves server (handling servers down)
6
+ - Support replication + sharding
7
+ - Wiki about validation
@@ -0,0 +1,42 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $LOAD_PATH.push File.expand_path('../lib', __FILE__)
3
+ require 'octopus/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'misha-ar-octopus'
7
+ s.version = Octopus::VERSION
8
+ s.authors = ['Thiago Pradi', 'Mike Perham', 'Gabriel Sobrinho']
9
+ s.email = ['tchandy@gmail.com', 'mperham@gmail.com', 'gabriel.sobrinho@gmail.com']
10
+ s.homepage = 'https://github.com/tchandy/octopus'
11
+ s.summary = 'Easy Database Sharding for ActiveRecord'
12
+ s.description = 'This gem allows you to use sharded databases with ActiveRecord. This also provides a interface for replication and for running migrations with multiples shards.'
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
17
+ s.require_paths = ['lib']
18
+
19
+ s.post_install_message = "Important: If you are upgrading from < Octopus 0.5.0 you need to run:\n" \
20
+ "$ rake octopus:copy_schema_versions\n\n" \
21
+ 'Octopus now stores schema version information in each shard and migrations will not ' \
22
+ 'work properly unless this task is invoked.'
23
+
24
+ s.add_dependency 'activerecord', '>= 3.2.0'
25
+ s.add_dependency 'activesupport', '>= 3.2.0'
26
+
27
+ s.add_development_dependency 'appraisal', '>= 0.3.8'
28
+ s.add_development_dependency 'mysql2', '~> 0.3.18'
29
+ s.add_development_dependency 'pg', '>= 0.11.0'
30
+ s.add_development_dependency 'rake', '>= 0.8.7'
31
+ s.add_development_dependency 'rspec', '>= 3'
32
+ s.add_development_dependency 'rubocop'
33
+ s.add_development_dependency 'sqlite3', '>= 1.3.4'
34
+
35
+ if RUBY_VERSION < '2.0.0'
36
+ s.add_development_dependency 'pry-debugger'
37
+ else
38
+ s.add_development_dependency 'pry-byebug'
39
+ end
40
+
41
+ s.license = 'MIT'
42
+ end
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 3.2.0"
6
+
7
+ gemspec :path => "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 4.0.0"
6
+
7
+ gemspec :path => "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 4.1.0"
6
+
7
+ gemspec :path => "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 4.2.0"
6
+
7
+ gemspec :path => "../"
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), 'rails', 'init')
data/lib/ar-octopus.rb ADDED
@@ -0,0 +1 @@
1
+ require 'octopus'
data/lib/octopus.rb ADDED
@@ -0,0 +1,181 @@
1
+ require 'active_record'
2
+ require 'active_support/version'
3
+ require 'active_support/core_ext/class'
4
+
5
+ require 'yaml'
6
+ require 'erb'
7
+
8
+ module Octopus
9
+ def self.env
10
+ @env ||= 'octopus'
11
+ end
12
+
13
+ def self.rails_env
14
+ @rails_env ||= defined?(::Rails.env) ? Rails.env.to_s : 'shards'
15
+ end
16
+
17
+ def self.config
18
+ @config ||= begin
19
+ file_name = File.join(Octopus.directory, 'config/shards.yml').to_s
20
+
21
+ if File.exist?(file_name) || File.symlink?(file_name)
22
+ config ||= HashWithIndifferentAccess.new(YAML.load(ERB.new(File.read(file_name)).result))[Octopus.env]
23
+ else
24
+ config ||= HashWithIndifferentAccess.new
25
+ end
26
+
27
+ config
28
+ end
29
+ end
30
+
31
+ def self.load_balancer=(balancer)
32
+ @load_balancer = balancer
33
+ end
34
+
35
+ def self.load_balancer
36
+ @load_balancer ||= Octopus::LoadBalancing::RoundRobin
37
+ end
38
+
39
+ def self.master_shard
40
+ ((config && config[:master_shard]) || :master).to_sym
41
+ end
42
+
43
+ # Public: Whether or not Octopus is configured and should hook into the
44
+ # current environment. Checks the environments config option for the Rails
45
+ # environment by default.
46
+ #
47
+ # Returns a boolean
48
+ def self.enabled?
49
+ if defined?(::Rails.env)
50
+ Octopus.environments.include?(Rails.env.to_s)
51
+ else
52
+ # TODO: This doens't feel right but !Octopus.config.blank? is breaking a
53
+ # test. Also, Octopus.config is always returning a hash.
54
+ Octopus.config
55
+ end
56
+ end
57
+
58
+ # Returns the Rails.root_to_s when you are using rails
59
+ # Running the current directory in a generic Ruby process
60
+ def self.directory
61
+ @directory ||= defined?(::Rails.root) ? Rails.root.to_s : Dir.pwd
62
+ end
63
+
64
+ # This is the default way to do Octopus Setup
65
+ # Available variables:
66
+ # :enviroments => the enviroments that octopus will run. default: 'production'
67
+ def self.setup
68
+ yield self
69
+ end
70
+
71
+ def self.environments=(environments)
72
+ @environments = environments.map(&:to_s)
73
+ end
74
+
75
+ def self.environments
76
+ @environments ||= config['environments'] || ['production']
77
+ end
78
+
79
+ def self.robust_environments=(environments)
80
+ @robust_environments = environments.map(&:to_s)
81
+ end
82
+
83
+ # Environments in which to swallow failures from a single shard
84
+ # when iterating through all.
85
+ def self.robust_environments
86
+ @robust_environments ||= config['robust_environments'] || ['production']
87
+ end
88
+
89
+ def self.robust_environment?
90
+ robust_environments.include? rails_env
91
+ end
92
+
93
+ def self.rails3?
94
+ ActiveRecord::VERSION::MAJOR <= 3
95
+ end
96
+
97
+ def self.rails4?
98
+ ActiveRecord::VERSION::MAJOR >= 4
99
+ end
100
+
101
+ def self.rails41?
102
+ rails4? && ActiveRecord::VERSION::MINOR >= 1
103
+ end
104
+
105
+ attr_writer :logger
106
+
107
+ def self.logger
108
+ if defined?(Rails.logger)
109
+ @logger ||= Rails.logger
110
+ else
111
+ @logger ||= Logger.new($stderr)
112
+ end
113
+ end
114
+
115
+ def self.shards=(shards)
116
+ config[rails_env] = HashWithIndifferentAccess.new(shards)
117
+ ActiveRecord::Base.connection.initialize_shards(@config)
118
+ end
119
+
120
+ def self.using(shard, options = {}, &block)
121
+ conn = ActiveRecord::Base.connection
122
+
123
+ if conn.is_a?(Octopus::Proxy)
124
+ conn.run_queries_on_shard(shard, options, &block)
125
+ else
126
+ yield
127
+ end
128
+ end
129
+
130
+ def self.using_group(group, &block)
131
+ conn = ActiveRecord::Base.connection
132
+
133
+ if conn.is_a?(Octopus::Proxy)
134
+ conn.send_queries_to_group(group, &block)
135
+ else
136
+ yield
137
+ end
138
+ end
139
+
140
+ def self.using_all(&block)
141
+ conn = ActiveRecord::Base.connection
142
+
143
+ if conn.is_a?(Octopus::Proxy)
144
+ conn.send_queries_to_all_shards(&block)
145
+ else
146
+ yield
147
+ end
148
+ end
149
+
150
+ def self.fully_replicated(&_block)
151
+ old_fully_replicated = Thread.current[Octopus::Proxy::FULLY_REPLICATED_KEY]
152
+ Thread.current[Octopus::Proxy::FULLY_REPLICATED_KEY] = true
153
+ yield
154
+ ensure
155
+ Thread.current[Octopus::Proxy::FULLY_REPLICATED_KEY] = old_fully_replicated
156
+ end
157
+ end
158
+
159
+ require 'octopus/exception'
160
+
161
+ require 'octopus/shard_tracking'
162
+ require 'octopus/shard_tracking/attribute'
163
+ require 'octopus/shard_tracking/dynamic'
164
+
165
+ require 'octopus/model'
166
+ require 'octopus/migration'
167
+ require 'octopus/association'
168
+ require 'octopus/collection_association'
169
+ require 'octopus/has_and_belongs_to_many_association' unless Octopus.rails41?
170
+ require 'octopus/association_shard_tracking'
171
+ require 'octopus/persistence'
172
+ require 'octopus/log_subscriber'
173
+ require 'octopus/abstract_adapter'
174
+ require 'octopus/singular_association'
175
+
176
+ require 'octopus/railtie' if defined?(::Rails::Railtie)
177
+
178
+ require 'octopus/proxy'
179
+ require 'octopus/collection_proxy'
180
+ require 'octopus/relation_proxy'
181
+ require 'octopus/scope_proxy'
@@ -0,0 +1,35 @@
1
+ # Implementation courtesy of db-charmer.
2
+ module Octopus
3
+ module AbstractAdapter
4
+ module OctopusShard
5
+ parent = Octopus.rails3? ? ActiveSupport::BasicObject : ActiveSupport::ProxyObject
6
+
7
+ class InstrumenterDecorator < parent
8
+ def initialize(adapter, instrumenter)
9
+ @adapter = adapter
10
+ @instrumenter = instrumenter
11
+ end
12
+
13
+ def instrument(name, payload = {}, &block)
14
+ payload[:octopus_shard] ||= @adapter.octopus_shard
15
+ @instrumenter.instrument(name, payload, &block)
16
+ end
17
+
18
+ def method_missing(meth, *args, &block)
19
+ @instrumenter.send(meth, *args, &block)
20
+ end
21
+ end
22
+
23
+ def octopus_shard
24
+ @config[:octopus_shard]
25
+ end
26
+
27
+ def initialize(*args)
28
+ super
29
+ @instrumenter = InstrumenterDecorator.new(self, @instrumenter)
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ ActiveRecord::ConnectionAdapters::AbstractAdapter.send(:prepend, Octopus::AbstractAdapter::OctopusShard)
@@ -0,0 +1,13 @@
1
+ module Octopus
2
+ module Association
3
+ def self.included(base)
4
+ base.send(:include, Octopus::ShardTracking::Dynamic)
5
+ end
6
+
7
+ def current_shard
8
+ owner.current_shard
9
+ end
10
+ end
11
+ end
12
+
13
+ ActiveRecord::Associations::Association.send(:include, Octopus::Association)
@@ -0,0 +1,114 @@
1
+ module Octopus
2
+ module AssociationShardTracking
3
+ def self.extended(base)
4
+ base.send(:include, InstanceMethods)
5
+ end
6
+
7
+ module QueryOnCurrentShard
8
+ METHODS = %w(
9
+ all
10
+ average
11
+ count
12
+ empty?
13
+ exists?
14
+ find
15
+ find_by_sql
16
+ first
17
+ last
18
+ maximum
19
+ minimum
20
+ pluck
21
+ scoping
22
+ size
23
+ sum
24
+ to_a
25
+ )
26
+
27
+ METHODS.each do |m|
28
+ if Octopus.rails4?
29
+ define_method m.to_sym do |*args, &block|
30
+ if defined?(@association) && @association
31
+ @association.owner.run_on_shard { super(*args, &block) }
32
+ else
33
+ super(*args, &block)
34
+ end
35
+ end
36
+ else
37
+ define_method m.to_sym do |*args, &block|
38
+ if respond_to?(:proxy_association) && proxy_association
39
+ proxy_association.owner.run_on_shard { super(*args, &block) }
40
+ else
41
+ super(*args, &block)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ module InstanceMethods
49
+ def connection_on_association=(record)
50
+ return unless ::Octopus.enabled?
51
+ return if !self.class.connection.respond_to?(:current_shard) || !self.respond_to?(:current_shard)
52
+ if !record.current_shard.nil? && !current_shard.nil? && record.current_shard != current_shard
53
+ fail 'Association Error: Records are from different shards'
54
+ end
55
+
56
+ record.current_shard = self.class.connection.current_shard = current_shard if should_set_current_shard?
57
+ end
58
+ end
59
+
60
+ if Octopus.rails4?
61
+ def has_many(association_id, scope = nil, options = {}, &extension)
62
+ if options == {} && scope.is_a?(Hash)
63
+ default_octopus_opts(scope)
64
+ else
65
+ default_octopus_opts(options)
66
+ end
67
+ super
68
+ end
69
+ else
70
+ def has_many(association_id, options = {}, &extension)
71
+ default_octopus_opts(options)
72
+ super
73
+ end
74
+ end
75
+
76
+ if Octopus.rails4?
77
+ def has_and_belongs_to_many(association_id, scope = nil, options = {}, &extension)
78
+ if options == {} && scope.is_a?(Hash)
79
+ default_octopus_opts(scope)
80
+ else
81
+ default_octopus_opts(options)
82
+ end
83
+ super
84
+ end
85
+ else
86
+ def has_and_belongs_to_many(association_id, options = {}, &extension)
87
+ default_octopus_opts(options)
88
+ super
89
+ end
90
+ end
91
+
92
+ def default_octopus_opts(options)
93
+ if options[:before_add].is_a?(Array)
94
+ options[:before_add] << :connection_on_association=
95
+ elsif options[:before_add].is_a?(Symbol)
96
+ options[:before_add] = [:connection_on_association=, options[:before_add]]
97
+ else
98
+ options[:before_add] = :connection_on_association=
99
+ end
100
+
101
+ if options[:before_remove].is_a?(Array)
102
+ options[:before_remove] << :connection_on_association=
103
+ elsif options[:before_remove].is_a?(Symbol)
104
+ options[:before_remove] = [:connection_on_association=, options[:before_remove]]
105
+ else
106
+ options[:before_remove] = :connection_on_association=
107
+ end
108
+
109
+ options[:extend] = [Octopus::AssociationShardTracking::QueryOnCurrentShard, options[:extend]].flatten.compact
110
+ end
111
+ end
112
+ end
113
+
114
+ ActiveRecord::Base.extend(Octopus::AssociationShardTracking)
@@ -0,0 +1,11 @@
1
+ module Octopus
2
+ module CollectionAssociation
3
+ def self.included(base)
4
+ base.sharded_methods :reader, :writer, :ids_reader, :ids_writer, :create, :create!,
5
+ :build, :any?, :count, :empty?, :first, :include?, :last, :length,
6
+ :load_target, :many?, :reload, :size, :select, :uniq
7
+ end
8
+ end
9
+ end
10
+
11
+ ActiveRecord::Associations::CollectionAssociation.send(:include, Octopus::CollectionAssociation)