switchman 2.1.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +10 -2
  3. data/app/models/switchman/shard.rb +234 -282
  4. data/app/models/switchman/unsharded_record.rb +7 -0
  5. data/db/migrate/20130328212039_create_switchman_shards.rb +1 -1
  6. data/db/migrate/20130328224244_create_default_shard.rb +5 -5
  7. data/db/migrate/20161206323434_add_back_default_string_limits_switchman.rb +1 -0
  8. data/db/migrate/20180828183945_add_default_shard_index.rb +2 -2
  9. data/db/migrate/20180828192111_add_timestamps_to_shards.rb +7 -5
  10. data/db/migrate/20190114212900_add_unique_name_indexes.rb +5 -3
  11. data/lib/switchman/action_controller/caching.rb +2 -2
  12. data/lib/switchman/active_record/abstract_adapter.rb +1 -0
  13. data/lib/switchman/active_record/association.rb +78 -89
  14. data/lib/switchman/active_record/attribute_methods.rb +58 -52
  15. data/lib/switchman/active_record/base.rb +58 -59
  16. data/lib/switchman/active_record/calculations.rb +74 -67
  17. data/lib/switchman/active_record/connection_pool.rb +14 -41
  18. data/lib/switchman/active_record/database_configurations/database_config.rb +13 -0
  19. data/lib/switchman/active_record/database_configurations.rb +34 -0
  20. data/lib/switchman/active_record/finder_methods.rb +11 -16
  21. data/lib/switchman/active_record/log_subscriber.rb +4 -8
  22. data/lib/switchman/active_record/migration.rb +6 -47
  23. data/lib/switchman/active_record/model_schema.rb +1 -1
  24. data/lib/switchman/active_record/persistence.rb +4 -6
  25. data/lib/switchman/active_record/postgresql_adapter.rb +124 -168
  26. data/lib/switchman/active_record/predicate_builder.rb +2 -2
  27. data/lib/switchman/active_record/query_cache.rb +18 -19
  28. data/lib/switchman/active_record/query_methods.rb +172 -197
  29. data/lib/switchman/active_record/reflection.rb +7 -22
  30. data/lib/switchman/active_record/relation.rb +30 -78
  31. data/lib/switchman/active_record/spawn_methods.rb +27 -29
  32. data/lib/switchman/active_record/statement_cache.rb +18 -35
  33. data/lib/switchman/active_record/tasks/database_tasks.rb +16 -0
  34. data/lib/switchman/active_support/cache.rb +3 -5
  35. data/lib/switchman/arel.rb +13 -8
  36. data/lib/switchman/database_server.rb +121 -142
  37. data/lib/switchman/default_shard.rb +52 -16
  38. data/lib/switchman/engine.rb +61 -58
  39. data/lib/switchman/environment.rb +4 -8
  40. data/lib/switchman/errors.rb +1 -0
  41. data/lib/switchman/guard_rail/relation.rb +5 -7
  42. data/lib/switchman/guard_rail.rb +6 -19
  43. data/lib/switchman/r_spec_helper.rb +29 -37
  44. data/lib/switchman/rails.rb +14 -12
  45. data/lib/switchman/schema_cache.rb +1 -9
  46. data/lib/switchman/sharded_instrumenter.rb +1 -1
  47. data/lib/switchman/standard_error.rb +15 -3
  48. data/lib/switchman/test_helper.rb +6 -4
  49. data/lib/switchman/version.rb +1 -1
  50. data/lib/switchman.rb +3 -5
  51. data/lib/tasks/switchman.rake +54 -69
  52. metadata +87 -45
  53. data/lib/switchman/active_record/batches.rb +0 -11
  54. data/lib/switchman/active_record/connection_handler.rb +0 -190
  55. data/lib/switchman/active_record/where_clause_factory.rb +0 -36
  56. data/lib/switchman/connection_pool_proxy.rb +0 -173
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Switchman
4
+ class UnshardedRecord < ::ActiveRecord::Base
5
+ sharded_model
6
+ end
7
+ end
@@ -5,7 +5,7 @@ class CreateSwitchmanShards < ActiveRecord::Migration[4.2]
5
5
  create_table :switchman_shards do |t|
6
6
  t.string :name
7
7
  t.string :database_server_id
8
- t.boolean :default, :default => false, :null => false
8
+ t.boolean :default, default: false, null: false
9
9
  end
10
10
  end
11
11
  end
@@ -2,10 +2,10 @@
2
2
 
3
3
  class CreateDefaultShard < ActiveRecord::Migration[4.2]
4
4
  def up
5
- unless Switchman::Shard.default.is_a?(Switchman::Shard)
6
- Switchman::Shard.reset_column_information
7
- Switchman::Shard.create!(:default => true)
8
- Switchman::Shard.default(reload: true)
9
- end
5
+ return if Switchman::Shard.default.is_a?(Switchman::Shard)
6
+
7
+ Switchman::Shard.reset_column_information
8
+ Switchman::Shard.create!(default: true)
9
+ Switchman::Shard.default(reload: true)
10
10
  end
11
11
  end
@@ -8,6 +8,7 @@ class AddBackDefaultStringLimitsSwitchman < ActiveRecord::Migration[4.2]
8
8
 
9
9
  def add_string_limit_if_missing(table, column)
10
10
  return if column_exists?(table, column, :string, limit: 255)
11
+
11
12
  change_column table, column, :string, limit: 255
12
13
  end
13
14
  end
@@ -6,10 +6,10 @@ class AddDefaultShardIndex < ActiveRecord::Migration[4.2]
6
6
  change_column_default :switchman_shards, :default, false
7
7
  change_column_null :switchman_shards, :default, false
8
8
  options = if connection.adapter_name == 'PostgreSQL'
9
- { unique: true, where: "\"default\"" }
9
+ { unique: true, where: '"default"' }
10
10
  else
11
11
  {}
12
12
  end
13
- add_index :switchman_shards, :default, **options
13
+ add_index :switchman_shards, :default, options
14
14
  end
15
15
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class AddTimestampsToShards < ActiveRecord::Migration[4.2]
4
+ disable_ddl_transaction!
5
+
4
6
  def change
5
7
  add_timestamps :switchman_shards, null: true
6
8
  now = Time.now.utc
@@ -8,10 +10,10 @@ class AddTimestampsToShards < ActiveRecord::Migration[4.2]
8
10
  change_column_null :switchman_shards, :updated_at, false
9
11
  change_column_null :switchman_shards, :created_at, false
10
12
 
11
- if Switchman::Shard.current.default?
12
- Switchman::Shard.connection.schema_cache.clear!
13
- Switchman::Shard.reset_column_information
14
- Switchman::Shard.columns
15
- end
13
+ return unless Switchman::Shard.current.default?
14
+
15
+ Switchman::Shard.connection.schema_cache.clear!
16
+ Switchman::Shard.reset_column_information
17
+ Switchman::Shard.columns
16
18
  end
17
19
  end
@@ -2,8 +2,10 @@
2
2
 
3
3
  class AddUniqueNameIndexes < ActiveRecord::Migration[4.2]
4
4
  def change
5
- add_index :switchman_shards, [:database_server_id, :name], unique: true
6
- add_index :switchman_shards, :database_server_id, unique: true, where: "name IS NULL", name: 'index_switchman_shards_unique_primary_shard'
7
- add_index :switchman_shards, "(true)", unique: true, where: "database_server_id IS NULL AND name IS NULL", name: 'index_switchman_shards_unique_primary_db_and_shard'
5
+ add_index :switchman_shards, %i[database_server_id name], unique: true
6
+ add_index :switchman_shards, :database_server_id, unique: true, where: 'name IS NULL',
7
+ name: 'index_switchman_shards_unique_primary_shard'
8
+ add_index :switchman_shards, '(true)', unique: true, where: 'database_server_id IS NULL AND name IS NULL',
9
+ name: 'index_switchman_shards_unique_primary_db_and_shard'
8
10
  end
9
11
  end
@@ -13,7 +13,7 @@ module Switchman
13
13
  # disallow assigning to ActionController::Base.cache_store or
14
14
  # ActionController::Base#cache_store for the same reasons we disallow
15
15
  # assigning to Rails.cache
16
- def cache_store=(cache)
16
+ def cache_store=(_cache)
17
17
  raise NoMethodError
18
18
  end
19
19
  end
@@ -21,7 +21,7 @@ module Switchman
21
21
  include ConfigMethods
22
22
 
23
23
  def self.included(base)
24
- base.extend(ConfigMethods)
24
+ base.singleton_class.prepend(ConfigMethods)
25
25
  end
26
26
  end
27
27
  end
@@ -45,6 +45,7 @@ module Switchman
45
45
 
46
46
  def id_value_for_database(value)
47
47
  return super unless value.class.sharded_primary_key?
48
+
48
49
  # do this the Rails 4.2 way, so that if Shard.current != self.shard, the id gets transposed
49
50
  quote(value.id)
50
51
  end
@@ -8,27 +8,17 @@ module Switchman
8
8
  end
9
9
 
10
10
  def build_record(*args)
11
- self.shard.activate { super }
11
+ shard.activate { super }
12
12
  end
13
13
 
14
14
  def load_target
15
- self.shard.activate { super }
15
+ shard.activate { super }
16
16
  end
17
17
 
18
18
  def scope
19
- shard_value = @reflection.options[:multishard] ? @owner : self.shard
19
+ shard_value = @reflection.options[:multishard] ? @owner : shard
20
20
  @owner.shard.activate { super.shard(shard_value, :association) }
21
21
  end
22
-
23
- def creation_attributes
24
- attributes = super
25
-
26
- # translate keys
27
- if reflection.macro.in?([:has_one, :has_many]) && !options[:through]
28
- attributes[reflection.foreign_key] = Shard.relative_id_for(owner[reflection.active_record_primary_key], owner.shard, self.shard)
29
- end
30
- attributes
31
- end
32
22
  end
33
23
 
34
24
  module CollectionAssociation
@@ -36,15 +26,19 @@ module Switchman
36
26
  shards = reflection.options[:multishard] && owner.respond_to?(:associated_shards) ? owner.associated_shards : [shard]
37
27
  # activate both the owner and the target's shard category, so that Reflection#join_id_for,
38
28
  # when called for the owner, will be returned relative to shard the query will execute on
39
- Shard.with_each_shard(shards, [klass.shard_category, owner.class.shard_category].uniq) do
29
+ Shard.with_each_shard(shards, [klass.connection_classes, owner.class.connection_classes].uniq) do
40
30
  super
41
31
  end
42
32
  end
33
+
34
+ def _create_record(*)
35
+ shard.activate { super }
36
+ end
43
37
  end
44
38
 
45
39
  module BelongsToAssociation
46
- def replace_keys(record)
47
- if record && record.class.sharded_column?(reflection.association_primary_key(record.class))
40
+ def replace_keys(record, force: false)
41
+ if record&.class&.sharded_column?(reflection.association_primary_key(record.class))
48
42
  foreign_id = record[reflection.association_primary_key(record.class)]
49
43
  owner[reflection.foreign_key] = Shard.relative_id_for(foreign_id, record.shard, owner.shard)
50
44
  else
@@ -54,7 +48,7 @@ module Switchman
54
48
 
55
49
  def shard
56
50
  if @owner.class.sharded_column?(@reflection.foreign_key) &&
57
- foreign_id = @owner[@reflection.foreign_key]
51
+ (foreign_id = @owner[@reflection.foreign_key])
58
52
  Shard.shard_for(foreign_id, @owner.shard)
59
53
  else
60
54
  super
@@ -62,9 +56,22 @@ module Switchman
62
56
  end
63
57
  end
64
58
 
65
- module Extension
66
- def self.build(_model, _reflection)
59
+ module ForeignAssociation
60
+ # significant change:
61
+ # * transpose the key to the correct shard
62
+ def set_owner_attributes(record) # rubocop:disable Naming/AccessorMethodName
63
+ return if options[:through]
64
+
65
+ key = owner._read_attribute(reflection.join_foreign_key)
66
+ key = Shard.relative_id_for(key, owner.shard, shard)
67
+ record._write_attribute(reflection.join_primary_key, key)
68
+
69
+ record._write_attribute(reflection.type, owner.class.polymorphic_name) if reflection.type
67
70
  end
71
+ end
72
+
73
+ module Extension
74
+ def self.build(_model, _reflection); end
68
75
 
69
76
  def self.valid_options
70
77
  [:multishard]
@@ -75,45 +82,35 @@ module Switchman
75
82
 
76
83
  module Preloader
77
84
  module Association
78
- if ::Rails.version >= "5.2" and ::Rails.version < "6.0"
79
- def run(preloader)
80
- associated_records_by_owner.each do |owner, records|
81
- associate_records_to_owner(owner, records)
82
- end
85
+ # Copypasta from Activerecord but with added global_id_for goodness.
86
+ def records_for(ids)
87
+ scope.where(association_key_name => ids).load do |record|
88
+ global_key = if record.class.connection_classes == UnshardedRecord
89
+ convert_key(record[association_key_name])
90
+ else
91
+ Shard.global_id_for(record[association_key_name], record.shard)
92
+ end
93
+ owner = owners_by_key[convert_key(global_key)].first
94
+ association = owner.association(reflection.name)
95
+ association.set_inverse_instance(record)
83
96
  end
84
97
  end
85
98
 
86
- if ::Rails.version >= "6.0"
87
- # Copypasta from Activerecord but with added global_id_for goodness.
88
- def records_for(ids)
89
- scope.where(association_key_name => ids).load do |record|
90
- global_key = if model.shard_category == :unsharded
91
- convert_key(record[association_key_name])
92
- else
93
- Shard.global_id_for(record[association_key_name], record.shard)
94
- end
95
- owner = owners_by_key[global_key.to_s].first
96
- association = owner.association(reflection.name)
97
- association.set_inverse_instance(record)
98
- end
99
- end
100
-
101
- def records_by_owner
102
- associated_records_by_owner
103
- end
104
- end
105
-
106
- def associated_records_by_owner(preloader = nil)
107
- return @associated_records_by_owner if defined?(@associated_records_by_owner)
108
- owners_map = owners_by_key
99
+ # significant changes:
100
+ # * partition_by_shard the records_for call
101
+ # * re-globalize the fetched owner id before looking up in the map
102
+ def load_records
103
+ # owners can be duplicated when a relation has a collection association join
104
+ # #compare_by_identity makes such owners different hash keys
105
+ @records_by_owner = {}.compare_by_identity
109
106
 
110
- if klass.nil? || owners_map.empty?
111
- records = []
107
+ if owner_keys.empty?
108
+ raw_records = []
112
109
  else
113
110
  # determine the shard to search for each owner
114
111
  if reflection.macro == :belongs_to
115
112
  # for belongs_to, it's the shard of the foreign_key
116
- partition_proc = ->(owner) do
113
+ partition_proc = lambda do |owner|
117
114
  if owner.class.sharded_column?(owner_key_name)
118
115
  Shard.shard_for(owner[owner_key_name], owner.shard)
119
116
  else
@@ -123,64 +120,56 @@ module Switchman
123
120
  elsif !reflection.options[:multishard]
124
121
  # for non-multishard associations, it's *just* the owner's shard
125
122
  partition_proc = ->(owner) { owner.shard }
126
- else
127
- # for multishard associations, it's the owner object itself
128
- # (all associated shards)
129
-
130
- # this is the default behavior of partition_by_shard, so just let it be nil
131
- # to avoid the proc call
132
- # partition_proc = ->(owner) { owner }
133
123
  end
134
124
 
135
- records = Shard.partition_by_shard(owners, partition_proc) do |partitioned_owners|
136
- # Some databases impose a limit on the number of ids in a list (in Oracle it's 1000)
137
- # Make several smaller queries if necessary or make one query if the adapter supports it
138
- sliced_owners = partitioned_owners.each_slice(model.connection.in_clause_length || partitioned_owners.size)
139
- sliced_owners.map do |slice|
140
- relative_owner_keys = slice.map do |owner|
141
- key = owner[owner_key_name]
142
- if key && owner.class.sharded_column?(owner_key_name)
143
- key = Shard.relative_id_for(key, owner.shard, Shard.current(owner.class.shard_category))
144
- end
145
- key && key.to_s
125
+ raw_records = Shard.partition_by_shard(owners, partition_proc) do |partitioned_owners|
126
+ relative_owner_keys = partitioned_owners.map do |owner|
127
+ key = owner[owner_key_name]
128
+ if key && owner.class.sharded_column?(owner_key_name)
129
+ key = Shard.relative_id_for(key, owner.shard,
130
+ Shard.current(klass.connection_classes))
146
131
  end
147
- relative_owner_keys.compact!
148
- relative_owner_keys.uniq!
149
- records_for(relative_owner_keys)
132
+ convert_key(key)
150
133
  end
134
+ relative_owner_keys.compact!
135
+ relative_owner_keys.uniq!
136
+ records_for(relative_owner_keys)
151
137
  end
152
- records.flatten!
153
138
  end
154
139
 
155
- # This ivar may look unused, but remember this is an extension of
156
- # rails' AR::Associations::Preloader::Association class. It gets used
157
- # by that class (and its subclasses).
158
- @preloaded_records = records
140
+ @preloaded_records = raw_records.select do |record|
141
+ assignments = false
159
142
 
160
- # Each record may have multiple owners, and vice-versa
161
- @associated_records_by_owner = owners.each_with_object({}) do |owner,h|
162
- h[owner] = []
163
- end
164
- records.each do |record|
165
143
  owner_key = record[association_key_name]
166
- owner_key = Shard.global_id_for(owner_key, record.shard) if owner_key && record.class.sharded_column?(association_key_name)
144
+ if owner_key && record.class.sharded_column?(association_key_name)
145
+ owner_key = Shard.global_id_for(owner_key,
146
+ record.shard)
147
+ end
167
148
 
168
- owners_map[owner_key.to_s].each do |owner|
169
- owner.association(reflection.name).set_inverse_instance(record)
170
- @associated_records_by_owner[owner] << record
149
+ owners_by_key[convert_key(owner_key)].each do |owner|
150
+ entries = (@records_by_owner[owner] ||= [])
151
+
152
+ if reflection.collection? || entries.empty?
153
+ entries << record
154
+ assignments = true
155
+ end
171
156
  end
157
+
158
+ assignments
172
159
  end
173
- @associated_records_by_owner
174
160
  end
175
161
 
162
+ # significant change: globalize keys on sharded columns
176
163
  def owners_by_key
177
- @owners_by_key ||= owners.group_by do |owner|
164
+ @owners_by_key ||= owners.each_with_object({}) do |owner, result|
178
165
  key = owner[owner_key_name]
179
166
  key = Shard.global_id_for(key, owner.shard) if key && owner.class.sharded_column?(owner_key_name)
180
- key && key.to_s
167
+ key = convert_key(key)
168
+ (result[key] ||= []) << owner if key
181
169
  end
182
170
  end
183
171
 
172
+ # significant change: don't cache scope (since it could be for different shards)
184
173
  def scope
185
174
  build_scope
186
175
  end
@@ -210,7 +199,7 @@ module Switchman
210
199
  # this seems counter-intuitive, but the autosave code will assign to attribute bypassing switchman,
211
200
  # after reading the id attribute _without_ bypassing switchman. So we need Shard.current for the
212
201
  # category of the associated record to match Shard.current for the category of self
213
- shard.activate(shard_category_for_reflection(reflection)) { super }
202
+ shard.activate(connection_classes_for_reflection(reflection)) { super }
214
203
  end
215
204
  end
216
205
  end
@@ -4,22 +4,23 @@ module Switchman
4
4
  module ActiveRecord
5
5
  module AttributeMethods
6
6
  module ClassMethods
7
-
8
7
  def sharded_primary_key?
9
- self != Shard && shard_category != :unsharded && integral_id?
8
+ !(self <= UnshardedRecord) && integral_id?
10
9
  end
11
10
 
12
11
  def sharded_foreign_key?(column_name)
13
12
  reflection = reflection_for_integer_attribute(column_name.to_s)
14
13
  return false unless reflection
14
+
15
15
  reflection.options[:polymorphic] || reflection.klass.sharded_primary_key?
16
16
  end
17
17
 
18
18
  def sharded_column?(column_name)
19
19
  column_name = column_name.to_s
20
20
  @sharded_column_values ||= {}
21
- unless @sharded_column_values.has_key?(column_name)
22
- @sharded_column_values[column_name] = (column_name == primary_key && sharded_primary_key?) || sharded_foreign_key?(column_name)
21
+ unless @sharded_column_values.key?(column_name)
22
+ @sharded_column_values[column_name] =
23
+ (column_name == primary_key && sharded_primary_key?) || sharded_foreign_key?(column_name)
23
24
  end
24
25
  @sharded_column_values[column_name]
25
26
  end
@@ -29,109 +30,114 @@ module Switchman
29
30
  def reflection_for_integer_attribute(attr_name)
30
31
  attr_name = attr_name.to_s
31
32
  columns_hash[attr_name] && columns_hash[attr_name].type == :integer &&
32
- reflections.find { |_, r| r.belongs_to? && r.foreign_key.to_s == attr_name }&.last
33
+ reflections.find { |_, r| r.belongs_to? && r.foreign_key.to_s == attr_name }&.last
33
34
  rescue ::ActiveRecord::StatementInvalid
34
35
  # this is for when models are referenced in initializers before migrations have been run
35
- raise if connection.open_transactions > 0
36
+ raise if connection.open_transactions.positive?
36
37
  end
37
38
 
38
- def define_method_global_attribute(attr_name)
39
+ def define_method_global_attribute(attr_name, owner:)
39
40
  if sharded_column?(attr_name)
40
- generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
41
- def __temp__
42
- Shard.global_id_for(original_#{attr_name}, shard)
41
+ owner << <<-RUBY
42
+ def global_#{attr_name}
43
+ ::Switchman::Shard.global_id_for(original_#{attr_name}, shard)
43
44
  end
44
- alias_method 'global_#{attr_name}', :__temp__
45
- undef_method :__temp__
46
45
  RUBY
47
46
  else
48
- define_method_unsharded_column(attr_name, 'global')
47
+ define_method_unsharded_column(attr_name, 'global', owner)
49
48
  end
50
49
  end
51
50
 
52
- def define_method_local_attribute(attr_name)
51
+ def define_method_local_attribute(attr_name, owner:)
53
52
  if sharded_column?(attr_name)
54
- generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
55
- def __temp__
56
- Shard.local_id_for(original_#{attr_name}).first
53
+ owner << <<-RUBY
54
+ def local_#{attr_name}
55
+ ::Switchman::Shard.local_id_for(original_#{attr_name}).first
57
56
  end
58
- alias_method 'local_#{attr_name}', :__temp__
59
- undef_method :__temp__
60
57
  RUBY
61
58
  else
62
- define_method_unsharded_column(attr_name, 'local')
59
+ define_method_unsharded_column(attr_name, 'local', owner)
63
60
  end
64
61
  end
65
62
 
66
- # see also Base#shard_category_for_reflection
63
+ # see also Base#connection_classes_for_reflection
67
64
  # the difference being this will output static strings for the common cases, making them
68
65
  # more performant
69
- def shard_category_code_for_reflection(reflection)
66
+ def connection_classes_code_for_reflection(reflection)
70
67
  if reflection
71
68
  if reflection.options[:polymorphic]
72
69
  # a polymorphic association has to be discovered at runtime. This code ends up being something like
73
- # context_type.&.constantize&.shard_category || :primary
74
- "read_attribute(:#{reflection.foreign_type})&.constantize&.shard_category || :primary"
70
+ # context_type.&.constantize&.connection_classes
71
+ "read_attribute(:#{reflection.foreign_type})&.constantize&.connection_classes"
75
72
  else
76
73
  # otherwise we can just return a symbol for the statically known type of the association
77
- reflection.klass.shard_category.inspect
74
+ "::#{reflection.klass.connection_classes.name}"
78
75
  end
79
76
  else
80
- shard_category.inspect
77
+ "::#{connection_classes.name}"
78
+ end
79
+ end
80
+
81
+ # just a dummy class with the proper interface that calls module_eval immediately
82
+ class CodeGenerator
83
+ def initialize(mod, line)
84
+ @module = mod
85
+ @line = line
86
+ end
87
+
88
+ def <<(string)
89
+ @module.module_eval(string, __FILE__, @line)
81
90
  end
82
91
  end
83
92
 
84
- def define_method_original_attribute(attr_name)
93
+ def define_method_original_attribute(attr_name, owner:)
85
94
  if sharded_column?(attr_name)
86
95
  reflection = reflection_for_integer_attribute(attr_name)
87
- if attr_name == "id" && ::Rails.version >= '5.1.2'
88
- return if self.method_defined?(:original_id)
89
- owner = self
90
- else
91
- owner = generated_attribute_methods
96
+ if attr_name == 'id'
97
+ return if method_defined?(:original_id)
98
+
99
+ owner = CodeGenerator.new(self, __LINE__ + 4)
92
100
  end
93
- owner.module_eval <<-RUBY, __FILE__, __LINE__ + 1
94
- # rename the original method to original_
101
+
102
+ owner << <<-RUBY
103
+ # rename the original method to original_*
95
104
  alias_method 'original_#{attr_name}', '#{attr_name}'
96
105
  # and replace with one that transposes the id
97
- def __temp__
98
- Shard.relative_id_for(original_#{attr_name}, shard, Shard.current(#{shard_category_code_for_reflection(reflection)}))
106
+ def #{attr_name}
107
+ ::Switchman::Shard.relative_id_for(original_#{attr_name}, shard, ::Switchman::Shard.current(#{connection_classes_code_for_reflection(reflection)}))
99
108
  end
100
- alias_method '#{attr_name}', :__temp__
101
- undef_method :__temp__
102
109
 
103
110
  alias_method 'original_#{attr_name}=', '#{attr_name}='
104
- def __temp__(new_value)
105
- self.original_#{attr_name} = Shard.relative_id_for(new_value, Shard.current(#{shard_category_code_for_reflection(reflection)}), shard)
111
+ def #{attr_name}=(new_value)
112
+ self.original_#{attr_name} = ::Switchman::Shard.relative_id_for(new_value, ::Switchman::Shard.current(#{connection_classes_code_for_reflection(reflection)}), shard)
106
113
  end
107
- alias_method '#{attr_name}=', :__temp__
108
- undef_method :__temp__
109
114
  RUBY
110
115
  else
111
- define_method_unsharded_column(attr_name, 'global')
116
+ define_method_unsharded_column(attr_name, 'global', owner)
112
117
  end
113
118
  end
114
119
 
115
- def define_method_unsharded_column(attr_name, prefix)
120
+ def define_method_unsharded_column(attr_name, prefix, owner)
116
121
  return if columns_hash["#{prefix}_#{attr_name}"]
117
- generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
118
- def __temp__
119
- raise NoMethodError, "undefined method `#{prefix}_#{attr_name}'; are you missing an association?"
120
- end
121
- alias_method '#{prefix}_#{attr_name}', :__temp__
122
- undef_method :__temp__
122
+
123
+ owner << <<-RUBY
124
+ def #{prefix}_#{attr_name}
125
+ raise NoMethodError, "undefined method `#{prefix}_#{attr_name}'; are you missing an association?"
126
+ end
123
127
  RUBY
124
128
  end
125
129
  end
126
130
 
127
131
  def self.included(klass)
128
- klass.extend(ClassMethods)
129
- klass.attribute_method_prefix "global_", "local_", "original_"
132
+ klass.singleton_class.include(ClassMethods)
133
+ klass.attribute_method_prefix 'global_', 'local_', 'original_'
130
134
  end
131
135
 
132
136
  # ensure that we're using the sharded attribute method
133
137
  # and not the silly one in AR::AttributeMethods::PrimaryKey
134
138
  def id
139
+ return super if is_a?(Shard)
140
+
135
141
  self.class.define_attribute_methods
136
142
  super
137
143
  end