xbar 0.0.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Appraisals +25 -0
- data/README.mkdn +215 -0
- data/Rakefile +337 -1
- data/examples/README +5 -0
- data/examples/config/simple.json +22 -0
- data/examples/example1.rb +34 -0
- data/examples/migrations/1_create_users.rb +10 -0
- data/examples/setup.rb +43 -0
- data/gemfiles/rails3.gemfile +8 -0
- data/gemfiles/rails3.gemfile.lock +74 -0
- data/gemfiles/rails31.gemfile +8 -0
- data/gemfiles/rails31.gemfile.lock +83 -0
- data/gemfiles/rails32.gemfile +7 -0
- data/gemfiles/rails32.gemfile.lock +117 -0
- data/gemfiles/rails4.gemfile +9 -0
- data/gemfiles/rails4.gemfile.lock +134 -0
- data/lib/migrations/1_create_usage_statistics.rb +23 -0
- data/lib/xbar/association.rb +49 -0
- data/lib/xbar/association_collection.rb +69 -0
- data/lib/xbar/colors.rb +32 -0
- data/lib/xbar/has_and_belongs_to_many_association.rb +17 -0
- data/lib/xbar/logger.rb +14 -0
- data/lib/xbar/mapper.rb +304 -0
- data/lib/xbar/migration.rb +76 -0
- data/lib/xbar/model.rb +165 -0
- data/lib/xbar/proxy.rb +249 -0
- data/lib/xbar/rails2/association.rb +133 -0
- data/lib/xbar/rails2/persistence.rb +39 -0
- data/lib/xbar/rails3/arel.rb +13 -0
- data/lib/xbar/rails3/association.rb +112 -0
- data/lib/xbar/rails3/persistence.rb +37 -0
- data/lib/xbar/rails3.1/singular_association.rb +34 -0
- data/lib/xbar/scope_proxy.rb +55 -0
- data/lib/xbar/shard.rb +95 -0
- data/lib/xbar/version.rb +2 -2
- data/lib/xbar.rb +121 -2
- data/run +27 -0
- data/spec/config/acme.json +53 -0
- data/spec/config/connection.rb +2 -0
- data/spec/config/default.json +160 -0
- data/spec/config/duplicate_shard.json +21 -0
- data/spec/config/missing_key.json +20 -0
- data/spec/config/new_shards.json +29 -0
- data/spec/config/no_master_shard.json +19 -0
- data/spec/config/not_entire_sharded.json +23 -0
- data/spec/config/octopus.json +27 -0
- data/spec/config/octopus_rails.json +25 -0
- data/spec/config/production_fully_replicated.json +21 -0
- data/spec/config/production_raise_error.json +17 -0
- data/spec/config/simple.json +22 -0
- data/spec/config/single_adapter.json +20 -0
- data/spec/console.rb +15 -0
- data/spec/migrations/10_create_users_using_replication.rb +12 -0
- data/spec/migrations/11_add_field_in_all_slaves.rb +11 -0
- data/spec/migrations/12_create_users_using_block.rb +23 -0
- data/spec/migrations/13_create_users_using_block_and_using.rb +15 -0
- data/spec/migrations/1_create_users_on_master.rb +9 -0
- data/spec/migrations/2_create_users_on_canada.rb +11 -0
- data/spec/migrations/3_create_users_on_both_shards.rb +11 -0
- data/spec/migrations/4_create_users_on_shards_of_a_group.rb +11 -0
- data/spec/migrations/5_create_users_on_multiples_groups.rb +11 -0
- data/spec/migrations/6_raise_exception_with_invalid_shard_name.rb +11 -0
- data/spec/migrations/7_raise_exception_with_invalid_multiple_shard_names.rb +11 -0
- data/spec/migrations/8_raise_exception_with_invalid_group_name.rb +11 -0
- data/spec/migrations/9_raise_exception_with_multiple_invalid_group_names.rb +11 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/support/database_models.rb +78 -0
- data/spec/support/xbar_helper.rb +42 -0
- data/spec/xbar/association_spec.rb +660 -0
- data/spec/xbar/controller_spec.rb +40 -0
- data/spec/xbar/logger_spec.rb +22 -0
- data/spec/xbar/mapper_spec.rb +283 -0
- data/spec/xbar/migration_spec.rb +110 -0
- data/spec/xbar/model_spec.rb +434 -0
- data/spec/xbar/proxy_spec.rb +124 -0
- data/spec/xbar/replication_spec.rb +94 -0
- data/spec/xbar/scope_proxy_spec.rb +22 -0
- data/spec/xbar/shard_spec.rb +36 -0
- data/xbar.gemspec +13 -3
- 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
|