ar-octopus 0.9.1 → 0.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b11a47088e651be24f08e79859ff992113aafa0d
4
- data.tar.gz: 9a33b695f7736f5ea3fecf905442eb26743a1c1d
3
+ metadata.gz: 5bacaaae3374f747c5158b3cf929bc53bb6b37c0
4
+ data.tar.gz: acef7102fc1a7639b43e3425325a357a682b553d
5
5
  SHA512:
6
- metadata.gz: 773b9c6d05403653ab26fc992daddfd9ac62d17e965ab0e980f8f052c450c8bde69951471993b03d56bac32c1015bcc42e7395876b91cc2df30a9a3892199778
7
- data.tar.gz: f185670274cafadefa2c14390a32cd2e8dbeae2ca59059b0c07cd4f2f93d215c5eefa0ff29783281cc6ff761bc918f3f984dc6ef19b6eb84554dc9056b5370a1
6
+ metadata.gz: d574b68144683c9ab61ee42e390c579ce41ff4997584eb081277b43df22a2b8ae0c996de227a04cb8832fb2435309b7429d3b630225306a4ff9939f55b5074d5
7
+ data.tar.gz: a24042265c44af4c6fe61c0681eac8f4848d565bd2b7a454942a4ed62497249abc0fd79c2d949ea28ea8daa01c5b534996e50a051b009246a8f95e0a91a0d315
@@ -28,7 +28,7 @@ Gem::Specification.new do |s|
28
28
 
29
29
  s.add_development_dependency 'appraisal', '>= 0.3.8'
30
30
  s.add_development_dependency 'mysql2', '~> 0.3.18'
31
- s.add_development_dependency 'pg', '>= 0.11.0'
31
+ s.add_development_dependency 'pg', '~> 0.18'
32
32
  s.add_development_dependency 'rake'
33
33
  s.add_development_dependency 'rspec', '>= 3'
34
34
  s.add_development_dependency 'rubocop'
@@ -94,6 +94,14 @@ module Octopus
94
94
  ActiveRecord::VERSION::MAJOR == 4
95
95
  end
96
96
 
97
+ def self.rails40?
98
+ rails4? && ActiveRecord::VERSION::MINOR == 0
99
+ end
100
+
101
+ def self.rails41_only?
102
+ rails4? && ActiveRecord::VERSION::MINOR == 1
103
+ end
104
+
97
105
  def self.rails41?
98
106
  rails4? && ActiveRecord::VERSION::MINOR >= 1
99
107
  end
@@ -110,6 +118,10 @@ module Octopus
110
118
  ActiveRecord::VERSION::MAJOR == 5 && ActiveRecord::VERSION::MINOR == 1
111
119
  end
112
120
 
121
+ def self.atleast_rails51?
122
+ ActiveRecord::VERSION::MAJOR > 5 || (ActiveRecord::VERSION::MAJOR == 5 && ActiveRecord::VERSION::MINOR >= 1)
123
+ end
124
+
113
125
  attr_writer :logger
114
126
 
115
127
  def self.logger
@@ -171,6 +183,7 @@ require 'octopus/shard_tracking/attribute'
171
183
  require 'octopus/shard_tracking/dynamic'
172
184
 
173
185
  require 'octopus/model'
186
+ require 'octopus/result_patch'
174
187
  require 'octopus/migration'
175
188
  require 'octopus/association'
176
189
  require 'octopus/collection_association'
@@ -27,7 +27,7 @@ module Octopus
27
27
  return unless ::Octopus.enabled?
28
28
  return if !self.class.connection.respond_to?(:current_shard) || !self.respond_to?(:current_shard)
29
29
 
30
- if !record.current_shard.nil? && !current_shard.nil? && record.current_shard != current_shard
30
+ if !record.current_shard.nil? && !current_shard.nil? && record.current_shard.to_s != current_shard.to_s
31
31
  raise MismatchedShards.new(record, current_shard)
32
32
  end
33
33
 
@@ -45,17 +45,28 @@ module Octopus
45
45
  end
46
46
 
47
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)
48
58
  if options == {} && scope.is_a?(Hash)
49
59
  default_octopus_opts(scope)
50
60
  else
51
61
  default_octopus_opts(options)
52
62
  end
53
- super
54
63
  end
55
64
 
56
- def default_octopus_opts(options)
57
- options[:before_add] = [ :connection_on_association=, options[:before_add] ].compact.flatten
58
- options[:before_remove] = [ :connection_on_association=, options[:before_remove] ].compact.flatten
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
59
70
  end
60
71
  end
61
72
  end
@@ -77,6 +77,9 @@ module Octopus
77
77
 
78
78
  alias_method :run_without_octopus, :run
79
79
  alias_method :run, :run_with_octopus
80
+
81
+ alias_method :rollback_without_octopus, :rollback
82
+ alias_method :rollback, :rollback_with_octopus
80
83
  end
81
84
  end
82
85
 
@@ -145,6 +148,14 @@ module Octopus
145
148
  end
146
149
  end
147
150
 
151
+ def rollback_with_octopus(migrations_paths, steps = 1)
152
+ return rollback_without_octopus(migrations_paths, steps) unless connection.is_a?(Octopus::Proxy)
153
+
154
+ connection.send_queries_to_multiple_shards(connection.shard_names) do
155
+ rollback_without_octopus(migrations_paths, steps)
156
+ end
157
+ end
158
+
148
159
  private
149
160
 
150
161
  def connection
@@ -43,6 +43,25 @@ If you are trying to scope everything to a specific shard, use Octopus.using ins
43
43
  self.current_shard = shard if self.class.allowed_shard?(shard)
44
44
  end
45
45
 
46
+ def init_with(coder)
47
+ current_shard_value = if Octopus.rails40? || Octopus.rails41_only?
48
+ coder['attributes']['current_shard'] if coder['attributes']['current_shard'].present?
49
+ else
50
+ coder['attributes']['current_shard'].value if coder['attributes']['current_shard'].present? && coder['attributes']['current_shard'].value.present?
51
+ end
52
+
53
+ if Octopus.rails40? || Octopus.rails41_only?
54
+ coder['attributes'].delete('current_shard')
55
+ else
56
+ coder['attributes'].send(:attributes).send(:values).delete('current_shard')
57
+ coder['attributes'].send(:attributes).send(:delegate_hash).delete('current_shard')
58
+ end
59
+
60
+ obj = super
61
+ obj.current_shard = current_shard_value if current_shard_value.present?
62
+ obj
63
+ end
64
+
46
65
  def should_set_current_shard?
47
66
  self.respond_to?(:current_shard) && !current_shard.nil?
48
67
  end
@@ -86,9 +105,10 @@ If you are trying to scope everything to a specific shard, use Octopus.using ins
86
105
  end
87
106
 
88
107
  def hijack_methods
89
- around_save :run_on_shard, :unless => lambda { self.class.custom_octopus_connection }
90
108
  after_initialize :set_current_shard
91
109
 
110
+ around_save :run_on_shard, :unless => lambda { self.class.custom_octopus_connection }
111
+
92
112
  class_attribute :custom_octopus_connection
93
113
 
94
114
  class << self
@@ -132,7 +152,7 @@ If you are trying to scope everything to a specific shard, use Octopus.using ins
132
152
 
133
153
  def allowed_shard?(shard)
134
154
  if custom_octopus_connection
135
- allowed_shards && shard && allowed_shards.include?(shard)
155
+ allowed_shards && shard && (allowed_shards.include?(shard.to_s) || allowed_shards.include?(shard.to_sym))
136
156
  else
137
157
  true
138
158
  end
@@ -35,19 +35,20 @@ module Octopus
35
35
 
36
36
  def execute(sql, name = nil)
37
37
  conn = select_connection
38
- clean_connection_proxy
38
+ clean_connection_proxy if should_clean_connection_proxy?('execute')
39
39
  conn.execute(sql, name)
40
40
  end
41
41
 
42
42
  def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
43
43
  conn = select_connection
44
- clean_connection_proxy
44
+ clean_connection_proxy if should_clean_connection_proxy?('insert')
45
45
  conn.insert(arel, name, pk, id_value, sequence_name, binds)
46
46
  end
47
47
 
48
48
  def update(arel, name = nil, binds = [])
49
49
  conn = select_connection
50
- clean_connection_proxy
50
+ # Call the legacy should_clean_connection_proxy? method here, emulating an insert.
51
+ clean_connection_proxy if should_clean_connection_proxy?('insert')
51
52
  conn.update(arel, name, binds)
52
53
  end
53
54
 
@@ -196,7 +197,13 @@ module Octopus
196
197
  elsif should_send_queries_to_replicated_databases?(method)
197
198
  send_queries_to_selected_slave(method, *args, &block)
198
199
  else
199
- select_connection.send(method, *args, &block)
200
+ val = select_connection.send(method, *args, &block)
201
+
202
+ if val.instance_of? ActiveRecord::Result
203
+ val.current_shard = shard_name
204
+ end
205
+
206
+ val
200
207
  end
201
208
  end
202
209
 
@@ -281,7 +288,11 @@ module Octopus
281
288
  # while preserving `current_shard`
282
289
  def send_queries_to_slave(slave, method, *args, &block)
283
290
  using_shard(slave) do
284
- select_connection.send(method, *args, &block)
291
+ val = select_connection.send(method, *args, &block)
292
+ if val.instance_of? ActiveRecord::Result
293
+ val.current_shard = slave
294
+ end
295
+ val
285
296
  end
286
297
  end
287
298
 
@@ -212,15 +212,15 @@ module Octopus
212
212
 
213
213
  private
214
214
 
215
- def connection_pool_for(adapter, config)
215
+ def connection_pool_for(config, adapter)
216
216
  if Octopus.rails4?
217
- arg = ActiveRecord::ConnectionAdapters::ConnectionSpecification.new(adapter.dup, config)
217
+ spec = ActiveRecord::ConnectionAdapters::ConnectionSpecification.new(config.dup, adapter )
218
218
  else
219
219
  name = adapter["octopus_shard"]
220
- arg = ActiveRecord::ConnectionAdapters::ConnectionSpecification.new(name, adapter.dup, config)
220
+ spec = ActiveRecord::ConnectionAdapters::ConnectionSpecification.new(name, config.dup, adapter)
221
221
  end
222
222
 
223
- ActiveRecord::ConnectionAdapters::ConnectionPool.new(arg)
223
+ ActiveRecord::ConnectionAdapters::ConnectionPool.new(spec)
224
224
  end
225
225
 
226
226
  def resolve_string_connection(spec)
@@ -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
@@ -46,8 +46,11 @@ module Octopus
46
46
  def method_missing(method, *args, &block)
47
47
  result = run_on_shard { @klass.send(method, *args, &block) }
48
48
  if result.respond_to?(:all)
49
- @klass = result
50
- 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
51
54
  end
52
55
 
53
56
  result
@@ -1,3 +1,3 @@
1
1
  module Octopus
2
- VERSION = '0.9.1'
2
+ VERSION = '0.9.2'
3
3
  end
@@ -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:
@@ -404,6 +404,12 @@ describe Octopus::AssociationShardTracking, :shards => [:brazil, :master, :canad
404
404
  end.to raise_error(Octopus::AssociationShardTracking::MismatchedShards)
405
405
  end
406
406
 
407
+ it 'should make difference if the shard is a symbol or a string for raising Octopus::AssociationShardTracking::MismatchedShards' do
408
+ expect do
409
+ @brazil_client.items << Item.using('brazil').create!(:name => 'New Brazil Item')
410
+ end.not_to raise_error
411
+ end
412
+
407
413
  it 'should update the attribute for the item' do
408
414
  new_brazil_client = Client.using(:brazil).create!(:name => 'new Client')
409
415
  @item_brazil.client = new_brazil_client
@@ -30,6 +30,25 @@ describe Octopus::Migration do
30
30
  end
31
31
  end
32
32
 
33
+ it "should rollback correctly migrations" do
34
+ migrations_root = File.expand_path(File.join(File.dirname(__FILE__), '..', 'migrations'))
35
+
36
+ ActiveRecord::Migrator.run(:up, migrations_root, 4)
37
+
38
+ expect(User.using(:canada).find_by_name('Group')).not_to be_nil
39
+ expect(User.using(:brazil).find_by_name('Group')).not_to be_nil
40
+ expect(User.using(:russia).find_by_name('Group')).not_to be_nil
41
+
42
+
43
+ Octopus.using(:canada) do
44
+ ActiveRecord::Migrator.rollback(migrations_root, 4)
45
+ end
46
+
47
+ expect(User.using(:canada).find_by_name('Group')).to be_nil
48
+ expect(User.using(:brazil).find_by_name('Group')).to be_nil
49
+ expect(User.using(:russia).find_by_name('Group')).to be_nil
50
+ end
51
+
33
52
  it 'should run once per shard' do
34
53
  OctopusHelper.migrating_to_version 5 do
35
54
  expect(User.using(:canada).where(:name => 'MultipleGroup').size).to eq(1)
@@ -63,6 +63,28 @@ describe Octopus::Model do
63
63
  expect(User.all).to eq([u1])
64
64
  end
65
65
 
66
+ it "should allow the #select method to fetch the correct data when using a block" do
67
+ canadian_user = User.using(:canada).create!(:name => 'Rafael Pilha')
68
+
69
+ Octopus.using('canada') do
70
+ @all_canadian_user_ids = User.select('id').to_a
71
+ end
72
+
73
+ expect(@all_canadian_user_ids).to eq([canadian_user])
74
+ end
75
+
76
+ it "should allow objects to be fetch using different blocks - GH #306" do
77
+ canadian_user = User.using(:canada).create!(:name => 'Rafael Pilha')
78
+
79
+ Octopus.using(:canada) { @users = User.where('id is not null') }
80
+ Octopus.using(:canada) { @user = @users.first }
81
+
82
+ Octopus.using(:canada) { @user2 = User.where('id is not null').first }
83
+
84
+ expect(@user).to eq(canadian_user)
85
+ expect(@user2).to eq(canadian_user)
86
+ end
87
+
66
88
  describe 'multiple calls to the same scope' do
67
89
  it 'works with nil response' do
68
90
  scope = User.using(:canada)
@@ -717,5 +739,16 @@ describe Octopus::Model do
717
739
  expect(Cat.replicated).to be true
718
740
  end
719
741
  end
742
+
743
+ it "should work on a fully replicated environment" do
744
+ OctopusHelper.using_environment :production_fully_replicated do
745
+ User.using(:slave1).create!(name: 'Thiago')
746
+ User.using(:slave2).create!(name: 'Thiago')
747
+
748
+ replicated_cat = User.find_by_name 'Thiago'
749
+
750
+ expect(replicated_cat.current_shard.to_s).to match(/slave/)
751
+ end
752
+ end
720
753
  end
721
754
  end
@@ -40,7 +40,7 @@ describe Octopus, :shards => [] do
40
40
  expect { User.using(:crazy_shard).create!(:name => 'Joaquim') }.to raise_error(RuntimeError)
41
41
 
42
42
  Octopus.setup do |config|
43
- config.shards = { :crazy_shard => { :adapter => 'mysql2', :database => 'octopus_shard_5', :username => 'root', :password => '' } }
43
+ config.shards = { :crazy_shard => { :adapter => 'mysql2', :database => 'octopus_shard_5', :username => "#{ENV['MYSQL_USER'] || 'root'}", :password => '' } }
44
44
  end
45
45
 
46
46
  expect { User.using(:crazy_shard).create!(:name => 'Joaquim') }.not_to raise_error
@@ -30,7 +30,7 @@ describe Octopus::Proxy do
30
30
  config = proxy.config
31
31
  expect(config[:adapter]).to eq('mysql2')
32
32
  expect(config[:database]).to eq('octopus_shard_1')
33
- expect(config[:username]).to eq('root')
33
+ expect(config[:username]).to eq("#{ENV['MYSQL_USER'] || 'root'}")
34
34
  end
35
35
 
36
36
  unless Octopus.rails50? || Octopus.rails51?
@@ -252,6 +252,22 @@ describe Octopus::Proxy do
252
252
  end
253
253
  end
254
254
 
255
+
256
+ describe 'cleaning the connection proxy' do
257
+ it 'should not clean #current_shard from proxy when using a block and calling #execute' do
258
+ Octopus.using(:canada) do
259
+ expect(User.connection.current_shard).to eq(:canada)
260
+
261
+ connection = User.connection
262
+
263
+ result = connection.execute('select * from users limit 1;')
264
+ result = connection.execute('select * from users limit 1;')
265
+
266
+ expect(User.connection.current_shard).to eq(:canada)
267
+ end
268
+ end
269
+ end
270
+
255
271
  describe 'connection reuse' do
256
272
  before :each do
257
273
  @item_brazil_conn = Item.using(:brazil).new(:name => 'Brazil Item').class.connection.select_connection
@@ -28,6 +28,26 @@ describe Octopus::RelationProxy do
28
28
  expect(not_relation.current_shard).to eq(@relation.current_shard)
29
29
  end
30
30
 
31
+ context 'when a new relation is constructed from the original relation' do
32
+ context 'and a where(...) is used' do
33
+ it 'does not tamper with the original relation' do
34
+ relation = Item.using(:canada).where(id: 1)
35
+ original_sql = relation.to_sql
36
+ new_relation = relation.where(id: 2)
37
+ expect(relation.to_sql).to eq(original_sql)
38
+ end
39
+ end
40
+
41
+ context 'and a where.not(...) is used' do
42
+ it 'does not tamper with the original relation' do
43
+ relation = Item.using(:canada).where(id: 1)
44
+ original_sql = relation.to_sql
45
+ new_relation = relation.where.not(id: 2)
46
+ expect(relation.to_sql).to eq(original_sql)
47
+ end
48
+ end
49
+ end
50
+
31
51
  context 'when comparing to other Relation objects' do
32
52
  before :each do
33
53
  @relation.reset
@@ -1,4 +1,4 @@
1
1
  require 'logger'
2
2
 
3
- ActiveRecord::Base.establish_connection(:adapter => 'mysql2', :database => 'octopus_shard_1', :username => 'root', :password => '')
3
+ ActiveRecord::Base.establish_connection(:adapter => 'mysql2', :database => 'octopus_shard_1', :username => "#{ENV['MYSQL_USER'] || 'root'}", :password => '')
4
4
  ActiveRecord::Base.logger = Logger.new(File.open('database.log', 'a'))
@@ -28,7 +28,7 @@ end
28
28
  # This class sets its own connection
29
29
  class CustomConnection < ActiveRecord::Base
30
30
  self.table_name = 'custom'
31
- octopus_establish_connection(:adapter => 'mysql2', :database => 'octopus_shard_2', :username => 'root', :password => '')
31
+ octopus_establish_connection(:adapter => 'mysql2', :database => 'octopus_shard_2', :username => "#{ENV['MYSQL_USER'] || 'root'}", :password => '')
32
32
  end
33
33
 
34
34
  # This items belongs to a client
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ar-octopus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.1
4
+ version: 0.9.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thiago Pradi
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2017-07-16 00:00:00.000000000 Z
13
+ date: 2018-02-18 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord
@@ -72,16 +72,16 @@ dependencies:
72
72
  name: pg
73
73
  requirement: !ruby/object:Gem::Requirement
74
74
  requirements:
75
- - - ">="
75
+ - - "~>"
76
76
  - !ruby/object:Gem::Version
77
- version: 0.11.0
77
+ version: '0.18'
78
78
  type: :development
79
79
  prerelease: false
80
80
  version_requirements: !ruby/object:Gem::Requirement
81
81
  requirements:
82
- - - ">="
82
+ - - "~>"
83
83
  - !ruby/object:Gem::Version
84
- version: 0.11.0
84
+ version: '0.18'
85
85
  - !ruby/object:Gem::Dependency
86
86
  name: rake
87
87
  requirement: !ruby/object:Gem::Requirement
@@ -199,6 +199,7 @@ files:
199
199
  - lib/octopus/proxy_config.rb
200
200
  - lib/octopus/railtie.rb
201
201
  - lib/octopus/relation_proxy.rb
202
+ - lib/octopus/result_patch.rb
202
203
  - lib/octopus/scope_proxy.rb
203
204
  - lib/octopus/shard_tracking.rb
204
205
  - lib/octopus/shard_tracking/attribute.rb
@@ -344,7 +345,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
344
345
  version: '0'
345
346
  requirements: []
346
347
  rubyforge_project:
347
- rubygems_version: 2.2.2
348
+ rubygems_version: 2.4.5.1
348
349
  signing_key:
349
350
  specification_version: 4
350
351
  summary: Easy Database Sharding for ActiveRecord