ar-octopus 0.9.1 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
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