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