data_fabric 1.1.0 → 1.2.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/CHANGELOG +4 -0
- data/Manifest +73 -0
- data/README.rdoc +1 -1
- data/Rakefile +34 -8
- data/data_fabric.gemspec +7 -7
- data/example/app/models/figment.rb +1 -1
- data/example/db/development.sqlite3 +0 -0
- data/example/db/s0_development.sqlite3 +0 -0
- data/example/db/s0_test.sqlite3 +0 -0
- data/example/db/s1_development.sqlite3 +0 -0
- data/example/db/s1_test.sqlite3 +0 -0
- data/example/db/test.sqlite3 +0 -0
- data/example/vendor/plugins/data_fabric/init.rb +1 -0
- data/example/vendor/plugins/data_fabric/lib/data_fabric.rb +106 -0
- data/example/vendor/plugins/data_fabric/lib/data_fabric/ar20.rb +135 -0
- data/example/vendor/plugins/data_fabric/lib/data_fabric/ar22.rb +172 -0
- data/example/vendor/plugins/data_fabric/lib/data_fabric/version.rb +5 -0
- data/example22/Rakefile +58 -0
- data/example22/app/controllers/accounts_controller.rb +22 -0
- data/example22/app/controllers/application.rb +39 -0
- data/example22/app/controllers/figments_controller.rb +8 -0
- data/example22/app/helpers/application_helper.rb +3 -0
- data/example22/app/models/account.rb +3 -0
- data/example22/app/models/figment.rb +4 -0
- data/example22/app/views/accounts/index.html.erb +47 -0
- data/example22/app/views/layouts/application.html.erb +8 -0
- data/example22/config/boot.rb +109 -0
- data/example22/config/database.yml +21 -0
- data/example22/config/environment.rb +76 -0
- data/example22/config/environments/development.rb +17 -0
- data/example22/config/environments/production.rb +24 -0
- data/example22/config/environments/test.rb +22 -0
- data/example22/config/initializers/inflections.rb +10 -0
- data/example22/config/initializers/mime_types.rb +5 -0
- data/example22/config/initializers/new_rails_defaults.rb +17 -0
- data/example22/config/locales/en.yml +5 -0
- data/example22/config/routes.rb +46 -0
- data/example22/db/migrate/20080702154628_create_accounts.rb +14 -0
- data/example22/db/migrate/20080702154820_create_figments.rb +14 -0
- data/example22/public/404.html +30 -0
- data/example22/public/422.html +30 -0
- data/example22/public/500.html +33 -0
- data/example22/public/dispatch.cgi +10 -0
- data/example22/public/dispatch.fcgi +24 -0
- data/example22/public/dispatch.rb +10 -0
- data/example22/public/favicon.ico +0 -0
- data/example22/public/images/rails.png +0 -0
- data/example22/public/index.html +274 -0
- data/example22/public/javascripts/application.js +2 -0
- data/example22/public/javascripts/controls.js +963 -0
- data/example22/public/javascripts/dragdrop.js +973 -0
- data/example22/public/javascripts/effects.js +1128 -0
- data/example22/public/javascripts/prototype.js +4320 -0
- data/example22/public/robots.txt +5 -0
- data/example22/script/about +4 -0
- data/example22/script/console +3 -0
- data/example22/script/dbconsole +3 -0
- data/example22/script/destroy +3 -0
- data/example22/script/generate +3 -0
- data/example22/script/performance/benchmarker +3 -0
- data/example22/script/performance/profiler +3 -0
- data/example22/script/performance/request +3 -0
- data/example22/script/plugin +3 -0
- data/example22/script/process/inspector +3 -0
- data/example22/script/process/reaper +3 -0
- data/example22/script/process/spawner +3 -0
- data/example22/script/runner +3 -0
- data/example22/script/server +3 -0
- data/example22/test/fixtures/accounts.yml +7 -0
- data/example22/test/functional/accounts_controller_test.rb +12 -0
- data/example22/test/integration/account_figments_test.rb +97 -0
- data/example22/test/performance/browsing_test.rb +9 -0
- data/example22/test/test_helper.rb +38 -0
- data/lib/data_fabric.rb +7 -132
- data/lib/data_fabric/ar20.rb +133 -0
- data/lib/data_fabric/ar22.rb +172 -0
- data/lib/data_fabric/version.rb +1 -1
- data/test/connection_test.rb +6 -2
- data/test/database_test.rb +26 -2
- data/test/test_helper.rb +34 -28
- data/test/thread_test.rb +19 -11
- data/test/vr_austin_master.db +0 -0
- data/test/vr_austin_slave.db +0 -0
- data/test/vr_dallas_master.db +0 -0
- data/test/vr_dallas_slave.db +0 -0
- metadata +79 -5
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class AccountFigmentsTest < ActionController::IntegrationTest
|
4
|
+
|
5
|
+
def test_create_account_and_figments
|
6
|
+
conn0 = db_connection "shard_0_test"
|
7
|
+
conn1 = db_connection "shard_1_test"
|
8
|
+
conn0.clear 'figments'
|
9
|
+
conn1.clear 'figments'
|
10
|
+
assert_equal 0, conn0.count_for('figments')
|
11
|
+
assert_equal 0, conn1.count_for('figments')
|
12
|
+
|
13
|
+
new_session(0) do |user|
|
14
|
+
user.goes_home
|
15
|
+
mike = user.creates_account('mike', '0')
|
16
|
+
user.selects_account(mike)
|
17
|
+
before = mike.figments.size
|
18
|
+
user.creates_figment(14)
|
19
|
+
mike.figments.reload
|
20
|
+
assert_equal before + 1, mike.figments.size
|
21
|
+
assert_equal 14, mike.figments.first.value
|
22
|
+
end
|
23
|
+
|
24
|
+
# Bypass data_fabric and verify the figment went to shard 0.
|
25
|
+
assert_equal 1, conn0.count_for('figments')
|
26
|
+
assert_equal 0, conn1.count_for('figments')
|
27
|
+
|
28
|
+
new_session(1) do |user|
|
29
|
+
user.goes_home
|
30
|
+
bob = user.creates_account('bob', '1')
|
31
|
+
user.selects_account(bob)
|
32
|
+
before = bob.figments.size
|
33
|
+
user.creates_figment(66)
|
34
|
+
bob.figments.reload
|
35
|
+
assert_equal before + 1, bob.figments.size
|
36
|
+
assert_equal 66, bob.figments.first.value
|
37
|
+
end
|
38
|
+
|
39
|
+
# Bypass data_fabric and verify the figment went to shard 1.
|
40
|
+
assert_equal 1, conn0.count_for('figments')
|
41
|
+
assert_equal 1, conn1.count_for('figments')
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def db_connection(conf)
|
47
|
+
conn = ActiveRecord::Base.sqlite3_connection(HashWithIndifferentAccess.new(ActiveRecord::Base.configurations[conf]))
|
48
|
+
def conn.count_for(table)
|
49
|
+
Integer(execute("select count(*) as c from #{table}")[0]['c'])
|
50
|
+
end
|
51
|
+
def conn.clear(table)
|
52
|
+
execute("delete from #{table}")
|
53
|
+
end
|
54
|
+
conn
|
55
|
+
end
|
56
|
+
|
57
|
+
def new_session(shard)
|
58
|
+
open_session do |sess|
|
59
|
+
DataFabric.activate_shard :shard => shard do
|
60
|
+
sess.extend(Operations)
|
61
|
+
yield sess if block_given?
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
module Operations
|
67
|
+
def goes_home
|
68
|
+
get accounts_path
|
69
|
+
assert_response :success
|
70
|
+
end
|
71
|
+
|
72
|
+
def creates_account(name, shard)
|
73
|
+
post accounts_path, :acct => { :name => name, :shard => shard }
|
74
|
+
assert_response :redirect
|
75
|
+
follow_redirect!
|
76
|
+
assert_response :success
|
77
|
+
assert_template "accounts/index"
|
78
|
+
Account.find_by_name(name)
|
79
|
+
end
|
80
|
+
|
81
|
+
def selects_account(account)
|
82
|
+
get choose_account_path(account)
|
83
|
+
assert_response :redirect
|
84
|
+
follow_redirect!
|
85
|
+
assert_response :success
|
86
|
+
assert_template "accounts/index"
|
87
|
+
end
|
88
|
+
|
89
|
+
def creates_figment(value)
|
90
|
+
post figments_path, :figment => { :value => value }
|
91
|
+
assert_response :redirect
|
92
|
+
follow_redirect!
|
93
|
+
assert_response :success
|
94
|
+
assert_template "accounts/index"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
ENV["RAILS_ENV"] = "test"
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
|
3
|
+
require 'test_help'
|
4
|
+
|
5
|
+
class Test::Unit::TestCase
|
6
|
+
# Transactional fixtures accelerate your tests by wrapping each test method
|
7
|
+
# in a transaction that's rolled back on completion. This ensures that the
|
8
|
+
# test database remains unchanged so your fixtures don't have to be reloaded
|
9
|
+
# between every test method. Fewer database queries means faster tests.
|
10
|
+
#
|
11
|
+
# Read Mike Clark's excellent walkthrough at
|
12
|
+
# http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting
|
13
|
+
#
|
14
|
+
# Every Active Record database supports transactions except MyISAM tables
|
15
|
+
# in MySQL. Turn off transactional fixtures in this case; however, if you
|
16
|
+
# don't care one way or the other, switching from MyISAM to InnoDB tables
|
17
|
+
# is recommended.
|
18
|
+
#
|
19
|
+
# The only drawback to using transactional fixtures is when you actually
|
20
|
+
# need to test transactions. Since your test is bracketed by a transaction,
|
21
|
+
# any transactions started in your code will be automatically rolled back.
|
22
|
+
self.use_transactional_fixtures = true
|
23
|
+
|
24
|
+
# Instantiated fixtures are slow, but give you @david where otherwise you
|
25
|
+
# would need people(:david). If you don't want to migrate your existing
|
26
|
+
# test cases which use the @david style and don't mind the speed hit (each
|
27
|
+
# instantiated fixtures translates to a database query per test method),
|
28
|
+
# then set this back to true.
|
29
|
+
self.use_instantiated_fixtures = false
|
30
|
+
|
31
|
+
# Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
|
32
|
+
#
|
33
|
+
# Note: You'll currently still have to declare fixtures explicitly in integration tests
|
34
|
+
# -- they do not yet inherit this setting
|
35
|
+
fixtures :all
|
36
|
+
|
37
|
+
# Add more helper methods to be used by all tests here...
|
38
|
+
end
|
data/lib/data_fabric.rb
CHANGED
@@ -45,7 +45,13 @@ module DataFabric
|
|
45
45
|
def self.init
|
46
46
|
logger = ActiveRecord::Base.logger unless logger
|
47
47
|
log { "Loading data_fabric #{DataFabric::Version::STRING} with ActiveRecord #{ActiveRecord::VERSION::STRING}" }
|
48
|
-
|
48
|
+
|
49
|
+
if ActiveRecord::VERSION::STRING < '2.2.0'
|
50
|
+
require 'data_fabric/ar20'
|
51
|
+
else
|
52
|
+
require 'data_fabric/ar22'
|
53
|
+
end
|
54
|
+
ActiveRecord::Base.send(:include, DataFabric::Extensions)
|
49
55
|
end
|
50
56
|
|
51
57
|
def self.activate_shard(shards, &block)
|
@@ -89,11 +95,6 @@ module DataFabric
|
|
89
95
|
Thread.current[:shards] and Thread.current[:shards][group.to_s]
|
90
96
|
end
|
91
97
|
|
92
|
-
def self.included(model)
|
93
|
-
# Wire up ActiveRecord::Base
|
94
|
-
model.extend ClassMethods
|
95
|
-
end
|
96
|
-
|
97
98
|
def self.ensure_setup
|
98
99
|
Thread.current[:shards] = {} unless Thread.current[:shards]
|
99
100
|
end
|
@@ -102,130 +103,4 @@ module DataFabric
|
|
102
103
|
logger && logger.add(level, &block)
|
103
104
|
end
|
104
105
|
|
105
|
-
# Class methods injected into ActiveRecord::Base
|
106
|
-
module ClassMethods
|
107
|
-
def data_fabric(options)
|
108
|
-
proxy = DataFabric::ConnectionProxy.new(self, options)
|
109
|
-
ActiveRecord::Base.active_connections[name] = proxy
|
110
|
-
|
111
|
-
raise ArgumentError, "data_fabric does not support ActiveRecord's allow_concurrency = true" if allow_concurrency
|
112
|
-
DataFabric.log { "Creating data_fabric proxy for class #{name}" }
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
class StringProxy
|
117
|
-
def initialize(&block)
|
118
|
-
@proc = block
|
119
|
-
end
|
120
|
-
def to_s
|
121
|
-
@proc.call
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
class ConnectionProxy
|
126
|
-
def initialize(model_class, options)
|
127
|
-
@model_class = model_class
|
128
|
-
@replicated = options[:replicated]
|
129
|
-
@shard_group = options[:shard_by]
|
130
|
-
@prefix = options[:prefix]
|
131
|
-
@role = 'slave' if @replicated
|
132
|
-
|
133
|
-
@model_class.send :include, ActiveRecordConnectionMethods if @replicated
|
134
|
-
end
|
135
|
-
|
136
|
-
delegate :insert, :update, :delete, :create_table, :rename_table, :drop_table, :add_column, :remove_column,
|
137
|
-
:change_column, :change_column_default, :rename_column, :add_index, :remove_index, :initialize_schema_information,
|
138
|
-
:dump_schema_information, :execute, :execute_ignore_duplicate, :to => :master
|
139
|
-
|
140
|
-
def cache(&block)
|
141
|
-
connection.cache(&block)
|
142
|
-
end
|
143
|
-
|
144
|
-
def transaction(start_db_transaction = true, &block)
|
145
|
-
with_master { connection.transaction(start_db_transaction, &block) }
|
146
|
-
end
|
147
|
-
|
148
|
-
def method_missing(method, *args, &block)
|
149
|
-
DataFabric.log(Logger::DEBUG) { "Calling #{method} on #{connection}" }
|
150
|
-
connection.send(method, *args, &block)
|
151
|
-
end
|
152
|
-
|
153
|
-
def connection_name
|
154
|
-
connection_name_builder.join('_')
|
155
|
-
end
|
156
|
-
|
157
|
-
def disconnect!
|
158
|
-
if connected?
|
159
|
-
connection.disconnect!
|
160
|
-
cached_connections[connection_name] = nil
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
def verify!(arg)
|
165
|
-
connection.verify!(arg) if connected?
|
166
|
-
end
|
167
|
-
|
168
|
-
def with_master
|
169
|
-
# Allow nesting of with_master.
|
170
|
-
old_role = @role
|
171
|
-
set_role('master')
|
172
|
-
yield
|
173
|
-
ensure
|
174
|
-
set_role(old_role)
|
175
|
-
end
|
176
|
-
|
177
|
-
private
|
178
|
-
|
179
|
-
def cached_connections
|
180
|
-
@cached_connections ||= {}
|
181
|
-
end
|
182
|
-
|
183
|
-
def connection_name_builder
|
184
|
-
@connection_name_builder ||= begin
|
185
|
-
clauses = []
|
186
|
-
clauses << @prefix if @prefix
|
187
|
-
clauses << @shard_group if @shard_group
|
188
|
-
clauses << StringProxy.new { DataFabric.active_shard(@shard_group) } if @shard_group
|
189
|
-
clauses << RAILS_ENV
|
190
|
-
clauses << StringProxy.new { @role } if @replicated
|
191
|
-
clauses
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
|
-
def connection
|
196
|
-
name = connection_name
|
197
|
-
if not connected?
|
198
|
-
config = ActiveRecord::Base.configurations[name]
|
199
|
-
raise ArgumentError, "Unknown database config: #{name}, have #{ActiveRecord::Base.configurations.inspect}" unless config
|
200
|
-
DataFabric.log { "Connecting to #{name}" }
|
201
|
-
@model_class.establish_connection(config)
|
202
|
-
cached_connections[name] = @model_class.connection
|
203
|
-
@model_class.active_connections[@model_class.name] = self
|
204
|
-
end
|
205
|
-
cached_connections[name].verify!(3600)
|
206
|
-
cached_connections[name]
|
207
|
-
end
|
208
|
-
|
209
|
-
def connected?
|
210
|
-
DataFabric.shard_active_for?(@shard_group) and cached_connections[connection_name]
|
211
|
-
end
|
212
|
-
|
213
|
-
def set_role(role)
|
214
|
-
@role = role if @replicated
|
215
|
-
end
|
216
|
-
|
217
|
-
def master
|
218
|
-
with_master { return connection }
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
module ActiveRecordConnectionMethods
|
223
|
-
def self.included(base)
|
224
|
-
base.alias_method_chain :reload, :master
|
225
|
-
end
|
226
|
-
|
227
|
-
def reload_with_master(*args, &block)
|
228
|
-
connection.with_master { reload_without_master }
|
229
|
-
end
|
230
|
-
end
|
231
106
|
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module DataFabric
|
2
|
+
module Extensions
|
3
|
+
def self.included(model)
|
4
|
+
# Wire up ActiveRecord::Base
|
5
|
+
model.extend ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
# Class methods injected into ActiveRecord::Base
|
9
|
+
module ClassMethods
|
10
|
+
def data_fabric(options)
|
11
|
+
proxy = DataFabric::ConnectionProxy.new(self, options)
|
12
|
+
ActiveRecord::Base.active_connections[name] = proxy
|
13
|
+
|
14
|
+
raise ArgumentError, "data_fabric does not support ActiveRecord's allow_concurrency = true" if allow_concurrency
|
15
|
+
DataFabric.log { "Creating data_fabric proxy for class #{name}" }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class ConnectionProxy
|
21
|
+
def initialize(model_class, options)
|
22
|
+
@model_class = model_class
|
23
|
+
@replicated = options[:replicated]
|
24
|
+
@shard_group = options[:shard_by]
|
25
|
+
@prefix = options[:prefix]
|
26
|
+
@role = 'slave' if @replicated
|
27
|
+
|
28
|
+
@model_class.send :include, ActiveRecordConnectionMethods if @replicated
|
29
|
+
end
|
30
|
+
|
31
|
+
delegate :insert, :update, :delete, :create_table, :rename_table, :drop_table, :add_column, :remove_column,
|
32
|
+
:change_column, :change_column_default, :rename_column, :add_index, :remove_index, :initialize_schema_information,
|
33
|
+
:dump_schema_information, :execute, :execute_ignore_duplicate, :to => :master
|
34
|
+
|
35
|
+
delegate :insert_many, :to => :master # ar-extensions bulk insert support
|
36
|
+
|
37
|
+
def transaction(start_db_transaction = true, &block)
|
38
|
+
with_master { connection.transaction(start_db_transaction, &block) }
|
39
|
+
end
|
40
|
+
|
41
|
+
def method_missing(method, *args, &block)
|
42
|
+
DataFabric.log(Logger::DEBUG) { "Calling #{method} on #{connection}" }
|
43
|
+
connection.send(method, *args, &block)
|
44
|
+
end
|
45
|
+
|
46
|
+
def connection_name
|
47
|
+
connection_name_builder.join('_')
|
48
|
+
end
|
49
|
+
|
50
|
+
def disconnect!
|
51
|
+
if connected?
|
52
|
+
connection.disconnect!
|
53
|
+
cached_connections[connection_name] = nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def verify!(arg)
|
58
|
+
connection.verify!(arg) if connected?
|
59
|
+
end
|
60
|
+
|
61
|
+
def with_master
|
62
|
+
# Allow nesting of with_master.
|
63
|
+
old_role = @role
|
64
|
+
set_role('master')
|
65
|
+
yield
|
66
|
+
ensure
|
67
|
+
set_role(old_role)
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def cached_connections
|
73
|
+
@cached_connections ||= {}
|
74
|
+
end
|
75
|
+
|
76
|
+
def connection_name_builder
|
77
|
+
@connection_name_builder ||= begin
|
78
|
+
clauses = []
|
79
|
+
clauses << @prefix if @prefix
|
80
|
+
clauses << @shard_group if @shard_group
|
81
|
+
clauses << StringProxy.new { DataFabric.active_shard(@shard_group) } if @shard_group
|
82
|
+
clauses << RAILS_ENV
|
83
|
+
clauses << StringProxy.new { @role } if @replicated
|
84
|
+
clauses
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def connection
|
89
|
+
name = connection_name
|
90
|
+
if not connected?
|
91
|
+
config = ActiveRecord::Base.configurations[name]
|
92
|
+
raise ArgumentError, "Unknown database config: #{name}, have #{ActiveRecord::Base.configurations.inspect}" unless config
|
93
|
+
DataFabric.log { "Connecting to #{name}" }
|
94
|
+
@model_class.establish_connection(config)
|
95
|
+
cached_connections[name] = @model_class.connection
|
96
|
+
@model_class.active_connections[@model_class.name] = self
|
97
|
+
end
|
98
|
+
cached_connections[name].verify!(3600)
|
99
|
+
cached_connections[name]
|
100
|
+
end
|
101
|
+
|
102
|
+
def connected?
|
103
|
+
DataFabric.shard_active_for?(@shard_group) and cached_connections[connection_name]
|
104
|
+
end
|
105
|
+
|
106
|
+
def set_role(role)
|
107
|
+
@role = role if @replicated
|
108
|
+
end
|
109
|
+
|
110
|
+
def master
|
111
|
+
with_master { return connection }
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
module ActiveRecordConnectionMethods
|
116
|
+
def self.included(base)
|
117
|
+
base.alias_method_chain :reload, :master
|
118
|
+
end
|
119
|
+
|
120
|
+
def reload_with_master(*args, &block)
|
121
|
+
connection.with_master { reload_without_master }
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
class StringProxy
|
126
|
+
def initialize(&block)
|
127
|
+
@proc = block
|
128
|
+
end
|
129
|
+
def to_s
|
130
|
+
@proc.call
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|