ar-octopus 0.8.5 → 0.10.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.travis.yml +6 -9
  4. data/Appraisals +8 -8
  5. data/README.mkdn +47 -15
  6. data/Rakefile +1 -0
  7. data/ar-octopus.gemspec +9 -12
  8. data/gemfiles/rails42.gemfile +2 -2
  9. data/gemfiles/{rails32.gemfile → rails5.gemfile} +2 -2
  10. data/gemfiles/{rails4.gemfile → rails51.gemfile} +2 -2
  11. data/gemfiles/{rails41.gemfile → rails52.gemfile} +2 -2
  12. data/lib/octopus/abstract_adapter.rb +4 -10
  13. data/lib/octopus/association.rb +1 -0
  14. data/lib/octopus/association_shard_tracking.rb +41 -71
  15. data/lib/octopus/collection_association.rb +9 -3
  16. data/lib/octopus/exception.rb +4 -0
  17. data/lib/octopus/finder_methods.rb +8 -0
  18. data/lib/octopus/load_balancing/round_robin.rb +2 -1
  19. data/lib/octopus/log_subscriber.rb +6 -2
  20. data/lib/octopus/migration.rb +123 -55
  21. data/lib/octopus/model.rb +42 -27
  22. data/lib/octopus/persistence.rb +33 -27
  23. data/lib/octopus/proxy.rb +147 -272
  24. data/lib/octopus/proxy_config.rb +251 -0
  25. data/lib/octopus/query_cache_for_shards.rb +24 -0
  26. data/lib/octopus/railtie.rb +0 -2
  27. data/lib/octopus/relation_proxy.rb +36 -1
  28. data/lib/octopus/result_patch.rb +19 -0
  29. data/lib/octopus/scope_proxy.rb +12 -5
  30. data/lib/octopus/shard_tracking.rb +8 -3
  31. data/lib/octopus/slave_group.rb +3 -3
  32. data/lib/octopus/version.rb +1 -1
  33. data/lib/octopus.rb +71 -18
  34. data/spec/config/shards.yml +12 -0
  35. data/spec/migrations/10_create_users_using_replication.rb +1 -1
  36. data/spec/migrations/11_add_field_in_all_slaves.rb +1 -1
  37. data/spec/migrations/12_create_users_using_block.rb +1 -1
  38. data/spec/migrations/13_create_users_using_block_and_using.rb +1 -1
  39. data/spec/migrations/14_create_users_on_shards_of_a_group_with_versions.rb +1 -1
  40. data/spec/migrations/15_create_user_on_shards_of_default_group_with_versions.rb +1 -1
  41. data/spec/migrations/1_create_users_on_master.rb +1 -1
  42. data/spec/migrations/2_create_users_on_canada.rb +1 -1
  43. data/spec/migrations/3_create_users_on_both_shards.rb +1 -1
  44. data/spec/migrations/4_create_users_on_shards_of_a_group.rb +1 -1
  45. data/spec/migrations/5_create_users_on_multiples_groups.rb +1 -1
  46. data/spec/migrations/6_raise_exception_with_invalid_shard_name.rb +1 -1
  47. data/spec/migrations/7_raise_exception_with_invalid_multiple_shard_names.rb +1 -1
  48. data/spec/migrations/8_raise_exception_with_invalid_group_name.rb +1 -1
  49. data/spec/migrations/9_raise_exception_with_multiple_invalid_group_names.rb +1 -1
  50. data/spec/octopus/association_shard_tracking_spec.rb +344 -16
  51. data/spec/octopus/load_balancing/round_robin_spec.rb +15 -0
  52. data/spec/octopus/log_subscriber_spec.rb +1 -1
  53. data/spec/octopus/migration_spec.rb +45 -11
  54. data/spec/octopus/model_spec.rb +204 -16
  55. data/spec/octopus/octopus_spec.rb +2 -2
  56. data/spec/octopus/proxy_spec.rb +44 -40
  57. data/spec/octopus/query_cache_for_shards_spec.rb +40 -0
  58. data/spec/octopus/relation_proxy_spec.rb +71 -23
  59. data/spec/octopus/replicated_slave_grouped_spec.rb +27 -0
  60. data/spec/octopus/replication_spec.rb +72 -2
  61. data/spec/octopus/scope_proxy_spec.rb +41 -7
  62. data/spec/spec_helper.rb +2 -0
  63. data/spec/support/database_connection.rb +1 -1
  64. data/spec/support/database_models.rb +1 -1
  65. data/spec/support/octopus_helper.rb +14 -6
  66. data/spec/tasks/octopus.rake_spec.rb +1 -1
  67. metadata +40 -30
  68. data/.ruby-version +0 -1
  69. data/init.rb +0 -1
  70. data/lib/octopus/has_and_belongs_to_many_association.rb +0 -9
  71. data/rails/init.rb +0 -1
@@ -0,0 +1,251 @@
1
+ module Octopus
2
+ class ProxyConfig
3
+ CURRENT_MODEL_KEY = 'octopus.current_model'.freeze
4
+ CURRENT_SHARD_KEY = 'octopus.current_shard'.freeze
5
+ CURRENT_GROUP_KEY = 'octopus.current_group'.freeze
6
+ CURRENT_SLAVE_GROUP_KEY = 'octopus.current_slave_group'.freeze
7
+ CURRENT_LOAD_BALANCE_OPTIONS_KEY = 'octopus.current_load_balance_options'.freeze
8
+ BLOCK_KEY = 'octopus.block'.freeze
9
+ FULLY_REPLICATED_KEY = 'octopus.fully_replicated'.freeze
10
+
11
+ attr_accessor :config, :sharded, :shards, :shards_slave_groups, :slave_groups,
12
+ :adapters, :replicated, :slaves_load_balancer, :slaves_list, :shards_slave_groups,
13
+ :slave_groups, :groups, :entire_sharded, :shards_config
14
+
15
+ def initialize(config)
16
+ initialize_shards(config)
17
+ initialize_replication(config) if !config.nil? && config['replicated']
18
+ end
19
+
20
+ def current_model
21
+ Thread.current[CURRENT_MODEL_KEY]
22
+ end
23
+
24
+ def current_model=(model)
25
+ Thread.current[CURRENT_MODEL_KEY] = model.is_a?(ActiveRecord::Base) ? model.class : model
26
+ end
27
+
28
+ def current_shard
29
+ Thread.current[CURRENT_SHARD_KEY] ||= Octopus.master_shard
30
+ end
31
+
32
+ def current_shard=(shard_symbol)
33
+ if shard_symbol.is_a?(Array)
34
+ self.current_slave_group = nil
35
+ shard_symbol.each { |symbol| fail "Nonexistent Shard Name: #{symbol}" if shards[symbol].nil? }
36
+ elsif shard_symbol.is_a?(Hash)
37
+ hash = shard_symbol
38
+ shard_symbol = hash[:shard]
39
+ slave_group_symbol = hash[:slave_group]
40
+ load_balance_options = hash[:load_balance_options]
41
+
42
+ if shard_symbol.nil? && slave_group_symbol.nil?
43
+ fail 'Neither shard or slave group must be specified'
44
+ end
45
+
46
+ if shard_symbol.present?
47
+ fail "Nonexistent Shard Name: #{shard_symbol}" if shards[shard_symbol].nil?
48
+ end
49
+
50
+ if slave_group_symbol.present?
51
+ if (shards_slave_groups.try(:[], shard_symbol).present? && shards_slave_groups[shard_symbol][slave_group_symbol].nil?) ||
52
+ (shards_slave_groups.try(:[], shard_symbol).nil? && @slave_groups[slave_group_symbol].nil?)
53
+ fail "Nonexistent Slave Group Name: #{slave_group_symbol} in shards config: #{shards_config.inspect}"
54
+ end
55
+ end
56
+ self.current_slave_group = slave_group_symbol
57
+ self.current_load_balance_options = load_balance_options
58
+ else
59
+ fail "Nonexistent Shard Name: #{shard_symbol}" if shards[shard_symbol].nil?
60
+ end
61
+
62
+ Thread.current[CURRENT_SHARD_KEY] = shard_symbol
63
+ end
64
+
65
+ def current_group
66
+ Thread.current[CURRENT_GROUP_KEY]
67
+ end
68
+
69
+ def current_group=(group_symbol)
70
+ # TODO: Error message should include all groups if given more than one bad name.
71
+ [group_symbol].flatten.compact.each do |group|
72
+ fail "Nonexistent Group Name: #{group}" unless has_group?(group)
73
+ end
74
+
75
+ Thread.current[CURRENT_GROUP_KEY] = group_symbol
76
+ end
77
+
78
+ def current_slave_group
79
+ Thread.current[CURRENT_SLAVE_GROUP_KEY]
80
+ end
81
+
82
+ def current_slave_group=(slave_group_symbol)
83
+ Thread.current[CURRENT_SLAVE_GROUP_KEY] = slave_group_symbol
84
+ Thread.current[CURRENT_LOAD_BALANCE_OPTIONS_KEY] = nil if slave_group_symbol.nil?
85
+ end
86
+
87
+ def current_load_balance_options
88
+ Thread.current[CURRENT_LOAD_BALANCE_OPTIONS_KEY]
89
+ end
90
+
91
+ def current_load_balance_options=(options)
92
+ Thread.current[CURRENT_LOAD_BALANCE_OPTIONS_KEY] = options
93
+ end
94
+
95
+ def block
96
+ Thread.current[BLOCK_KEY]
97
+ end
98
+
99
+ def block=(block)
100
+ Thread.current[BLOCK_KEY] = block
101
+ end
102
+
103
+ def fully_replicated?
104
+ @fully_replicated || Thread.current[FULLY_REPLICATED_KEY]
105
+ end
106
+
107
+ # Public: Whether or not a group exists with the given name converted to a
108
+ # string.
109
+ #
110
+ # Returns a boolean.
111
+ def has_group?(group)
112
+ @groups.key?(group.to_s)
113
+ end
114
+
115
+ # Public: Retrieves names of all loaded shards.
116
+ #
117
+ # Returns an array of shard names as symbols
118
+ def shard_names
119
+ shards.keys
120
+ end
121
+
122
+ def shard_name
123
+ current_shard.is_a?(Array) ? current_shard.first : current_shard
124
+ end
125
+
126
+ # Public: Retrieves the defined shards for a given group.
127
+ #
128
+ # Returns an array of shard names as symbols or nil if the group is not
129
+ # defined.
130
+ def shards_for_group(group)
131
+ @groups.fetch(group.to_s, nil)
132
+ end
133
+
134
+ def initialize_shards(config)
135
+ @original_config = config
136
+
137
+ self.shards = HashWithIndifferentAccess.new
138
+ self.shards_slave_groups = HashWithIndifferentAccess.new
139
+ self.slave_groups = HashWithIndifferentAccess.new
140
+ self.groups = {}
141
+ self.config = ActiveRecord::Base.connection_pool_without_octopus.spec.config
142
+
143
+ unless config.nil?
144
+ self.entire_sharded = config['entire_sharded']
145
+ self.shards_config = config[Octopus.rails_env]
146
+ end
147
+
148
+ self.shards_config ||= []
149
+
150
+ shards_config.each do |key, value|
151
+ if value.is_a?(String)
152
+ value = resolve_string_connection(value).merge(:octopus_shard => key)
153
+ initialize_adapter(value['adapter'])
154
+ shards[key.to_sym] = connection_pool_for(value, "#{value['adapter']}_connection")
155
+ elsif value.is_a?(Hash) && value.key?('adapter')
156
+ value.merge!(:octopus_shard => key)
157
+ initialize_adapter(value['adapter'])
158
+ shards[key.to_sym] = connection_pool_for(value, "#{value['adapter']}_connection")
159
+
160
+ slave_group_configs = value.select do |_k, v|
161
+ structurally_slave_group? v
162
+ end
163
+
164
+ if slave_group_configs.present?
165
+ slave_groups = HashWithIndifferentAccess.new
166
+ slave_group_configs.each do |slave_group_name, slave_configs|
167
+ slaves = HashWithIndifferentAccess.new
168
+ slave_configs.each do |slave_name, slave_config|
169
+ shards[slave_name.to_sym] = connection_pool_for(slave_config, "#{value['adapter']}_connection")
170
+ slaves[slave_name.to_sym] = slave_name.to_sym
171
+ end
172
+ slave_groups[slave_group_name.to_sym] = Octopus::SlaveGroup.new(slaves)
173
+ end
174
+ @shards_slave_groups[key.to_sym] = slave_groups
175
+ @sharded = true
176
+ end
177
+ elsif value.is_a?(Hash)
178
+ @groups[key.to_s] = []
179
+
180
+ value.each do |k, v|
181
+ fail 'You have duplicated shard names!' if shards.key?(k.to_sym)
182
+
183
+ initialize_adapter(v['adapter'])
184
+ config_with_octopus_shard = v.merge(:octopus_shard => k)
185
+
186
+ shards[k.to_sym] = connection_pool_for(config_with_octopus_shard, "#{v['adapter']}_connection")
187
+ @groups[key.to_s] << k.to_sym
188
+ end
189
+
190
+ if structurally_slave_group? value
191
+ slaves = Hash[@groups[key.to_s].map { |v| [v, v] }]
192
+ @slave_groups[key.to_sym] = Octopus::SlaveGroup.new(slaves)
193
+ end
194
+ end
195
+ end
196
+
197
+ shards[:master] ||= ActiveRecord::Base.connection_pool_without_octopus if Octopus.master_shard == :master
198
+ end
199
+
200
+ def initialize_replication(config)
201
+ @replicated = true
202
+ if config.key?('fully_replicated')
203
+ @fully_replicated = config['fully_replicated']
204
+ else
205
+ @fully_replicated = true
206
+ end
207
+
208
+ @slaves_list = shards.keys.map(&:to_s).sort
209
+ @slaves_list.delete('master')
210
+ @slaves_load_balancer = Octopus.load_balancer.new(@slaves_list)
211
+ end
212
+
213
+ def reinitialize_shards
214
+ initialize_shards(@original_config)
215
+ end
216
+
217
+ private
218
+
219
+ def connection_pool_for(config, adapter)
220
+ if Octopus.rails4?
221
+ spec = ActiveRecord::ConnectionAdapters::ConnectionSpecification.new(config.dup, adapter )
222
+ else
223
+ name = adapter["octopus_shard"]
224
+ spec = ActiveRecord::ConnectionAdapters::ConnectionSpecification.new(name, config.dup, adapter)
225
+ end
226
+
227
+ ActiveRecord::ConnectionAdapters::ConnectionPool.new(spec)
228
+ end
229
+
230
+ def resolve_string_connection(spec)
231
+ resolver = ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.new({})
232
+ HashWithIndifferentAccess.new(resolver.spec(spec).config)
233
+ end
234
+
235
+ def structurally_slave?(config)
236
+ config.is_a?(Hash) && config.key?('adapter')
237
+ end
238
+
239
+ def structurally_slave_group?(config)
240
+ config.is_a?(Hash) && config.values.any? { |v| structurally_slave? v }
241
+ end
242
+
243
+ def initialize_adapter(adapter)
244
+ begin
245
+ require "active_record/connection_adapters/#{adapter}_adapter"
246
+ rescue LoadError
247
+ raise "Please install the #{adapter} adapter: `gem install activerecord-#{adapter}-adapter` (#{$ERROR_INFO})"
248
+ end
249
+ end
250
+ end
251
+ end
@@ -0,0 +1,24 @@
1
+ # query cache methods are moved to ConnectionPool for Rails >= 5.0
2
+ module Octopus
3
+ module ConnectionPool
4
+ module QueryCacheForShards
5
+ %i(enable_query_cache! disable_query_cache!).each do |method|
6
+ define_method(method) do
7
+ if(Octopus.enabled? && (shards = ActiveRecord::Base.connection.shards)['master'] == self)
8
+ shards.each do |shard_name, v|
9
+ if shard_name == 'master'
10
+ super()
11
+ else
12
+ v.public_send(method)
13
+ end
14
+ end
15
+ else
16
+ super()
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ ActiveRecord::ConnectionAdapters::ConnectionPool.send(:prepend, Octopus::ConnectionPool::QueryCacheForShards)
@@ -1,6 +1,4 @@
1
1
  begin
2
- require 'rails/railtie'
3
-
4
2
  module Octopus
5
3
  class Railtie < Rails::Railtie
6
4
  rake_tasks do
@@ -16,8 +16,43 @@ module Octopus
16
16
  @ar_relation = ar_relation
17
17
  end
18
18
 
19
+ def respond_to?(*args)
20
+ method_missing(:respond_to?, *args)
21
+ end
22
+
23
+ # methods redefined in ActiveRecord that should run_on_shard
24
+ ENUM_METHODS = (::Enumerable.instance_methods - ::Object.instance_methods).reject do |m|
25
+ ::ActiveRecord::Relation.instance_method(m).source_location rescue nil
26
+ end + [:each, :map, :index_by]
27
+ # `find { ... }` etc. should run_on_shard, `find(id)` should be sent to relation
28
+ ENUM_WITH_BLOCK_METHODS = [:find, :select, :none?, :any?, :one?, :many?, :sum]
29
+ BATCH_METHODS = [:find_each, :find_in_batches, :in_batches]
30
+ WHERE_CHAIN_METHODS = [:not]
31
+
19
32
  def method_missing(method, *args, &block)
20
- run_on_shard { @ar_relation.public_send(method, *args, &block) }
33
+ if !block && BATCH_METHODS.include?(method)
34
+ ::Enumerator.new do |yielder|
35
+ run_on_shard do
36
+ @ar_relation.public_send(method, *args) do |batch_item|
37
+ yielder << batch_item
38
+ end
39
+ end
40
+ end
41
+ elsif ENUM_METHODS.include?(method) || block && ENUM_WITH_BLOCK_METHODS.include?(method)
42
+ run_on_shard { @ar_relation.to_a }.public_send(method, *args, &block)
43
+ elsif WHERE_CHAIN_METHODS.include?(method)
44
+ ::Octopus::ScopeProxy.new(@current_shard, run_on_shard { @ar_relation.public_send(method, *args) } )
45
+ elsif block
46
+ @ar_relation.public_send(method, *args, &block)
47
+ else
48
+ run_on_shard do
49
+ if method == :load_records
50
+ @ar_relation.send(method, *args)
51
+ else
52
+ @ar_relation.public_send(method, *args)
53
+ end
54
+ end
55
+ end
21
56
  end
22
57
 
23
58
  def ==(other)
@@ -0,0 +1,19 @@
1
+ module Octopus::ResultPatch
2
+ attr_accessor :current_shard
3
+
4
+ private
5
+
6
+ def hash_rows
7
+ if current_shard.blank?
8
+ super
9
+ else
10
+ foo = super
11
+ foo.each { |f| f.merge!('current_shard' => current_shard) }
12
+ foo
13
+ end
14
+ end
15
+ end
16
+
17
+ class ActiveRecord::Result
18
+ prepend Octopus::ResultPatch
19
+ end
@@ -11,20 +11,24 @@ module Octopus
11
11
 
12
12
  attr_accessor :klass
13
13
 
14
+ # Dup and clone should be delegated to the class.
15
+ # We want to dup the query, not the scope proxy.
16
+ delegate :dup, :clone, to: :klass
17
+
14
18
  def initialize(shard, klass)
15
19
  @current_shard = shard
16
20
  @klass = klass
17
21
  end
18
22
 
19
23
  def using(shard)
20
- fail "Nonexistent Shard Name: #{shard}" if @klass.connection.instance_variable_get(:@shards)[shard].nil?
24
+ fail "Nonexistent Shard Name: #{shard}" if @klass.connection.shards[shard].nil?
21
25
  @current_shard = shard
22
26
  self
23
27
  end
24
28
 
25
29
  # Transaction Method send all queries to a specified shard.
26
30
  def transaction(options = {}, &block)
27
- run_on_shard { @klass = klass.transaction(options, &block) }
31
+ run_on_shard { klass.transaction(options, &block) }
28
32
  end
29
33
 
30
34
  def connection
@@ -40,10 +44,13 @@ module Octopus
40
44
  end
41
45
 
42
46
  def method_missing(method, *args, &block)
43
- result = run_on_shard { @klass.send(method, *args, &block) }
47
+ result = run_on_shard { @klass.__send__(method, *args, &block) }
44
48
  if result.respond_to?(:all)
45
- @klass = result
46
- return self
49
+ return ::Octopus::ScopeProxy.new(current_shard, result)
50
+ end
51
+
52
+ if result.respond_to?(:current_shard)
53
+ result.current_shard = current_shard
47
54
  end
48
55
 
49
56
  result
@@ -20,7 +20,8 @@ module Octopus
20
20
  define_method with do |*args, &block|
21
21
  run_on_shard { send(without, *args, &block) }
22
22
  end
23
- alias_method_chain name.to_sym, :octopus
23
+ alias_method without.to_sym, name.to_sym
24
+ alias_method name.to_sym, with.to_sym
24
25
  end
25
26
  end
26
27
 
@@ -31,8 +32,12 @@ module Octopus
31
32
  # Use a case statement to avoid any path through ActiveRecord::Delegation's
32
33
  # respond_to? code. We want to avoid the respond_to? code because it can have
33
34
  # the side effect of causing a call to load_target
34
- r = Octopus::RelationProxy.new(cs, r) if ActiveRecord::Relation === r and not Octopus::RelationProxy === r
35
- r
35
+
36
+ if (ActiveRecord::Relation === r || ActiveRecord::QueryMethods::WhereChain === r) && !(Octopus::RelationProxy === r)
37
+ Octopus::RelationProxy.new(cs, r)
38
+ else
39
+ r
40
+ end
36
41
  else
37
42
  yield
38
43
  end
@@ -3,11 +3,11 @@ module Octopus
3
3
  def initialize(slaves)
4
4
  slaves = HashWithIndifferentAccess.new(slaves)
5
5
  slaves_list = slaves.values
6
- @load_balancer = Octopus::LoadBalancing::RoundRobin.new(slaves_list)
6
+ @load_balancer = Octopus.load_balancer.new(slaves_list)
7
7
  end
8
8
 
9
- def next
10
- @load_balancer.next
9
+ def next(options)
10
+ @load_balancer.next options
11
11
  end
12
12
  end
13
13
  end
@@ -1,3 +1,3 @@
1
1
  module Octopus
2
- VERSION = '0.8.5'
2
+ VERSION = '0.10.2'
3
3
  end
data/lib/octopus.rb CHANGED
@@ -11,12 +11,12 @@ module Octopus
11
11
  end
12
12
 
13
13
  def self.rails_env
14
- @rails_env ||= self.rails? ? Rails.env.to_s : 'shards'
14
+ @rails_env ||= defined?(::Rails.env) ? Rails.env.to_s : 'shards'
15
15
  end
16
16
 
17
17
  def self.config
18
18
  @config ||= begin
19
- file_name = Octopus.directory + '/config/shards.yml'
19
+ file_name = File.join(Octopus.directory, 'config/shards.yml').to_s
20
20
 
21
21
  if File.exist?(file_name) || File.symlink?(file_name)
22
22
  config ||= HashWithIndifferentAccess.new(YAML.load(ERB.new(File.read(file_name)).result))[Octopus.env]
@@ -28,13 +28,25 @@ module Octopus
28
28
  end
29
29
  end
30
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
+
31
43
  # Public: Whether or not Octopus is configured and should hook into the
32
44
  # current environment. Checks the environments config option for the Rails
33
45
  # environment by default.
34
46
  #
35
47
  # Returns a boolean
36
48
  def self.enabled?
37
- if defined?(::Rails)
49
+ if defined?(::Rails.env)
38
50
  Octopus.environments.include?(Rails.env.to_s)
39
51
  else
40
52
  # TODO: This doens't feel right but !Octopus.config.blank? is breaking a
@@ -46,7 +58,7 @@ module Octopus
46
58
  # Returns the Rails.root_to_s when you are using rails
47
59
  # Running the current directory in a generic Ruby process
48
60
  def self.directory
49
- @directory ||= defined?(Rails) ? Rails.root.to_s : Dir.pwd
61
+ @directory ||= defined?(::Rails.root) ? Rails.root.to_s : Dir.pwd
50
62
  end
51
63
 
52
64
  # This is the default way to do Octopus Setup
@@ -78,26 +90,42 @@ module Octopus
78
90
  robust_environments.include? rails_env
79
91
  end
80
92
 
81
- def self.rails3?
82
- ActiveRecord::VERSION::MAJOR <= 3
93
+ def self.rails4?
94
+ ActiveRecord::VERSION::MAJOR == 4
83
95
  end
84
96
 
85
- def self.rails4?
86
- ActiveRecord::VERSION::MAJOR >= 4
97
+ def self.rails42?
98
+ rails4? && ActiveRecord::VERSION::MINOR == 2
87
99
  end
88
100
 
89
- def self.rails41?
90
- rails4? && ActiveRecord::VERSION::MINOR >= 1
101
+ def self.rails50?
102
+ ActiveRecord::VERSION::MAJOR == 5 && ActiveRecord::VERSION::MINOR == 0
103
+ end
104
+
105
+ def self.atleast_rails50?
106
+ ActiveRecord::VERSION::MAJOR >= 5
107
+ end
108
+
109
+ def self.rails51?
110
+ ActiveRecord::VERSION::MAJOR == 5 && ActiveRecord::VERSION::MINOR == 1
111
+ end
112
+
113
+ def self.rails52?
114
+ ActiveRecord::VERSION::MAJOR == 5 && ActiveRecord::VERSION::MINOR == 2
115
+ end
116
+
117
+ def self.atleast_rails51?
118
+ ActiveRecord::VERSION::MAJOR > 5 || (ActiveRecord::VERSION::MAJOR == 5 && ActiveRecord::VERSION::MINOR >= 1)
91
119
  end
92
120
 
93
- def self.rails?
94
- defined?(Rails)
121
+ def self.atleast_rails52?
122
+ ActiveRecord::VERSION::MAJOR > 5 || (ActiveRecord::VERSION::MAJOR == 5 && ActiveRecord::VERSION::MINOR > 1)
95
123
  end
96
124
 
97
125
  attr_writer :logger
98
126
 
99
127
  def self.logger
100
- if defined?(Rails)
128
+ if defined?(Rails.logger)
101
129
  @logger ||= Rails.logger
102
130
  else
103
131
  @logger ||= Logger.new($stderr)
@@ -119,32 +147,57 @@ module Octopus
119
147
  end
120
148
  end
121
149
 
150
+ def self.using_group(group, &block)
151
+ conn = ActiveRecord::Base.connection
152
+
153
+ if conn.is_a?(Octopus::Proxy)
154
+ conn.send_queries_to_group(group, &block)
155
+ else
156
+ yield
157
+ end
158
+ end
159
+
160
+ def self.using_all(&block)
161
+ conn = ActiveRecord::Base.connection
162
+
163
+ if conn.is_a?(Octopus::Proxy)
164
+ conn.send_queries_to_all_shards(&block)
165
+ else
166
+ yield
167
+ end
168
+ end
169
+
122
170
  def self.fully_replicated(&_block)
123
- old_fully_replicated = Thread.current['octopus.fully_replicated']
124
- Thread.current['octopus.fully_replicated'] = true
171
+ old_fully_replicated = Thread.current[Octopus::ProxyConfig::FULLY_REPLICATED_KEY]
172
+ Thread.current[Octopus::ProxyConfig::FULLY_REPLICATED_KEY] = true
125
173
  yield
126
174
  ensure
127
- Thread.current['octopus.fully_replicated'] = old_fully_replicated
175
+ Thread.current[Octopus::ProxyConfig::FULLY_REPLICATED_KEY] = old_fully_replicated
128
176
  end
129
177
  end
130
178
 
179
+ require 'octopus/exception'
180
+
131
181
  require 'octopus/shard_tracking'
132
182
  require 'octopus/shard_tracking/attribute'
133
183
  require 'octopus/shard_tracking/dynamic'
134
184
 
135
185
  require 'octopus/model'
186
+ require 'octopus/result_patch'
136
187
  require 'octopus/migration'
137
188
  require 'octopus/association'
138
189
  require 'octopus/collection_association'
139
- require 'octopus/has_and_belongs_to_many_association' unless Octopus.rails41?
140
190
  require 'octopus/association_shard_tracking'
141
191
  require 'octopus/persistence'
142
192
  require 'octopus/log_subscriber'
143
193
  require 'octopus/abstract_adapter'
144
194
  require 'octopus/singular_association'
195
+ require 'octopus/finder_methods'
196
+ require 'octopus/query_cache_for_shards' unless Octopus.rails4?
145
197
 
146
- require 'octopus/railtie' if defined?(::Rails)
198
+ require 'octopus/railtie' if defined?(::Rails::Railtie)
147
199
 
200
+ require 'octopus/proxy_config'
148
201
  require 'octopus/proxy'
149
202
  require 'octopus/collection_proxy'
150
203
  require 'octopus/relation_proxy'
@@ -86,6 +86,18 @@ production_replicated:
86
86
  <<: *mysql
87
87
 
88
88
 
89
+ production_fully_replicated:
90
+ replicated: true
91
+ fully_replicated: true
92
+
93
+ shards:
94
+ slave1:
95
+ database: octopus_shard_2
96
+ <<: *mysql
97
+ slave2:
98
+ database: octopus_shard_3
99
+ <<: *mysql
100
+
89
101
  replicated_with_one_slave:
90
102
  replicated: true
91
103
  shards:
@@ -1,4 +1,4 @@
1
- class CreateUsersUsingReplication < ActiveRecord::Migration
1
+ class CreateUsersUsingReplication < BaseOctopusMigrationClass
2
2
  def self.up
3
3
  Cat.create!(:name => 'Replication')
4
4
  end
@@ -1,4 +1,4 @@
1
- class AddFieldInAllSlaves < ActiveRecord::Migration
1
+ class AddFieldInAllSlaves < BaseOctopusMigrationClass
2
2
  using(:slave1, :slave2, :slave3, :slave4)
3
3
 
4
4
  def self.up
@@ -1,4 +1,4 @@
1
- class CreateUsersUsingBlock < ActiveRecord::Migration
1
+ class CreateUsersUsingBlock < BaseOctopusMigrationClass
2
2
  def self.up
3
3
  Octopus.using(:brazil) do
4
4
  User.create!(:name => 'UsingBlock1')
@@ -1,4 +1,4 @@
1
- class CreateUsersUsingBlockAndUsing < ActiveRecord::Migration
1
+ class CreateUsersUsingBlockAndUsing < BaseOctopusMigrationClass
2
2
  using(:brazil)
3
3
 
4
4
  def self.up
@@ -1,4 +1,4 @@
1
- class CreateUsersOnShardsOfAGroupWithVersions < ActiveRecord::Migration
1
+ class CreateUsersOnShardsOfAGroupWithVersions < BaseOctopusMigrationClass
2
2
  using_group(:country_shards)
3
3
 
4
4
  def self.up
@@ -1,4 +1,4 @@
1
- class CreateUserOnShardsOfDefaultGroupWithVersions < ActiveRecord::Migration
1
+ class CreateUserOnShardsOfDefaultGroupWithVersions < BaseOctopusMigrationClass
2
2
  def self.up
3
3
  User.create!(:name => 'Default Group')
4
4
  end
@@ -1,4 +1,4 @@
1
- class CreateUsersOnMaster < ActiveRecord::Migration
1
+ class CreateUsersOnMaster < BaseOctopusMigrationClass
2
2
  def self.up
3
3
  User.create!(:name => 'Master')
4
4
  end
@@ -1,4 +1,4 @@
1
- class CreateUsersOnCanada < ActiveRecord::Migration
1
+ class CreateUsersOnCanada < BaseOctopusMigrationClass
2
2
  using(:canada)
3
3
 
4
4
  def self.up