ar-octopus 0.8.5 → 0.10.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 (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