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