xbar 0.0.1 → 0.4.0

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.
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