switchman 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +8 -8
  2. data/app/models/switchman/shard.rb +10 -3
  3. data/lib/switchman/active_record/association.rb +36 -10
  4. data/lib/switchman/active_record/base.rb +9 -5
  5. data/lib/switchman/active_record/calculations.rb +12 -11
  6. data/lib/switchman/active_record/connection_handler.rb +102 -47
  7. data/lib/switchman/active_record/finder_methods.rb +1 -1
  8. data/lib/switchman/active_record/query_methods.rb +50 -25
  9. data/lib/switchman/active_record/relation.rb +4 -4
  10. data/lib/switchman/active_record/spawn_methods.rb +76 -29
  11. data/lib/switchman/connection_pool_proxy.rb +2 -1
  12. data/lib/switchman/database_server.rb +4 -2
  13. data/lib/switchman/engine.rb +1 -1
  14. data/lib/switchman/r_spec_helper.rb +14 -6
  15. data/lib/switchman/rails.rb +7 -9
  16. data/lib/switchman/test_helper.rb +4 -3
  17. data/lib/switchman/version.rb +1 -1
  18. data/spec/dummy/app/models/appendage.rb +2 -4
  19. data/spec/dummy/app/models/digit.rb +2 -4
  20. data/spec/dummy/app/models/feature.rb +0 -2
  21. data/spec/dummy/app/models/user.rb +0 -2
  22. data/spec/dummy/config/application.rb +0 -6
  23. data/spec/dummy/config/environments/development.rb +2 -9
  24. data/spec/dummy/config/environments/production.rb +2 -3
  25. data/spec/dummy/config/environments/test.rb +2 -11
  26. data/spec/dummy/config/routes.rb +2 -2
  27. data/spec/dummy/log/test.log +9947 -0
  28. data/spec/dummy/tmp/cache/375/530/shard%2F3200 +1 -0
  29. data/spec/dummy/tmp/cache/37A/590/shard%2F3214 +1 -0
  30. data/spec/dummy/tmp/cache/37E/620/shard%2F3182 +0 -0
  31. data/spec/dummy/tmp/cache/381/650/shard%2F3185 +1 -0
  32. data/spec/dummy/tmp/cache/382/650/shard%2F3177 +1 -0
  33. data/spec/dummy/tmp/cache/386/6B0/shard%2F3199 +1 -0
  34. data/spec/dummy/tmp/cache/3A6/F80/shard%2F13200 +1 -0
  35. data/spec/dummy/tmp/cache/3B0/080/shard%2F13183 +1 -0
  36. data/spec/dummy/tmp/cache/3B1/070/shard%2F13166 +1 -0
  37. data/spec/lib/active_record/association_spec.rb +21 -11
  38. data/spec/lib/active_record/finder_methods_spec.rb +1 -1
  39. data/spec/lib/active_record/query_cache_spec.rb +17 -17
  40. data/spec/lib/active_record/query_methods_spec.rb +6 -5
  41. data/spec/lib/active_record/relation_spec.rb +1 -1
  42. data/spec/lib/active_record/spawn_methods_spec.rb +5 -4
  43. data/spec/lib/connection_pool_proxy_spec.rb +2 -0
  44. data/spec/lib/database_server_spec.rb +12 -4
  45. data/spec/lib/shackles_spec.rb +8 -6
  46. metadata +61 -11
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NTc5NGVlNTU2ODI2YTQ3NjY4OTBhOGZiNTZjN2MyMWNjYjY5YmNhZg==
4
+ YzRjZGFmYzVmY2EwYjYyNGQ3Nzc0NzYxNjdhOTQ2M2U5ZDU1YmE0Zg==
5
5
  data.tar.gz: !binary |-
6
- ZGNkZjQ3YWQ0Njg5ZTJhMDQ5ZTVmMWIyZGVhZDJhMTQ5MzE4ZDNlZg==
6
+ ZTY2ZDgwNDU2YjFmOWYwMDMyYzgyYzJmOGU5MWIzZTJlNGIzNDVhNg==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- ODY5NDM1MTFiYTFkMWM5MWYzMDJiZjFkNjY1YWNjZDg3MjFmZjM4YTM3ZjEx
10
- MzA4MDJjMjJkMjViOTQ5ZWZlMGYyOWUwMmRmYjI2MzNhYjE3ZmJhODk4ZGJh
11
- ODhlYzllMmQxYzVhYjc2NTE4MjYxNmIzZGM2N2M0YWU2NmZkMzg=
9
+ NTVmOWYyY2Y2YWUzMmQ4MWY3NjUzZTFiYThmMmM0OGM3ZDA1YTczNDdmZjFl
10
+ MjEzYmJhNzBjODc4OGZmYzgwZmEwNDI3NWQ2YWU3Yzg3ODBkNTBmNTYyNThh
11
+ YTJlOGRjMDI5NzhjYmNhZmQyYTMwNWQ4MGIwOWQ2MDM4ODYwZDU=
12
12
  data.tar.gz: !binary |-
13
- YzlkM2FlYmY5MTNlMzUzMDBhYjRkZGU5MGY5NWZjMzRiMGYyYzhlNDAyMmM0
14
- MWE1M2RkZmUyNWRjNDdlN2U5YTY3NTgxNWY4YWI4Y2M3NGZjMmUxMGNjODFh
15
- NTA1MGIwNTdlZTBhY2U1MzM1ZmU5ZDBiYjc1ZmM2MTE2MDdjZmY=
13
+ MzY2ODJiM2E3YWZkNjJhYjllODIyNDBkMzQ5NTkyNzhjNjY4Yzc5MDdiY2Iy
14
+ OGE3ZTA2N2U4YTdlZWFmMWJhZjQzNTU5NzNmYWYyMGIxZTNmOWVhZTc3Mzgw
15
+ MWE3YjYwNTlkODIzZThkNTlmMDI2NTVhNWNlNTA4Nzc5MTdlNjI=
@@ -16,8 +16,6 @@ module Switchman
16
16
  private_constant :CATEGORIES
17
17
  @shard_category = :unsharded
18
18
 
19
- attr_accessible :name, :database_server, :default
20
-
21
19
  # only allow one default
22
20
  validates_uniqueness_of :default, :if => lambda { |s| s.default? }
23
21
 
@@ -87,7 +85,11 @@ module Switchman
87
85
  nil
88
86
  else
89
87
  shard = Shard.new
90
- shard.assign_attributes(attributes, :without_protection => true)
88
+ if ::Rails.version < '4'
89
+ shard.assign_attributes(attributes, :without_protection => true)
90
+ else
91
+ shard.assign_attributes(attributes)
92
+ end
91
93
  shard.instance_variable_set(:@new_record, false)
92
94
  # connection info doesn't exist in database.yml;
93
95
  # pretend the shard doesn't exist either
@@ -520,6 +522,11 @@ module Switchman
520
522
  super
521
523
  end
522
524
 
525
+ # skip global_id.hash
526
+ def hash
527
+ id.hash
528
+ end
529
+
523
530
  private
524
531
 
525
532
  def clear_cache
@@ -2,7 +2,8 @@ module Switchman
2
2
  module ActiveRecord
3
3
  module Association
4
4
  def self.included(klass)
5
- %w{build_record creation_attributes load_target scoped}.each do |method|
5
+ %w{build_record creation_attributes load_target scope}.each do |method|
6
+ method = 'scoped' if method == 'scope' && ::Rails.version < '4'
6
7
  klass.alias_method_chain(method, :sharding)
7
8
  end
8
9
  end
@@ -24,10 +25,14 @@ module Switchman
24
25
  self.shard.activate { load_target_without_sharding }
25
26
  end
26
27
 
27
- def scoped_with_sharding
28
- shard_value = @reflection.options[:multishard] ? @owner : self.shard
29
- @owner.shard.activate { scoped_without_sharding.shard(shard_value, :association) }
30
- end
28
+ # scoped is renamed to scope in Rails 4
29
+ method = ::Rails.version < '4' ? 'scoped' : 'scope'
30
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
31
+ def #{method}_with_sharding
32
+ shard_value = @reflection.options[:multishard] ? @owner : self.shard
33
+ @owner.shard.activate { #{method}_without_sharding.shard(shard_value, :association) }
34
+ end
35
+ RUBY
31
36
 
32
37
  def creation_attributes_with_sharding
33
38
  attributes = creation_attributes_without_sharding
@@ -65,9 +70,17 @@ module Switchman
65
70
  end
66
71
 
67
72
  module Builder
68
- module Association
73
+ module CollectionAssociation
69
74
  def self.included(klass)
70
- klass.descendants.each{|d| d.valid_options += [:multishard]}
75
+ if ::Rails.version < '4'
76
+ [klass] + klass.descendants.each do |k|
77
+ k.valid_options << :multishard
78
+ end
79
+ end
80
+ end
81
+
82
+ def valid_options
83
+ super + [:multishard]
71
84
  end
72
85
  end
73
86
  end
@@ -77,7 +90,11 @@ module Switchman
77
90
  def self.included(klass)
78
91
  klass.send(:remove_method, :associated_records_by_owner)
79
92
  klass.send(:remove_method, :owners_by_key)
80
- klass.send(:remove_method, :scoped)
93
+ if ::Rails.version < '4'
94
+ klass.send(:remove_method, :scoped)
95
+ else
96
+ klass.send(:remove_method, :scope)
97
+ end
81
98
  end
82
99
 
83
100
  def associated_records_by_owner
@@ -126,15 +143,24 @@ module Switchman
126
143
  end
127
144
  end
128
145
 
129
- def scoped
146
+ def scope
130
147
  build_scope
131
148
  end
149
+ # renamed to just scope in Rails 4
150
+ if ::Rails.version < '4'
151
+ alias_method :scoped, :scope
152
+ remove_method(:scope)
153
+ end
132
154
  end
133
155
  end
134
156
 
135
157
  module CollectionProxy
136
158
  def shard(*args)
137
- scoped.shard(*args)
159
+ if ::Rails.version < '4'
160
+ scoped.shard(*args)
161
+ else
162
+ scope.shard(*args)
163
+ end
138
164
  end
139
165
  end
140
166
  end
@@ -2,7 +2,11 @@ module Switchman
2
2
  module ActiveRecord
3
3
  module Base
4
4
  module ClassMethods
5
- delegate :shard, :to => :scoped
5
+ if ::Rails.version < '4'
6
+ delegate :shard, to: :scoped
7
+ else
8
+ delegate :shard, to: :all
9
+ end
6
10
 
7
11
  def shard_category
8
12
  @shard_category || :default
@@ -29,7 +33,7 @@ module Switchman
29
33
  end
30
34
 
31
35
  def transaction(*args)
32
- if current_scope
36
+ if self != ::ActiveRecord::Base && current_scope
33
37
  current_scope.activate do
34
38
  db = Shard.current(shard_category).database_server
35
39
  if ::Shackles.environment != db.shackles_environment
@@ -78,16 +82,16 @@ module Switchman
78
82
 
79
83
  def save(*args)
80
84
  @shard_set_in_stone = true
81
- self.class.with_scope(self.class.shard(shard), :overwrite) { super }
85
+ self.class.shard(shard).scoping { super }
82
86
  end
83
87
 
84
88
  def save!(*args)
85
89
  @shard_set_in_stone = true
86
- self.class.with_scope(self.class.shard(shard), :overwrite) { super }
90
+ self.class.shard(shard).scoping { super }
87
91
  end
88
92
 
89
93
  def destroy
90
- self.class.with_scope(self.class.shard(shard), :overwrite) { super }
94
+ self.class.shard(shard).scoping { super }
91
95
  end
92
96
 
93
97
  def clone
@@ -45,15 +45,15 @@ module Switchman
45
45
  relation.select_values = [operation_over_aggregate_column(column, "average", distinct).as("average"),
46
46
  operation_over_aggregate_column(column, "count", distinct).as("count")]
47
47
 
48
- initial_results = relation.activate{ |rel| @klass.connection.select_all(rel) }
48
+ initial_results = relation.activate{ |rel| klass.connection.select_all(rel) }
49
49
  if initial_results.is_a?(Array)
50
50
  initial_results.each do |r|
51
- r["average"] = type_cast_calculated_value(r["average"], nil, "average")
52
- r["count"] = type_cast_calculated_value(r["count"], nil, "count")
51
+ r["average"] = type_cast_calculated_value(r["average"], column_for(column_name), "average")
52
+ r["count"] = type_cast_calculated_value(r["count"], column_for(column_name), "count")
53
53
  end
54
54
  result = initial_results.map{|r| r["average"] * r["count"]}.sum / initial_results.map{|r| r["count"]}.sum
55
55
  else
56
- result = type_cast_calculated_value(initial_results["average"], nil, "average")
56
+ result = type_cast_calculated_value(initial_results.first["average"], column_for(column_name), "average")
57
57
  end
58
58
  result
59
59
  end
@@ -66,7 +66,7 @@ module Switchman
66
66
  target_shard = Shard.current(:default)
67
67
 
68
68
  rows = relation.activate do |rel, shard|
69
- calculated_data = @klass.connection.select_all(rel)
69
+ calculated_data = klass.connection.select_all(rel)
70
70
 
71
71
  if opts[:association]
72
72
  key_ids = calculated_data.collect { |row| row[opts[:group_aliases].first] }
@@ -98,9 +98,9 @@ module Switchman
98
98
  opts = {:operation => operation, :column_name => column_name, :distinct => distinct}
99
99
 
100
100
  opts[:aggregate_alias] = aggregate_alias_for(operation, column_name)
101
- group_attrs = @group_values
101
+ group_attrs = group_values
102
102
  if group_attrs.first.respond_to?(:to_sym)
103
- association = @klass.reflect_on_association(group_attrs.first.to_sym)
103
+ association = klass.reflect_on_association(group_attrs.first.to_sym)
104
104
  associated = group_attrs.size == 1 && association && association.macro == :belongs_to # only count belongs_to associations
105
105
  group_fields = Array(associated ? association.foreign_key : group_attrs)
106
106
  else
@@ -125,12 +125,12 @@ module Switchman
125
125
  elsif operation == 'average'
126
126
  'average'
127
127
  else
128
- column_alias_for(operation, column_name)
128
+ column_alias_for("#{operation} #{column_name}")
129
129
  end
130
130
  end
131
131
 
132
132
  def build_grouped_calculation_relation(opts)
133
- group = @klass.connection.adapter_name == 'FrontBase' ? opts[:group_aliases] : opts[:group_fields]
133
+ group = opts[:group_fields]
134
134
 
135
135
  select_values = [
136
136
  operation_over_aggregate_column(
@@ -146,7 +146,7 @@ module Switchman
146
146
  'count', opts[:distinct]).as('count')
147
147
  end
148
148
 
149
- select_values += @select_values unless @having_values.empty?
149
+ select_values += select_values unless having_values.empty?
150
150
  select_values.concat opts[:group_fields].zip(opts[:group_aliases]).map { |field,aliaz|
151
151
  if field.respond_to?(:as)
152
152
  field.as(aliaz)
@@ -155,7 +155,8 @@ module Switchman
155
155
  end
156
156
  }
157
157
 
158
- relation = except(:group).group(group)
158
+ relation = except(:group)
159
+ relation.group_values = group
159
160
  relation.select_values = select_values
160
161
  relation
161
162
  end
@@ -22,10 +22,11 @@ module Switchman
22
22
  def self.included(klass)
23
23
  klass.alias_method_chain(:establish_connection, :sharding)
24
24
  klass.alias_method_chain(:remove_connection, :sharding)
25
+ klass.send(:remove_method, :retrieve_connection_pool) if ::Rails.version >= '4'
25
26
  end
26
27
 
27
- def establish_connection_with_sharding(name, spec)
28
- establish_connection_without_sharding(name, spec)
28
+ def establish_connection_with_sharding(owner, spec)
29
+ establish_connection_without_sharding(owner, spec)
29
30
 
30
31
  # this is the first place that the adapter would have been required; but now we
31
32
  # need this addition ASAP since it will be called when loading the default shard below
@@ -34,8 +35,16 @@ module Switchman
34
35
  ::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:include, ActiveRecord::PostgreSQLAdapter)
35
36
  end
36
37
 
37
- model = name.constantize
38
- pool = connection_pools[spec]
38
+ # AR3 uses the name, AR4 uses the model
39
+ model = case owner
40
+ when String
41
+ owner.constantize
42
+ when Class
43
+ owner
44
+ else
45
+ raise "unknown owner #{owner}"
46
+ end
47
+ pool = ::Rails.version < '4' ? connection_pools[spec] : owner_to_pool[owner.name]
39
48
 
40
49
  first_time = !Shard.instance_variable_get(:@default)
41
50
  if first_time
@@ -52,7 +61,12 @@ module Switchman
52
61
  proxy = ConnectionPoolProxy.new(model.shard_category,
53
62
  pool,
54
63
  @shard_connection_pools)
55
- connection_pools[spec] = proxy
64
+ if ::Rails.version < '4'
65
+ connection_pools[spec] = proxy
66
+ else
67
+ owner_to_pool[owner.name] = proxy
68
+ class_to_pool.clear
69
+ end
56
70
 
57
71
  if first_time
58
72
  if Shard.default.database_server.config[:prefer_slave]
@@ -65,8 +79,10 @@ module Switchman
65
79
  end
66
80
  end
67
81
 
68
- initialize_categories(model)
69
- @class_to_pool[name] = proxy
82
+ if ::Rails.version < '4'
83
+ initialize_categories(model)
84
+ class_to_pool[model.name] = proxy
85
+ end
70
86
 
71
87
  # reload the default shard if we just got a new connection
72
88
  # to where the Shards table is
@@ -96,61 +112,100 @@ module Switchman
96
112
  end
97
113
 
98
114
  def remove_connection_with_sharding(model)
99
- uninitialize_ar(model) if @class_to_pool[model.name].is_a?(ConnectionPoolProxy)
100
- remove_connection_without_sharding(model)
115
+ uninitialize_ar(model) if (::Rails.version < '4' ? class_to_pool : owner_to_pool)[model.name].is_a?(ConnectionPoolProxy)
116
+ result = remove_connection_without_sharding(model)
117
+ initialize_categories if ::Rails.version >= '4'
118
+ result
119
+ end
120
+
121
+ if ::Rails.version >= '4'
122
+ def retrieve_connection_pool(klass)
123
+ class_to_pool[klass.name] ||= begin
124
+ original_klass = klass
125
+ until pool = pool_for(klass)
126
+ klass = klass.superclass
127
+ break unless klass <= Base
128
+ end
129
+
130
+ if pool.is_a?(ConnectionPoolProxy) && pool.category != original_klass.shard_category
131
+ default_pool = pool.default_pool
132
+ pool = nil
133
+ class_to_pool.each_value { |p| pool = p if p.is_a?(ConnectionPoolProxy) &&
134
+ p.category == original_klass.shard_category &&
135
+ p.default_pool == default_pool }
136
+ pool ||= ConnectionPoolProxy.new(original_klass.shard_category, default_pool, @shard_connection_pools)
137
+ end
138
+
139
+ class_to_pool[original_klass.name] = pool
140
+ end
141
+ end
101
142
  end
102
143
 
103
144
  private
104
145
 
146
+ # AR3 only; AR4 defines it, and hides this version,
147
+ # and it's a slightly different data structure
148
+ def class_to_pool
149
+ @class_to_pool
150
+ end
151
+
105
152
  def uninitialize_ar(model = ::ActiveRecord::Base)
106
153
  # take the proxies out
107
- @class_to_pool.each_key do |model_name|
108
- pool_model = model_name.constantize
109
- # only de-proxify models that inherit from what we're uninitializing
110
- next unless pool_model == model || pool_model < model
111
- proxy = @class_to_pool[model_name]
112
- next unless proxy.is_a?(ConnectionPoolProxy)
113
-
114
- # make sure we're switched back to the default shard for the
115
- # connection that will remain
116
- if proxy.connected?
117
- Shard.default.activate { proxy.connection }
154
+ if ::Rails.version >= '4'
155
+ owner_to_pool[model.name] = owner_to_pool[model.name].default_pool
156
+ else
157
+ class_to_pool.each_key do |model_name|
158
+ pool_model = model_name.constantize
159
+ # only de-proxify models that inherit from what we're uninitializing
160
+ next unless pool_model == model || pool_model < model
161
+ proxy = class_to_pool[model_name]
162
+ next unless proxy.is_a?(ConnectionPoolProxy)
163
+
164
+ # make sure we're switched back to the default shard for the
165
+ # connection that will remain
166
+ if proxy.connected?
167
+ Shard.default.activate(proxy.category) { proxy.connection }
168
+ end
169
+ connection_pools[proxy.spec] = proxy.default_pool
170
+ class_to_pool[model_name] = proxy.default_pool
118
171
  end
119
- connection_pools[proxy.spec] = proxy.default_pool
120
- @class_to_pool[model_name] = proxy.default_pool
121
- end
122
172
 
123
- # prune dups that were created for implementing shard categories
124
- @class_to_pool.each_key do |model_name|
125
- next if model_name == ::ActiveRecord::Base.name
126
- pool_model = model_name.constantize
127
- @class_to_pool.delete(model_name) if retrieve_connection_pool(pool_model.superclass) == @class_to_pool[model_name]
173
+ # prune dups that were created for implementing shard categories
174
+ class_to_pool.each_key do |model_name|
175
+ next if model_name == ::ActiveRecord::Base.name
176
+ pool_model = model_name.constantize
177
+ class_to_pool.delete(model_name) if retrieve_connection_pool(pool_model.superclass) == class_to_pool[model_name]
178
+ end
128
179
  end
129
180
  end
130
181
 
131
182
  # semi-private
132
183
  public
133
184
  def initialize_categories(model = ::ActiveRecord::Base)
134
- # now set up pools for models that inherit from this model, but with a different
135
- # sharding category
136
- Shard.const_get(:CATEGORIES).each do |category, models|
137
- next if category == :default
138
- next if category == model.shard_category
139
-
140
- this_proxy = nil
141
- Array(models).each do |category_model|
142
- category_model = category_model.constantize if category_model.is_a? String
143
- next unless category_model < model
144
-
145
- # don't replace existing connections
146
- next if @class_to_pool[category_model.name]
147
-
148
- default_pool = retrieve_connection_pool(model)
149
- default_pool = default_pool.default_pool if default_pool.is_a?(ConnectionPoolProxy)
150
- # look for an existing compatible proxy for this category
151
- this_proxy ||= ConnectionPoolProxy.new(category_model.shard_category, default_pool, @shard_connection_pools)
152
- @class_to_pool[category_model.name] = this_proxy
185
+ if ::Rails.version < '4'
186
+ # now set up pools for models that inherit from this model, but with a different
187
+ # sharding category
188
+ Shard.const_get(:CATEGORIES).each do |category, models|
189
+ next if category == :default
190
+ next if category == model.shard_category
191
+
192
+ this_proxy = nil
193
+ Array(models).each do |category_model|
194
+ category_model = category_model.constantize if category_model.is_a? String
195
+ next unless category_model < model
196
+
197
+ # don't replace existing connections
198
+ next if class_to_pool[category_model.name]
199
+
200
+ default_pool = retrieve_connection_pool(model)
201
+ default_pool = default_pool.default_pool if default_pool.is_a?(ConnectionPoolProxy)
202
+ # look for an existing compatible proxy for this category
203
+ this_proxy ||= ConnectionPoolProxy.new(category_model.shard_category, default_pool, @shard_connection_pools)
204
+ class_to_pool[category_model.name] = this_proxy
205
+ end
153
206
  end
207
+ else
208
+ class_to_pool.clear
154
209
  end
155
210
  end
156
211
  end