xbar 0.0.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. data/Appraisals +25 -0
  2. data/README.mkdn +215 -0
  3. data/Rakefile +337 -1
  4. data/examples/README +5 -0
  5. data/examples/config/simple.json +22 -0
  6. data/examples/example1.rb +34 -0
  7. data/examples/migrations/1_create_users.rb +10 -0
  8. data/examples/setup.rb +43 -0
  9. data/gemfiles/rails3.gemfile +8 -0
  10. data/gemfiles/rails3.gemfile.lock +74 -0
  11. data/gemfiles/rails31.gemfile +8 -0
  12. data/gemfiles/rails31.gemfile.lock +83 -0
  13. data/gemfiles/rails32.gemfile +7 -0
  14. data/gemfiles/rails32.gemfile.lock +117 -0
  15. data/gemfiles/rails4.gemfile +9 -0
  16. data/gemfiles/rails4.gemfile.lock +134 -0
  17. data/lib/migrations/1_create_usage_statistics.rb +23 -0
  18. data/lib/xbar/association.rb +49 -0
  19. data/lib/xbar/association_collection.rb +69 -0
  20. data/lib/xbar/colors.rb +32 -0
  21. data/lib/xbar/has_and_belongs_to_many_association.rb +17 -0
  22. data/lib/xbar/logger.rb +14 -0
  23. data/lib/xbar/mapper.rb +304 -0
  24. data/lib/xbar/migration.rb +76 -0
  25. data/lib/xbar/model.rb +165 -0
  26. data/lib/xbar/proxy.rb +249 -0
  27. data/lib/xbar/rails2/association.rb +133 -0
  28. data/lib/xbar/rails2/persistence.rb +39 -0
  29. data/lib/xbar/rails3/arel.rb +13 -0
  30. data/lib/xbar/rails3/association.rb +112 -0
  31. data/lib/xbar/rails3/persistence.rb +37 -0
  32. data/lib/xbar/rails3.1/singular_association.rb +34 -0
  33. data/lib/xbar/scope_proxy.rb +55 -0
  34. data/lib/xbar/shard.rb +95 -0
  35. data/lib/xbar/version.rb +2 -2
  36. data/lib/xbar.rb +121 -2
  37. data/run +27 -0
  38. data/spec/config/acme.json +53 -0
  39. data/spec/config/connection.rb +2 -0
  40. data/spec/config/default.json +160 -0
  41. data/spec/config/duplicate_shard.json +21 -0
  42. data/spec/config/missing_key.json +20 -0
  43. data/spec/config/new_shards.json +29 -0
  44. data/spec/config/no_master_shard.json +19 -0
  45. data/spec/config/not_entire_sharded.json +23 -0
  46. data/spec/config/octopus.json +27 -0
  47. data/spec/config/octopus_rails.json +25 -0
  48. data/spec/config/production_fully_replicated.json +21 -0
  49. data/spec/config/production_raise_error.json +17 -0
  50. data/spec/config/simple.json +22 -0
  51. data/spec/config/single_adapter.json +20 -0
  52. data/spec/console.rb +15 -0
  53. data/spec/migrations/10_create_users_using_replication.rb +12 -0
  54. data/spec/migrations/11_add_field_in_all_slaves.rb +11 -0
  55. data/spec/migrations/12_create_users_using_block.rb +23 -0
  56. data/spec/migrations/13_create_users_using_block_and_using.rb +15 -0
  57. data/spec/migrations/1_create_users_on_master.rb +9 -0
  58. data/spec/migrations/2_create_users_on_canada.rb +11 -0
  59. data/spec/migrations/3_create_users_on_both_shards.rb +11 -0
  60. data/spec/migrations/4_create_users_on_shards_of_a_group.rb +11 -0
  61. data/spec/migrations/5_create_users_on_multiples_groups.rb +11 -0
  62. data/spec/migrations/6_raise_exception_with_invalid_shard_name.rb +11 -0
  63. data/spec/migrations/7_raise_exception_with_invalid_multiple_shard_names.rb +11 -0
  64. data/spec/migrations/8_raise_exception_with_invalid_group_name.rb +11 -0
  65. data/spec/migrations/9_raise_exception_with_multiple_invalid_group_names.rb +11 -0
  66. data/spec/spec_helper.rb +25 -0
  67. data/spec/support/database_models.rb +78 -0
  68. data/spec/support/xbar_helper.rb +42 -0
  69. data/spec/xbar/association_spec.rb +660 -0
  70. data/spec/xbar/controller_spec.rb +40 -0
  71. data/spec/xbar/logger_spec.rb +22 -0
  72. data/spec/xbar/mapper_spec.rb +283 -0
  73. data/spec/xbar/migration_spec.rb +110 -0
  74. data/spec/xbar/model_spec.rb +434 -0
  75. data/spec/xbar/proxy_spec.rb +124 -0
  76. data/spec/xbar/replication_spec.rb +94 -0
  77. data/spec/xbar/scope_proxy_spec.rb +22 -0
  78. data/spec/xbar/shard_spec.rb +36 -0
  79. data/xbar.gemspec +13 -3
  80. metadata +231 -10
data/lib/xbar/proxy.rb ADDED
@@ -0,0 +1,249 @@
1
+ require "set"
2
+
3
+ # For debugging.
4
+ require File.expand_path(File.join(File.dirname(__FILE__), 'colors'))
5
+
6
+ class UsageStatistics < ActiveRecord::Base
7
+ end
8
+
9
+ class XBar::Proxy
10
+
11
+ include XBar::Mapper
12
+ include Colors
13
+
14
+ # No setter method.
15
+ attr_reader :last_current_shard
16
+
17
+ # Setters for these are written by hand below.
18
+ attr_reader :current_model, :current_shard
19
+
20
+ attr_reader :shard_list
21
+
22
+ def initialize
23
+ puts "Initializing new proxy."
24
+ register
25
+ reset_shards
26
+ clean_proxy
27
+ end
28
+
29
+ def reset_proxy
30
+ @current_shard = :master
31
+ puts ">>> reset_proxy: #{current_shard}" if XBar.debug
32
+ clear_block_scope
33
+ reset_shards
34
+ end
35
+
36
+ def clean_proxy
37
+ if XBar.debug
38
+ puts "Proxy##{BLUE_TEXT}clean_proxy=#{RESET_COLORS}: " +
39
+ "current shard = #{@current_shard}"
40
+ end
41
+ @current_shard = :master
42
+ clear_block_scope
43
+ end
44
+
45
+ def current_shard=(shard_name)
46
+ # shard name might actually be a list of shard names in the
47
+ # case of migration. Make it an array in all cases to check it.
48
+ Array(shard_name).each do |s|
49
+ if !@shard_list.member? s
50
+ puts "<<< #{@shard_list.keys} >>>"
51
+ puts "<<< #{XBar::Mapper.shards.keys} >>>"
52
+ raise "Nonexistent Shard Name: #{s}"
53
+ end
54
+ end
55
+ if XBar.debug
56
+ puts "Proxy##{BLUE_TEXT}current_shard=#{RESET_COLORS}: " +
57
+ "previous_shard = #{@current_shard}, new_shard = #{shard_name}"
58
+ end
59
+ @current_shard = shard_name
60
+ end
61
+
62
+ def select_shard
63
+ if current_shard.kind_of? Array
64
+ shard = current_shard.first
65
+ if current_shard.size != 1
66
+ puts "WARNING: selecting only first shard from array"
67
+ end
68
+ else
69
+ shard = current_shard
70
+ end
71
+ unless @shard_list[shard]
72
+ puts "Shard not found: current_shard = #{shard}, @shard_list = #{@shard_list.keys}"
73
+ end
74
+ @shard_list[shard]
75
+ end
76
+
77
+ def current_model=(model)
78
+ # The way that this function is used internally, kind_of?(ActiveRecord::Base)
79
+ # is always false -- we're always passing the class.cur
80
+ @current_model = model.kind_of?(ActiveRecord::Base) ? model.class : model
81
+ end
82
+
83
+ def enter_block_scope
84
+ @old_in_block = @in_block
85
+ @depth += 1
86
+ @in_block = true
87
+ end
88
+
89
+ def leave_block_scope
90
+ @in_block = @old_in_block
91
+ @old_in_block = false
92
+ @depth -= 1
93
+ end
94
+
95
+ def in_block_scope?
96
+ @in_block
97
+ end
98
+
99
+ def clear_block_scope
100
+ @in_block = @old_in_block = false
101
+ @depth = 0
102
+ end
103
+
104
+ def should_clean_table_name?
105
+ adapters.size > 1
106
+ end
107
+
108
+ def verify_connection
109
+ XBar::Mapper.options[:verify_connection]
110
+ end
111
+
112
+ def run_queries_on_shard(shard_name, use_scope = true)
113
+ older_shard = current_shard
114
+ enter_block_scope if use_scope
115
+ self.current_shard = shard_name
116
+ if XBar.debug
117
+ puts "Proxy##{BLUE_TEXT}run_queries_on_shard#{RESET_COLORS}: " +
118
+ "current shard = #{current_shard}, use_scope = #{use_scope}, " +
119
+ "previous shard = #{older_shard}"
120
+ end
121
+ result = yield
122
+ ensure
123
+ if XBar.debug
124
+ puts "Proxy##{BLUE_TEXT}run_queries_on_shard#{RESET_COLORS}: " +
125
+ "restoring previous shard = #{older_shard}"
126
+ end
127
+ leave_block_scope if use_scope
128
+ self.current_shard = older_shard
129
+ end
130
+
131
+ def send_queries_to_multiple_shards(shard_names, &block)
132
+ shard_names = Array(shard_names)
133
+ shard_names.each do |name|
134
+ run_queries_on_shard(name, &block)
135
+ end
136
+ end
137
+
138
+ def check_schema_migrations(shard_name)
139
+ @shard_list[shard_name].check_schema_migrations
140
+ end
141
+
142
+ def transaction(options = {}, &block)
143
+ select_shard.transaction(options, &block)
144
+ end
145
+
146
+ def schema_cache
147
+ select_shard.schema_cache
148
+ end
149
+
150
+ def quote_table_name(table_name)
151
+ select_shard.quote_table_name(table_name)
152
+ end
153
+
154
+ def method_missing(method, *args, &block)
155
+ if XBar.debug
156
+ puts("\nProxy##{BLUE_TEXT}method_missing#{RESET_COLORS}: " +
157
+ "method = #{RED_TEXT}#{method}#{RESET_COLORS}, " +
158
+ "current_shard=#{current_shard}, " +
159
+ "in_block_scope=#{in_block_scope?}")
160
+ end
161
+
162
+ if method.to_s =~ /insert|select|execute/ && !in_block_scope? # should clean connection
163
+ shard = @last_current_shard = current_shard
164
+ clean_proxy
165
+ @shard_list[shard].run_queries(method, *args, &block)
166
+ else
167
+ select_shard.run_queries(method, *args, &block)
168
+ end
169
+ end
170
+
171
+ def respond_to?(method, include_private = false)
172
+ super || current_shard.respond_to?(method, include_private)
173
+ end
174
+
175
+ def connection_pool
176
+ cp = shards[current_shard].first
177
+ cp.automatic_reconnect = true if XBar.rails31?
178
+ cp
179
+ end
180
+
181
+ def enter_statistics(shard_name, config, method)
182
+
183
+ return unless XBar.collect_stats?
184
+
185
+ saved_current_model = current_model
186
+ if UsageStatistics.connection.kind_of?(XBar::Proxy)
187
+ connection_pool = UsageStatistics.connection.shards[:master][0]
188
+ connection_pool.automatic_reconnect = true
189
+ connection = connection_pool.connection
190
+
191
+ return unless connection.table_exists? :usage_statistics
192
+
193
+ # We have an AbstractAdapter. We must insert statistics using this
194
+ # adapter, not the Proxy, because otherwise the insertion triggers
195
+ # 'enter_statistics' again and we would be in an infinite loop.
196
+
197
+ =begin
198
+ params = {
199
+ shard_name: shard_name,
200
+ method: method.to_s,
201
+ adapter: config[:adapter],
202
+ username: config[:username],
203
+ thread_id: Thread.current.object_id.to_s,
204
+ port: (config[:port] || "default"),
205
+ host: config[:host],
206
+ database_name: config[:database]}
207
+ insert(connection, params) -- alternatively
208
+ =end
209
+
210
+ sql = "INSERT INTO usage_statistics " +
211
+ "(shard_name, method, adapter, username, thread_id, port, host, database_name) " +
212
+ "VALUES (\'#{shard_name}\', \'#{method.to_s}\', \'#{config[:adapter]}\', " +
213
+ "\'#{config[:username]}\', \'#{Thread.current.object_id.to_s}\', " +
214
+ "#{config[:port] || "default"}, \'#{config[:host]}\', \'#{config[:database]}\')"
215
+ connection.execute(sql)
216
+
217
+ self.current_model = saved_current_model
218
+ end
219
+ end
220
+
221
+ private
222
+
223
+ def reset_shards
224
+ @shard_list = HashWithIndifferentAccess.new
225
+
226
+ # The proxy can have a number of shards, each shard can have a number
227
+ # of master/slave replicas. In the new refactoring, each shard is truly
228
+ # a shard, not a member of a replica set. The shards are available because
229
+ # the Mapper module is included.
230
+ shards.each do |shard_name, replicas|
231
+ master = replicas.first # car
232
+ slaves = replicas[1..-1] # cdr, could be empty array
233
+ @shard_list[shard_name] = XBar::Shard.new(self, shard_name, master, slaves)
234
+ end
235
+
236
+ end
237
+
238
+ def insert_some_sql(conn, data)
239
+ binds = data.map { |name, value|
240
+ [conn.columns('usage_statistics').find { |x| x.name == name.to_s }, value]
241
+ }
242
+ columns = binds.map(&:first).map(&:name)
243
+ puts "binds = #{binds.to_yaml}"
244
+ sql = "INSERT INTO usage_statistics (#{columns.join(", ")})
245
+ VALUES (#{(['?'] * columns.length).join(', ')})"
246
+ conn.exec_insert(sql, 'SQL', binds)
247
+ end
248
+
249
+ end
@@ -0,0 +1,133 @@
1
+ module XBar
2
+ module Rails2
3
+ module Association
4
+ def association_accessor_methods(reflection, association_proxy_class)
5
+ define_method(reflection.name) do |*params|
6
+ force_reload = params.first unless params.empty?
7
+ reload_connection()
8
+ association = association_instance_get(reflection.name)
9
+
10
+ if association.nil? || force_reload
11
+ association = association_proxy_class.new(self, reflection)
12
+ retval = force_reload ? reflection.klass.uncached { association.reload } : association.reload
13
+ if retval.nil? and association_proxy_class == ActiveRecord::Associations::BelongsToAssociation
14
+ association_instance_set(reflection.name, nil)
15
+ return nil
16
+ end
17
+ association_instance_set(reflection.name, association)
18
+ end
19
+
20
+ association.target.nil? ? nil : association
21
+ end
22
+
23
+ define_method("loaded_#{reflection.name}?") do
24
+ reload_connection()
25
+ association = association_instance_get(reflection.name)
26
+ association && association.loaded?
27
+ end
28
+
29
+ define_method("#{reflection.name}=") do |new_value|
30
+ association = association_instance_get(reflection.name)
31
+ reload_connection()
32
+ if association.nil? || association.target != new_value
33
+ association = association_proxy_class.new(self, reflection)
34
+ end
35
+
36
+ if association_proxy_class == ActiveRecord::Associations::HasOneThroughAssociation
37
+ association.create_through_record(new_value)
38
+ if new_record?
39
+ association_instance_set(reflection.name, new_value.nil? ? nil : association)
40
+ else
41
+ self.send(reflection.name, new_value)
42
+ end
43
+ else
44
+ association.replace(new_value)
45
+ association_instance_set(reflection.name, new_value.nil? ? nil : association)
46
+ end
47
+ end
48
+
49
+ define_method("set_#{reflection.name}_target") do |target|
50
+ reload_connection()
51
+ return if target.nil? and association_proxy_class == ActiveRecord::Associations::BelongsToAssociation
52
+ association = association_proxy_class.new(self, reflection)
53
+ association.target = target
54
+ association_instance_set(reflection.name, association)
55
+ end
56
+ end
57
+
58
+ def collection_reader_method(reflection, association_proxy_class)
59
+ define_method(reflection.name) do |*params|
60
+ force_reload = params.first unless params.empty?
61
+ reload_connection()
62
+ association = association_instance_get(reflection.name)
63
+
64
+ unless association
65
+ association = association_proxy_class.new(self, reflection)
66
+ association_instance_set(reflection.name, association)
67
+ end
68
+
69
+ reflection.klass.uncached { association.reload } if force_reload
70
+
71
+ association
72
+ end
73
+
74
+ define_method("#{reflection.name.to_s.singularize}_ids") do
75
+ reload_connection()
76
+ if send(reflection.name).loaded? || reflection.options[:finder_sql]
77
+ send(reflection.name).map(&:id)
78
+ else
79
+ send(reflection.name).all(:select => "#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").map(&:id)
80
+ end
81
+ end
82
+ end
83
+
84
+ def collection_accessor_methods(reflection, association_proxy_class, writer = true)
85
+ collection_reader_method(reflection, association_proxy_class)
86
+
87
+ if writer
88
+ define_method("#{reflection.name}=") do |new_value|
89
+ reload_connection()
90
+ # Loads proxy class instance (defined in collection_reader_method) if not already loaded
91
+ association = send(reflection.name)
92
+ association.replace(new_value)
93
+ association
94
+ end
95
+
96
+ define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
97
+ reload_connection()
98
+ ids = (new_value || []).reject { |nid| nid.blank? }.map(&:to_i)
99
+ send("#{reflection.name}=", reflection.klass.find(ids).index_by(&:id).values_at(*ids))
100
+ end
101
+ end
102
+ end
103
+
104
+ def association_constructor_method(constructor, reflection, association_proxy_class)
105
+ define_method("#{constructor}_#{reflection.name}") do |*params|
106
+ reload_connection()
107
+ attributees = params.first unless params.empty?
108
+ replace_existing = params[1].nil? ? true : params[1]
109
+ association = association_instance_get(reflection.name)
110
+
111
+ unless association
112
+ association = association_proxy_class.new(self, reflection)
113
+ association_instance_set(reflection.name, association)
114
+ end
115
+
116
+ if association_proxy_class == ActiveRecord::Associations::HasOneAssociation
117
+ ret_val = association.send(constructor, attributees, replace_existing)
118
+ else
119
+ ret_val = association.send(constructor, attributees)
120
+ end
121
+
122
+ if should_set_current_shard?
123
+ ret_val.current_shard = self.current_shard
124
+ end
125
+
126
+ return ret_val
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
132
+
133
+ ActiveRecord::Base.extend(XBar::Rails2::Association)
@@ -0,0 +1,39 @@
1
+ module XBar
2
+ module Rails2
3
+ module Persistence
4
+ def self.included(base)
5
+ base.instance_eval do
6
+ alias_method_chain :destroy, :xbar
7
+ alias_method_chain :delete, :xbar
8
+ alias_method_chain :reload, :xbar
9
+ end
10
+ end
11
+
12
+ def delete_with_xbar()
13
+ if should_set_current_shard?
14
+ XBar.using(self.current_shard) { delete_without_xbar() }
15
+ else
16
+ delete_without_xbar()
17
+ end
18
+ end
19
+
20
+ def destroy_with_xbar()
21
+ if should_set_current_shard?
22
+ XBar.using(self.current_shard) { destroy_without_xbar() }
23
+ else
24
+ destroy_without_xbar()
25
+ end
26
+ end
27
+
28
+ def reload_with_xbar(options = nil)
29
+ if should_set_current_shard?
30
+ XBar.using(self.current_shard) { reload_without_xbar(options) }
31
+ else
32
+ reload_without_xbar(options)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ ActiveRecord::Base.send(:include, XBar::Rails2::Persistence)
@@ -0,0 +1,13 @@
1
+ class Arel::Visitors::ToSql
2
+ def quote value, column = nil
3
+ ActiveRecord::Base.connection.quote value, column
4
+ end
5
+
6
+ def quote_table_name name
7
+ ActiveRecord::Base.connection.quote_table_name(name)
8
+ end
9
+
10
+ def quote_column_name name
11
+ Arel::Nodes::SqlLiteral === name ? name : ActiveRecord::Base.connection.quote_column_name(name)
12
+ end
13
+ end
@@ -0,0 +1,112 @@
1
+ module XBar
2
+ module Rails3
3
+ module Association
4
+ def collection_reader_method(reflection, association_proxy_class)
5
+ define_method(reflection.name) do |*params|
6
+ force_reload = params.first unless params.empty?
7
+ reload_connection()
8
+
9
+ association = association_instance_get(reflection.name)
10
+
11
+ unless association
12
+ association = association_proxy_class.new(self, reflection)
13
+ association_instance_set(reflection.name, association)
14
+ end
15
+
16
+ reflection.klass.uncached { association.reload } if force_reload
17
+
18
+ association
19
+ end
20
+
21
+ define_method("#{reflection.name.to_s.singularize}_ids") do
22
+ reload_connection()
23
+ if send(reflection.name).loaded? || reflection.options[:finder_sql]
24
+ send(reflection.name).map(&:id)
25
+ else
26
+ if reflection.through_reflection && reflection.source_reflection.belongs_to?
27
+ through = reflection.through_reflection
28
+ primary_key = reflection.source_reflection.primary_key_name
29
+ send(through.name).select("DISTINCT #{through.quoted_table_name}.#{primary_key}").map!(&:"#{primary_key}")
30
+ else
31
+ send(reflection.name).select("#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").except(:includes).map!(&:id)
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ def association_constructor_method(constructor, reflection, association_proxy_class)
38
+ define_method("#{constructor}_#{reflection.name}") do |*params|
39
+ reload_connection()
40
+ attributees = params.first unless params.empty?
41
+ replace_existing = params[1].nil? ? true : params[1]
42
+ association = association_instance_get(reflection.name)
43
+
44
+ unless association
45
+ association = association_proxy_class.new(self, reflection)
46
+ association_instance_set(reflection.name, association)
47
+ end
48
+
49
+ if association_proxy_class == ActiveRecord::Associations::HasOneAssociation
50
+ return_val = association.send(constructor, attributees, replace_existing)
51
+ else
52
+ return_val = association.send(constructor, attributees)
53
+ end
54
+
55
+ if should_set_current_shard?
56
+ return_val.current_shard = self.current_shard
57
+ end
58
+
59
+ return_val
60
+ end
61
+ end
62
+
63
+ def association_accessor_methods(reflection, association_proxy_class)
64
+ define_method(reflection.name) do |*params|
65
+ reload_connection()
66
+ force_reload = params.first unless params.empty?
67
+ association = association_instance_get(reflection.name)
68
+
69
+ if association.nil? || force_reload
70
+ association = association_proxy_class.new(self, reflection)
71
+ retval = force_reload ? reflection.klass.uncached { association.reload } : association.reload
72
+ if retval.nil? and association_proxy_class == ActiveRecord::Associations::BelongsToAssociation
73
+ association_instance_set(reflection.name, nil)
74
+ return nil
75
+ end
76
+ association_instance_set(reflection.name, association)
77
+ end
78
+
79
+ association.target.nil? ? nil : association
80
+ end
81
+
82
+ define_method("loaded_#{reflection.name}?") do
83
+ reload_connection()
84
+ association = association_instance_get(reflection.name)
85
+ association && association.loaded?
86
+ end
87
+
88
+ define_method("#{reflection.name}=") do |new_value|
89
+ reload_connection()
90
+ association = association_instance_get(reflection.name)
91
+
92
+ if association.nil? || association.target != new_value
93
+ association = association_proxy_class.new(self, reflection)
94
+ end
95
+
96
+ association.replace(new_value)
97
+ association_instance_set(reflection.name, new_value.nil? ? nil : association)
98
+ end
99
+
100
+ define_method("set_#{reflection.name}_target") do |target|
101
+ return if target.nil? and association_proxy_class == ActiveRecord::Associations::BelongsToAssociation
102
+ reload_connection()
103
+ association = association_proxy_class.new(self, reflection)
104
+ association.target = target
105
+ association_instance_set(reflection.name, association)
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+
112
+ ActiveRecord::Base.extend(XBar::Rails3::Association)
@@ -0,0 +1,37 @@
1
+ module XBar
2
+ module Rails3
3
+ module Persistence
4
+ def update_attribute(name, value)
5
+ reload_connection()
6
+ super
7
+ end
8
+
9
+ def update_attributes(attributes)
10
+ reload_connection()
11
+ super
12
+ end
13
+
14
+ def update_attributes!(attributes)
15
+ reload_connection()
16
+ super
17
+ end
18
+
19
+ def reload(options = nil)
20
+ reload_connection()
21
+ super(options)
22
+ end
23
+
24
+ def delete
25
+ reload_connection()
26
+ super
27
+ end
28
+
29
+ def destroy
30
+ reload_connection()
31
+ super
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ ActiveRecord::Base.send(:include, XBar::Rails3::Persistence)
@@ -0,0 +1,34 @@
1
+ module XBar::SingularAssociation
2
+ def self.included(base)
3
+ base.instance_eval do
4
+ alias_method_chain :reader, :xbar
5
+ alias_method_chain :writer, :xbar
6
+ alias_method_chain :create, :xbar
7
+ alias_method_chain :create!, :xbar
8
+ alias_method_chain :build, :xbar
9
+ end
10
+ end
11
+
12
+ def reader_with_xbar(*args)
13
+ owner.reload_connection_safe { reader_without_xbar(*args) }
14
+ end
15
+
16
+ def writer_with_xbar(*args)
17
+ owner.reload_connection_safe { writer_without_xbar(*args) }
18
+ end
19
+
20
+ def create_with_xbar(*args)
21
+ owner.reload_connection_safe { create_without_xbar(*args) }
22
+ end
23
+
24
+ def create_with_xbar!(*args)
25
+ owner.reload_connection_safe { create_without_xbar!(*args) }
26
+ end
27
+
28
+ def build_with_xbar(*args)
29
+ owner.reload_connection_safe { build_without_xbar(*args) }
30
+ end
31
+
32
+ end
33
+
34
+ ActiveRecord::Associations::SingularAssociation.send(:include, XBar::SingularAssociation)
@@ -0,0 +1,55 @@
1
+ class XBar::ScopeProxy
2
+ attr_accessor :shard, :klass
3
+
4
+ def initialize(shard, klass)
5
+ @shard = shard
6
+ @klass = klass
7
+ end
8
+
9
+ def using(shard)
10
+ unless @klass.connection.shards[shard]
11
+ raise "Nonexistent Shard Name: #{shard}"
12
+ end
13
+ @shard = shard
14
+ return self
15
+ end
16
+
17
+ # Transaction Method send all queries to a specified shard.
18
+ def transaction(options = {}, &block)
19
+ @klass.connection.run_queries_on_shard(@shard) do
20
+ @klass = @klass.connection.transaction(options, &block)
21
+ end
22
+ end
23
+
24
+ def connection
25
+ @klass.connection.current_shard = @shard
26
+ @klass.connection
27
+ end
28
+
29
+ def method_missing(method, *args, &block)
30
+
31
+ @klass.connection.current_model = @klass # XXX
32
+ if XBar.debug
33
+ # puts "Connection proxy klass proxy is #{@klass.connection.class.name}"
34
+ # puts "Scope proxy assigned current_model #{@klass.name}"
35
+ # puts "Block given is #{block_given?}"
36
+ end
37
+ @klass.connection.run_queries_on_shard(@shard, true) do
38
+ #puts "ScopeProxy, method missing, sending query to shard: #{@shard}, klass is #{@klass}"
39
+ #puts "ScropeProxy, has response: method = #{method.to_s}, #{@klass.respond_to? method}"
40
+ # puts Thread.current.backtrace
41
+ #puts "ScopeProxy, connection class = #{@klass.connection.class.name}"
42
+
43
+ @klass = @klass.send(method, *args, &block)
44
+ #puts "After invocation..."
45
+ end
46
+
47
+ return @klass if @klass.is_a?(ActiveRecord::Base) or @klass.is_a?(Array) or @klass.is_a?(Fixnum) or @klass.nil?
48
+ return self
49
+ end
50
+
51
+ def ==(other)
52
+ @shard == other.shard
53
+ @klass == other.klass
54
+ end
55
+ end