data_fabric 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. data/CHANGELOG +4 -0
  2. data/Manifest +73 -0
  3. data/README.rdoc +1 -1
  4. data/Rakefile +34 -8
  5. data/data_fabric.gemspec +7 -7
  6. data/example/app/models/figment.rb +1 -1
  7. data/example/db/development.sqlite3 +0 -0
  8. data/example/db/s0_development.sqlite3 +0 -0
  9. data/example/db/s0_test.sqlite3 +0 -0
  10. data/example/db/s1_development.sqlite3 +0 -0
  11. data/example/db/s1_test.sqlite3 +0 -0
  12. data/example/db/test.sqlite3 +0 -0
  13. data/example/vendor/plugins/data_fabric/init.rb +1 -0
  14. data/example/vendor/plugins/data_fabric/lib/data_fabric.rb +106 -0
  15. data/example/vendor/plugins/data_fabric/lib/data_fabric/ar20.rb +135 -0
  16. data/example/vendor/plugins/data_fabric/lib/data_fabric/ar22.rb +172 -0
  17. data/example/vendor/plugins/data_fabric/lib/data_fabric/version.rb +5 -0
  18. data/example22/Rakefile +58 -0
  19. data/example22/app/controllers/accounts_controller.rb +22 -0
  20. data/example22/app/controllers/application.rb +39 -0
  21. data/example22/app/controllers/figments_controller.rb +8 -0
  22. data/example22/app/helpers/application_helper.rb +3 -0
  23. data/example22/app/models/account.rb +3 -0
  24. data/example22/app/models/figment.rb +4 -0
  25. data/example22/app/views/accounts/index.html.erb +47 -0
  26. data/example22/app/views/layouts/application.html.erb +8 -0
  27. data/example22/config/boot.rb +109 -0
  28. data/example22/config/database.yml +21 -0
  29. data/example22/config/environment.rb +76 -0
  30. data/example22/config/environments/development.rb +17 -0
  31. data/example22/config/environments/production.rb +24 -0
  32. data/example22/config/environments/test.rb +22 -0
  33. data/example22/config/initializers/inflections.rb +10 -0
  34. data/example22/config/initializers/mime_types.rb +5 -0
  35. data/example22/config/initializers/new_rails_defaults.rb +17 -0
  36. data/example22/config/locales/en.yml +5 -0
  37. data/example22/config/routes.rb +46 -0
  38. data/example22/db/migrate/20080702154628_create_accounts.rb +14 -0
  39. data/example22/db/migrate/20080702154820_create_figments.rb +14 -0
  40. data/example22/public/404.html +30 -0
  41. data/example22/public/422.html +30 -0
  42. data/example22/public/500.html +33 -0
  43. data/example22/public/dispatch.cgi +10 -0
  44. data/example22/public/dispatch.fcgi +24 -0
  45. data/example22/public/dispatch.rb +10 -0
  46. data/example22/public/favicon.ico +0 -0
  47. data/example22/public/images/rails.png +0 -0
  48. data/example22/public/index.html +274 -0
  49. data/example22/public/javascripts/application.js +2 -0
  50. data/example22/public/javascripts/controls.js +963 -0
  51. data/example22/public/javascripts/dragdrop.js +973 -0
  52. data/example22/public/javascripts/effects.js +1128 -0
  53. data/example22/public/javascripts/prototype.js +4320 -0
  54. data/example22/public/robots.txt +5 -0
  55. data/example22/script/about +4 -0
  56. data/example22/script/console +3 -0
  57. data/example22/script/dbconsole +3 -0
  58. data/example22/script/destroy +3 -0
  59. data/example22/script/generate +3 -0
  60. data/example22/script/performance/benchmarker +3 -0
  61. data/example22/script/performance/profiler +3 -0
  62. data/example22/script/performance/request +3 -0
  63. data/example22/script/plugin +3 -0
  64. data/example22/script/process/inspector +3 -0
  65. data/example22/script/process/reaper +3 -0
  66. data/example22/script/process/spawner +3 -0
  67. data/example22/script/runner +3 -0
  68. data/example22/script/server +3 -0
  69. data/example22/test/fixtures/accounts.yml +7 -0
  70. data/example22/test/functional/accounts_controller_test.rb +12 -0
  71. data/example22/test/integration/account_figments_test.rb +97 -0
  72. data/example22/test/performance/browsing_test.rb +9 -0
  73. data/example22/test/test_helper.rb +38 -0
  74. data/lib/data_fabric.rb +7 -132
  75. data/lib/data_fabric/ar20.rb +133 -0
  76. data/lib/data_fabric/ar22.rb +172 -0
  77. data/lib/data_fabric/version.rb +1 -1
  78. data/test/connection_test.rb +6 -2
  79. data/test/database_test.rb +26 -2
  80. data/test/test_helper.rb +34 -28
  81. data/test/thread_test.rb +19 -11
  82. data/test/vr_austin_master.db +0 -0
  83. data/test/vr_austin_slave.db +0 -0
  84. data/test/vr_dallas_master.db +0 -0
  85. data/test/vr_dallas_slave.db +0 -0
  86. metadata +79 -5
@@ -0,0 +1,5 @@
1
+ # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
2
+ #
3
+ # To ban all spiders from the entire site uncomment the next two lines:
4
+ # User-Agent: *
5
+ # Disallow: /
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../config/boot'
3
+ $LOAD_PATH.unshift "#{RAILTIES_PATH}/builtin/rails_info"
4
+ require 'commands/about'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../config/boot'
3
+ require 'commands/console'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../config/boot'
3
+ require 'commands/dbconsole'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../config/boot'
3
+ require 'commands/destroy'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../config/boot'
3
+ require 'commands/generate'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../../config/boot'
3
+ require 'commands/performance/benchmarker'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../../config/boot'
3
+ require 'commands/performance/profiler'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../../config/boot'
3
+ require 'commands/performance/request'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../config/boot'
3
+ require 'commands/plugin'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../../config/boot'
3
+ require 'commands/process/inspector'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../../config/boot'
3
+ require 'commands/process/reaper'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../../config/boot'
3
+ require 'commands/process/spawner'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../config/boot'
3
+ require 'commands/runner'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../config/boot'
3
+ require 'commands/server'
@@ -0,0 +1,7 @@
1
+ # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2
+
3
+ <% %w(fiveruns google yahoo microsoft).each_with_index do |name, idx| %>
4
+ <%= name %>:
5
+ name: <%= name %>
6
+ shard: <%= idx % 2 %>
7
+ <% end %>
@@ -0,0 +1,12 @@
1
+ require 'test_helper'
2
+
3
+ class AccountsControllerTest < ActionController::TestCase
4
+ fixtures :accounts
5
+
6
+ def test_index
7
+ get :index
8
+ assert_response :success
9
+ assert assigns(:accounts)
10
+ assert_equal 4, assigns(:accounts).size
11
+ end
12
+ end
@@ -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,9 @@
1
+ require 'test_helper'
2
+ require 'performance_test_help'
3
+
4
+ # Profiling results for each test method are written to tmp/performance.
5
+ class BrowsingTest < ActionController::PerformanceTest
6
+ def test_homepage
7
+ get '/'
8
+ end
9
+ 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
- ActiveRecord::Base.send(:include, self)
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