xbar 0.0.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. data/Appraisals +25 -0
  2. data/README.mkdn +215 -0
  3. data/Rakefile +337 -1
  4. data/examples/README +5 -0
  5. data/examples/config/simple.json +22 -0
  6. data/examples/example1.rb +34 -0
  7. data/examples/migrations/1_create_users.rb +10 -0
  8. data/examples/setup.rb +43 -0
  9. data/gemfiles/rails3.gemfile +8 -0
  10. data/gemfiles/rails3.gemfile.lock +74 -0
  11. data/gemfiles/rails31.gemfile +8 -0
  12. data/gemfiles/rails31.gemfile.lock +83 -0
  13. data/gemfiles/rails32.gemfile +7 -0
  14. data/gemfiles/rails32.gemfile.lock +117 -0
  15. data/gemfiles/rails4.gemfile +9 -0
  16. data/gemfiles/rails4.gemfile.lock +134 -0
  17. data/lib/migrations/1_create_usage_statistics.rb +23 -0
  18. data/lib/xbar/association.rb +49 -0
  19. data/lib/xbar/association_collection.rb +69 -0
  20. data/lib/xbar/colors.rb +32 -0
  21. data/lib/xbar/has_and_belongs_to_many_association.rb +17 -0
  22. data/lib/xbar/logger.rb +14 -0
  23. data/lib/xbar/mapper.rb +304 -0
  24. data/lib/xbar/migration.rb +76 -0
  25. data/lib/xbar/model.rb +165 -0
  26. data/lib/xbar/proxy.rb +249 -0
  27. data/lib/xbar/rails2/association.rb +133 -0
  28. data/lib/xbar/rails2/persistence.rb +39 -0
  29. data/lib/xbar/rails3/arel.rb +13 -0
  30. data/lib/xbar/rails3/association.rb +112 -0
  31. data/lib/xbar/rails3/persistence.rb +37 -0
  32. data/lib/xbar/rails3.1/singular_association.rb +34 -0
  33. data/lib/xbar/scope_proxy.rb +55 -0
  34. data/lib/xbar/shard.rb +95 -0
  35. data/lib/xbar/version.rb +2 -2
  36. data/lib/xbar.rb +121 -2
  37. data/run +27 -0
  38. data/spec/config/acme.json +53 -0
  39. data/spec/config/connection.rb +2 -0
  40. data/spec/config/default.json +160 -0
  41. data/spec/config/duplicate_shard.json +21 -0
  42. data/spec/config/missing_key.json +20 -0
  43. data/spec/config/new_shards.json +29 -0
  44. data/spec/config/no_master_shard.json +19 -0
  45. data/spec/config/not_entire_sharded.json +23 -0
  46. data/spec/config/octopus.json +27 -0
  47. data/spec/config/octopus_rails.json +25 -0
  48. data/spec/config/production_fully_replicated.json +21 -0
  49. data/spec/config/production_raise_error.json +17 -0
  50. data/spec/config/simple.json +22 -0
  51. data/spec/config/single_adapter.json +20 -0
  52. data/spec/console.rb +15 -0
  53. data/spec/migrations/10_create_users_using_replication.rb +12 -0
  54. data/spec/migrations/11_add_field_in_all_slaves.rb +11 -0
  55. data/spec/migrations/12_create_users_using_block.rb +23 -0
  56. data/spec/migrations/13_create_users_using_block_and_using.rb +15 -0
  57. data/spec/migrations/1_create_users_on_master.rb +9 -0
  58. data/spec/migrations/2_create_users_on_canada.rb +11 -0
  59. data/spec/migrations/3_create_users_on_both_shards.rb +11 -0
  60. data/spec/migrations/4_create_users_on_shards_of_a_group.rb +11 -0
  61. data/spec/migrations/5_create_users_on_multiples_groups.rb +11 -0
  62. data/spec/migrations/6_raise_exception_with_invalid_shard_name.rb +11 -0
  63. data/spec/migrations/7_raise_exception_with_invalid_multiple_shard_names.rb +11 -0
  64. data/spec/migrations/8_raise_exception_with_invalid_group_name.rb +11 -0
  65. data/spec/migrations/9_raise_exception_with_multiple_invalid_group_names.rb +11 -0
  66. data/spec/spec_helper.rb +25 -0
  67. data/spec/support/database_models.rb +78 -0
  68. data/spec/support/xbar_helper.rb +42 -0
  69. data/spec/xbar/association_spec.rb +660 -0
  70. data/spec/xbar/controller_spec.rb +40 -0
  71. data/spec/xbar/logger_spec.rb +22 -0
  72. data/spec/xbar/mapper_spec.rb +283 -0
  73. data/spec/xbar/migration_spec.rb +110 -0
  74. data/spec/xbar/model_spec.rb +434 -0
  75. data/spec/xbar/proxy_spec.rb +124 -0
  76. data/spec/xbar/replication_spec.rb +94 -0
  77. data/spec/xbar/scope_proxy_spec.rb +22 -0
  78. data/spec/xbar/shard_spec.rb +36 -0
  79. data/xbar.gemspec +13 -3
  80. metadata +231 -10
data/Appraisals ADDED
@@ -0,0 +1,25 @@
1
+ # appraise "rails2" do
2
+ # gem 'activerecord', '~> 2.3'
3
+ # gem 'actionpack', '~> 2.3'
4
+ # end
5
+
6
+ appraise "rails3" do
7
+ gem 'activerecord', '~> 3.0.11'
8
+ gem 'actionpack', '~> 3.0.11'
9
+ end
10
+
11
+ appraise "rails31" do
12
+ gem 'activerecord', '~> 3.1.1'
13
+ gem 'actionpack', '~> 3.1.1'
14
+ end
15
+
16
+
17
+ appraise "rails32" do
18
+ gem 'rails', '3.2.1'
19
+ end
20
+
21
+ appraise "rails4" do
22
+ gem 'rails', :path => '/opt/rails/rails-edge'
23
+ gem 'journey', :git => 'https://github.com/rails/journey.git'
24
+ gem 'arel', :git => 'https://github.com/rails/arel.git'
25
+ end
data/README.mkdn ADDED
@@ -0,0 +1,215 @@
1
+ # XBar - MongoDB Style Sharding for ActiveRecord
2
+
3
+ ## Supported versions
4
+ *Caution* -- only ActiveRecord 3.2 is currently supported. This
5
+ will soon be improved.
6
+
7
+ ## General Design
8
+
9
+ The XBar project is derived from *Octopus*. Octopus showed that the
10
+ implementation technique of using a proxy for the
11
+ `ActiveRecord::ConnectionAdapters::AbstractAdapter` object instances
12
+ is possible. This proxy implements a sort of *late binding*,
13
+ returning a real abstract adapter object that depends on the current
14
+ state of the proxy, especially on the value of the `current_shard`.
15
+ Many of the tricky pieces of code, especially those for managing
16
+ associations, come from Octopus.
17
+
18
+ ## Changes from Octopus
19
+
20
+ The Octopus implementation has been modified to support efficient
21
+ dynamic reconfiguration. The configuration is given in a JSON
22
+ document, rather than YAML. This makes it convenient to update the
23
+ application over the network using the standard JSON over HTTP
24
+ technique. The JSON document supports multiple application
25
+ environments. These align with Rails environments when Rails is
26
+ present. Unlike Octopus, this document format does not allow multiple
27
+ 'Octopus' evironments. Instead, there is an `xbar_env` which
28
+ essentially specifies a *different* JSON file to use.
29
+
30
+ Another change is that shards that are themselves a collection of
31
+ mirrors is supported. Mirroring does not take place among shards, it
32
+ takes place among the mirrors that constitute a shard. Replication
33
+ among the mirrors is expected to be handling by some database specific
34
+ technique, such as native *MySQL* replication.
35
+
36
+ The Octopus `Proxy` class has been split into two classes `Proxy` and
37
+ `Shard`, and into a module `Mapper`. `Mapper` contains the in-memory
38
+ representation of the current configuration. Each `Proxy` instance no
39
+ longer has its own copy of the configuration. There is exactly one
40
+ `Mapper` module for the entire application, necessarily true, since
41
+ its a *module*. As before there is one `Proxy` instance per thread,
42
+ which allows per-thread state to be stored in the instance. A `Proxy`
43
+ instance references a collection of `Shard` instances. Each `Shard`
44
+ instance manages a set of replicas for that shard.
45
+
46
+ The concept of a *group* as used in Octopus is mostly gone. Vestiges
47
+ of it exist in the migration code. Thus a migration can still specify
48
+ that it should take place on multiple shards. In the general case, a
49
+ group concept would fork writes to send them to multiple shards. This
50
+ is only practical if the writes are performed in parallel in separate
51
+ threads. The current `Proxy` would become a group manager and we'd
52
+ have a hierarchy something like this:
53
+
54
+ Proxy (set of Groups) -> Group (set of Shards) -> Shard (set of replicas)
55
+
56
+ This complexity is too great for the first version of `XBar`.
57
+
58
+ ## Concepts
59
+
60
+ ### Mapper
61
+
62
+ The mapper is a module that maintains the state of the database servers, shards,
63
+ replication, and databases. It is configured via a JSON document. The JSON
64
+ document can be in the local file system, or it can be delivered over HTTP. A
65
+ new JSON document can be installed at any time. The mapper does not maintain
66
+ any per-thread state. All the proxies get their mapping information directly
67
+ from the mapper. Some information is cached in each proxy, optimized for use in
68
+ the proxy. Thus when the JSON document is changed, the mapper will notify each
69
+ proxy to rebuild its state.
70
+
71
+ ### Proxy
72
+
73
+ An instance of the `Proxy` object exists per thread. Thus thread-local state
74
+ can be kept directly in the proxy, and the proxy can refer to global state in
75
+ the mapper (and in the `XBar` module itself). Each proxy registers with the
76
+ mapper when it is created so that the mapper can notify the proxy of changes to
77
+ the global configuration.
78
+
79
+ A proxy is only responsible for managing shards, that is, except for some
80
+ initialization code, it has no knowledge of replicas within a shard. It has a
81
+ list, the `shard_list`, that maps shard names to `Shard` objects. For each SQL
82
+ statement, block of statements, or transaction, the proxy choose a shard and
83
+ deletegates the operation to the shard. The shard, in turn, chooses the particular
84
+ replica to use.
85
+
86
+ ### Shard
87
+
88
+ The term *shard* is used as it is used in MongoDB. A shard acts like an
89
+ independent database. In our case, the replication within the shard is handled
90
+ by some external means such as native MySQL replication. Nevertheless, XBar
91
+ knows about the replicas within the shard, and it knows which replica is the
92
+ replication master. This allows XBar to direct transactions to the master shard,
93
+ and reads to the (eventually-consistent) replicas within the shard.
94
+
95
+ The shard is a per-thread object, referenced only from a proxy. Thus the
96
+ overall structure is tree-like. One mapper references a collection of proxies
97
+ (one per thread), and each proxy references a collection of shards. Each shard
98
+ object references a list of `ActiveRecord::ConnectionAdapters::ConnectionPool`
99
+ instances that the shard uses to select connections to replicas within the
100
+ shard.
101
+
102
+ ### Environments
103
+
104
+ XBar has three concepts all called *environments*. First, there is the
105
+ *rails_env* that is inherited from Rails when this gem is included in a Rails
106
+ application. Second, the *xbar_env* is the name of the configuration file that
107
+ is currently in effect. In the case where the configuration was loaded via a
108
+ JSON document over HTTP, a name for the *xbar_env* is generated in some other
109
+ way (TBD). Finally, there is the *app_env*, that functions much like
110
+ *rails_env* when Rails is not present. When Rails is present, *app_env* is read
111
+ only and always has the same value as *rails_env*. When Rails is not present,
112
+ *app_env* can be selected by the user. In either case, its value should be name
113
+ of an environment stanza in the current configuration file.
114
+
115
+ ## Getting Started
116
+
117
+ The *examples* directory at the top level of the source distribution
118
+ has some stand-alone examples that show how to set up and use `XBar`.
119
+
120
+ ## Using XBar
121
+
122
+ XBar adds a method to each `ActiveRecord` Class and object: the using
123
+ method is used to select the shard like this:
124
+
125
+ ```ruby
126
+ User.where(:name => "Thiago").limit(3).using(:slave_one)
127
+ ```
128
+
129
+ XBar also supports queries within a block. When you pass a block to
130
+ the using method, all queries inside the block will be sent to the
131
+ specified shard.
132
+
133
+ ```ruby
134
+ XBar.using(:slave_two) do
135
+ User.create(:name => "Mike")
136
+ end
137
+ ```
138
+
139
+ Each model instance knows which shard it came from so this will work
140
+ automatically:
141
+
142
+ ```ruby
143
+ # This will find the user in the shard1
144
+ @user = User.using(:shard1).find_by_name("Joao")
145
+
146
+ # This will find the user in the master database
147
+ @user2 = User.find_by_name("Jose")
148
+
149
+ #Sets the name
150
+ @user.name = "Mike"
151
+
152
+ # Save the user in the correct shard, shard1.
153
+ @user.save
154
+ ```
155
+
156
+ ### Migrations
157
+
158
+ In migrations, you also have access to the using method. The syntax is
159
+ basically the same. This migration will run in the brazil and canada
160
+ shards.
161
+
162
+ ```ruby
163
+ class CreateUsersOnBothShards < ActiveRecord::Migration
164
+ using(:brazil, :canada)
165
+
166
+ def self.up
167
+ User.create!(:name => "Both")
168
+ end
169
+
170
+ def self.down
171
+ User.delete_all
172
+ end
173
+ end
174
+ ```
175
+
176
+ ### Rails Controllers
177
+
178
+ If you want to send a specified action, or all actions from a
179
+ controller, to a specific shard, use this syntax:
180
+
181
+ ```ruby
182
+ class ApplicationController < ActionController::Base
183
+ around_filter :select_shard
184
+
185
+ def select_shard(&block)
186
+ XBar.using(:brazil, &block)
187
+ end
188
+ end
189
+ ```
190
+
191
+ ## Exception with Idle Connections
192
+
193
+ Sometimes, when a connection isn't used for much time,
194
+ this will makes ActiveRecord raising an exception. if you have this
195
+ kind of applications, please, add the following line to your
196
+ configuration:
197
+
198
+ ```ruby
199
+ verify_connection: true
200
+ ```
201
+
202
+ This will tell XBar to verify the connection before sending the query.
203
+
204
+ ## Mixing XBar with the Rails Multiple Database Mechanism
205
+
206
+ If you want to set a custom connection to a specific model, use this
207
+ syntax:
208
+
209
+ ```ruby
210
+ #This class sets its own connection
211
+ # establish_connection will not work, use xbar_establish_connection instead.
212
+ class CustomConnection < ActiveRecord::Base
213
+ xbar_establish_connection(:adapter => "mysql", :database => "xbar_shard2")
214
+ end
215
+ ```
data/Rakefile CHANGED
@@ -1 +1,337 @@
1
- require "bundler/gem_tasks"
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ # require 'metric_fu'
4
+ require 'appraisal'
5
+
6
+ task :default => :spec
7
+
8
+ # MetricFu::Mapperuration.run do |config|
9
+ # config.metrics = [:churn,:flay, :flog, :reek, :roodi, :saikuro]
10
+ # config.graphs = [:flog, :flay, :reek, :roodi]
11
+ # config.flay = { :dirs_to_flay => ['spec', 'lib'] }
12
+ # config.flog = { :dirs_to_flog => ['spec', 'lib'] }
13
+ # config.reek = { :dirs_to_reek => ['spec', 'lib'] }
14
+ # config.roodi = { :dirs_to_roodi => ['spec', 'lib'] }
15
+ # config.churn = { :start_date => "1 year ago", :minimum_churn_count => 10 }
16
+ # end
17
+
18
+ RSpec::Core::RakeTask.new(:spec) do |spec|
19
+ end
20
+
21
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
22
+ end
23
+
24
+ namespace :test do
25
+
26
+ desc 'Create usage_statistics table'
27
+ task :add_stats do
28
+ require 'active_record'
29
+ require "xbar"
30
+ XBar.directory = File.expand_path("../spec", __FILE__)
31
+ XBar::Mapper.reset(xbar_env: 'default', app_env: 'test')
32
+
33
+ class CreateUsageStatistics < ActiveRecord::Migration
34
+ using :master
35
+ def up
36
+ create_table(:usage_statistics) do |t|
37
+ t.string :shard_name
38
+ t.string :method
39
+ t.string :adapter
40
+ t.string :username
41
+ t.string :thread_id
42
+ t.integer :port
43
+ t.string :host
44
+ t.string :database_name
45
+ end
46
+ end
47
+ end
48
+ CreateUsageStatistics.migrate(:up)
49
+ end
50
+
51
+ desc 'Drop usage_statistics table'
52
+ task :drop_stats do
53
+ require 'active_record'
54
+ require "xbar"
55
+ XBar.directory = File.expand_path("../spec", __FILE__)
56
+ XBar::Mapper.reset(xbar_env: 'default', app_env: 'test')
57
+
58
+ class CreateUsageStatistics < ActiveRecord::Migration
59
+ using :master
60
+ def down
61
+ drop_table(:usage_statistics)
62
+ end
63
+ end
64
+ CreateUsageStatistics.migrate(:down)
65
+ end
66
+
67
+ desc 'Drop the databases for tests'
68
+ task :drop do
69
+ mysql_user = ENV['MYSQL_USER'] || "root"
70
+ postgres_user = ENV['POSTGRES_USER'] || "postgres"
71
+
72
+ require 'active_record'
73
+ require "xbar"
74
+ XBar.directory = File.expand_path("../spec", __FILE__)
75
+ XBar::Mapper.reset(xbar_env: 'default', app_env: 'test')
76
+
77
+ %w(mysql_m london_s canada_1 brazil_1 china_1 china_2).each do |key|
78
+ config = XBar::Mapper.connections[key].spec.config
79
+ opts = "-h#{config[:host]} -P#{config[:port]} -u#{config[:username]}"
80
+ opts += " -p#{config[:password]}" if config[:password] &&
81
+ config[:password] != ""
82
+
83
+ db = config[:database]
84
+ cmd = "mysqladmin #{opts} -f drop #{db}"
85
+ %x( #{cmd} )
86
+ end
87
+
88
+ %x{ mysqladmin -uroot -f drop rogue }
89
+
90
+ %w(moscow_s russia_1 russia_2 russia_3).each do |key|
91
+ config = XBar::Mapper.connections[key].spec.config
92
+ %x( dropdb -U #{postgres_user} #{config[:database]} )
93
+ end
94
+
95
+ %x( rm -f /tmp/paris.sqlite3 )
96
+
97
+ end
98
+
99
+ desc 'Create databases for the tests'
100
+ task :create do
101
+ mysql_user = ENV['MYSQL_USER'] || "root"
102
+ postgres_user = ENV['POSTGRES_USER'] || "postgres"
103
+
104
+ require 'active_record'
105
+ require "xbar"
106
+ XBar.directory = File.expand_path("../spec", __FILE__)
107
+ XBar::Mapper.reset(xbar_env: 'default', app_env: 'test')
108
+
109
+ %w(brazil_1 canada_1 mysql_m london_s china_1 china_2).each do |key|
110
+ config = XBar::Mapper.connections[key].spec.config
111
+ opts = "-h#{config[:host]} -P#{config[:port]} -u#{config[:username]}"
112
+ opts += " -p#{config[:password]}" if config[:password ] &&
113
+ config[:password] != ""
114
+
115
+ db = config[:database]
116
+ sql = "create DATABASE #{db} DEFAULT CHARACTER SET utf8 " +
117
+ "DEFAULT COLLATE utf8_unicode_ci"
118
+ %x( echo #{sql} | mysql #{opts} )
119
+ end
120
+
121
+ %x{ mysqladmin -uroot create rogue }
122
+
123
+ %w(moscow_s russia_1 russia_2 russia_3).each do |key|
124
+ config = XBar::Mapper.connections[key].spec.config
125
+ %x( createdb -E UTF8 -U #{postgres_user} -T template0 #{config[:database]} )
126
+ end
127
+ end
128
+
129
+ desc 'Build test tables'
130
+ task :tables do
131
+
132
+ require 'active_record'
133
+ require "xbar"
134
+ XBar.directory = File.expand_path("../spec", __FILE__)
135
+ XBar::Mapper.reset(xbar_env: 'default', app_env: 'test')
136
+
137
+ class BlankModel < ActiveRecord::Base; end;
138
+
139
+ %w(master london paris moscow canada brazil russia_east
140
+ russia_west russia_central china_east china_west).each do |shard|
141
+
142
+ BlankModel.using(shard).connection.
143
+ initialize_schema_migrations_table
144
+
145
+ ## Find the best way to build tables on the master of each shard. ##
146
+ ## We should use migrations instead of this error-prone way. ##
147
+
148
+ BlankModel.using(shard).connection.create_table(:users) do |u|
149
+ u.string :name
150
+ u.integer :number
151
+ u.boolean :admin
152
+ end
153
+
154
+ BlankModel.using(shard).connection.create_table(:clients) do |u|
155
+ u.string :country
156
+ u.string :name
157
+ end
158
+
159
+ BlankModel.using(shard).connection.create_table(:cats) do |u|
160
+ u.string :name
161
+ end
162
+
163
+ BlankModel.using(shard).connection.create_table(:items) do |u|
164
+ u.string :name
165
+ u.integer :client_id
166
+ end
167
+
168
+ BlankModel.using(shard).connection.create_table(:computers) do |u|
169
+ u.string :name
170
+ end
171
+
172
+ BlankModel.using(shard).connection.create_table(:keyboards) do |u|
173
+ u.string :name
174
+ u.integer :computer_id
175
+ end
176
+
177
+ BlankModel.using(shard).connection.create_table(:roles) do |u|
178
+ u.string :name
179
+ end
180
+
181
+ BlankModel.using(shard).connection.create_table(:permissions) do |u|
182
+ u.string :name
183
+ end
184
+
185
+ BlankModel.using(shard).connection.create_table(:permissions_roles, :id => false) do |u|
186
+ u.integer :role_id
187
+ u.integer :permission_id
188
+ end
189
+
190
+ BlankModel.using(shard).connection.create_table(:assignments) do |u|
191
+ u.integer :programmer_id
192
+ u.integer :project_id
193
+ end
194
+
195
+ BlankModel.using(shard).connection.create_table(:programmers) do |u|
196
+ u.string :name
197
+ end
198
+
199
+ BlankModel.using(shard).connection.create_table(:projects) do |u|
200
+ u.string :name
201
+ end
202
+
203
+ BlankModel.using(shard).connection.create_table(:comments) do |u|
204
+ u.string :name
205
+ u.string :commentable_type
206
+ u.integer :commentable_id
207
+ end
208
+
209
+ BlankModel.using(shard).connection.create_table(:parts) do |u|
210
+ u.string :name
211
+ u.integer :item_id
212
+ end
213
+
214
+ BlankModel.using(shard).connection.create_table(:yummy) do |u|
215
+ u.string :name
216
+ end
217
+ end
218
+ end
219
+
220
+ desc 'Prepare the test databases'
221
+ task :prepare => [:drop, :create, :tables]
222
+ end
223
+
224
+ namespace :db do
225
+ desc 'Build the databases for tests'
226
+
227
+ task :build_databases do
228
+ mysql_user = ENV['MYSQL_USER'] || "root"
229
+ postgres_user = ENV['POSTGRES_USER'] || "postgres"
230
+ (1..5).each do |idx|
231
+ %x( echo "create DATABASE xbar_shard#{idx} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci " | mysql --user=#{mysql_user})
232
+ end
233
+
234
+ %x( createdb -E UTF8 -U #{postgres_user} -T template0 xbar_shard1 )
235
+ end
236
+
237
+ desc 'Drop the tests databases'
238
+ task :drop_databases do
239
+ mysql_user = ENV['MYSQL_USER'] || "root"
240
+ postgres_user = ENV['POSTGRES_USER'] || "postgres"
241
+ (1..5).each do |idx|
242
+ %x( mysqladmin --user=#{mysql_user} -f drop xbar_shard#{idx} )
243
+ end
244
+
245
+ %x( dropdb -U #{postgres_user} xbar_shard1 )
246
+ %x( rm -f /tmp/database.sqlite3 )
247
+ end
248
+
249
+ desc 'Create tables on tests databases'
250
+ task :create_tables do
251
+ Dir.chdir(File.expand_path(File.dirname(__FILE__) + "/spec"))
252
+ # require 'active_support/core_ext/class/inheritable_attributes'
253
+ require 'active_record'
254
+ require "xbar"
255
+ [:master, :brazil, :canada, :russia, :alone_shard, :postgresql_shard, :sqlite_shard].each do |shard_symbol|
256
+ # Rails 3.1 needs to do some introspection around the base class, which requires
257
+ # the model be a descendent of ActiveRecord::Base.
258
+ class BlankModel < ActiveRecord::Base; end;
259
+
260
+ BlankModel.using(shard_symbol).connection.initialize_schema_migrations_table()
261
+
262
+ BlankModel.using(shard_symbol).connection.create_table(:users) do |u|
263
+ u.string :name
264
+ u.integer :number
265
+ u.boolean :admin
266
+ end
267
+
268
+ BlankModel.using(shard_symbol).connection.create_table(:clients) do |u|
269
+ u.string :country
270
+ u.string :name
271
+ end
272
+
273
+ BlankModel.using(shard_symbol).connection.create_table(:cats) do |u|
274
+ u.string :name
275
+ end
276
+
277
+ BlankModel.using(shard_symbol).connection.create_table(:items) do |u|
278
+ u.string :name
279
+ u.integer :client_id
280
+ end
281
+
282
+ BlankModel.using(shard_symbol).connection.create_table(:computers) do |u|
283
+ u.string :name
284
+ end
285
+
286
+ BlankModel.using(shard_symbol).connection.create_table(:keyboards) do |u|
287
+ u.string :name
288
+ u.integer :computer_id
289
+ end
290
+
291
+ BlankModel.using(shard_symbol).connection.create_table(:roles) do |u|
292
+ u.string :name
293
+ end
294
+
295
+ BlankModel.using(shard_symbol).connection.create_table(:permissions) do |u|
296
+ u.string :name
297
+ end
298
+
299
+ BlankModel.using(shard_symbol).connection.create_table(:permissions_roles, :id => false) do |u|
300
+ u.integer :role_id
301
+ u.integer :permission_id
302
+ end
303
+
304
+ BlankModel.using(shard_symbol).connection.create_table(:assignments) do |u|
305
+ u.integer :programmer_id
306
+ u.integer :project_id
307
+ end
308
+
309
+ BlankModel.using(shard_symbol).connection.create_table(:programmers) do |u|
310
+ u.string :name
311
+ end
312
+
313
+ BlankModel.using(shard_symbol).connection.create_table(:projects) do |u|
314
+ u.string :name
315
+ end
316
+
317
+ BlankModel.using(shard_symbol).connection.create_table(:comments) do |u|
318
+ u.string :name
319
+ u.string :commentable_type
320
+ u.integer :commentable_id
321
+ end
322
+
323
+ BlankModel.using(shard_symbol).connection.create_table(:parts) do |u|
324
+ u.string :name
325
+ u.integer :item_id
326
+ end
327
+
328
+ BlankModel.using(shard_symbol).connection.create_table(:yummy) do |u|
329
+ u.string :name
330
+ end
331
+ end
332
+ end
333
+
334
+ desc 'Prepare the test databases'
335
+ task :prepare => [:drop_databases, :build_databases, :create_tables]
336
+ end
337
+
data/examples/README ADDED
@@ -0,0 +1,5 @@
1
+ Set your environment variable BUNDLE_GEMFILE to one of the
2
+ gemfiles in ../gemfiles, then do
3
+
4
+ bundle exec ruby example1.rb
5
+
@@ -0,0 +1,22 @@
1
+ {
2
+ "__COMMENT": "Simple SQLite French Environment",
3
+
4
+ "connections": {
5
+ "paris_m": { "adapter": "sqlite3", "database": "/tmp/paris.sqlite3"},
6
+ "france_1": { "adapter": "sqlite3", "database": "/tmp/france_1.sqlite3"},
7
+ "france_2": { "adapter": "sqlite3", "database": "/tmp/france_2.sqlite3"},
8
+ "france_3": { "adapter": "sqlite3", "database": "/tmp/france_3.sqlite3"}
9
+ },
10
+
11
+ "environments": {
12
+ "test": {
13
+ "shards": {
14
+ "master": "paris_m",
15
+ "france": ["france_1", "france_2", "france_3"],
16
+ "france_nord": "france_1",
17
+ "france_central": "france_2",
18
+ "france_sud": "france_3"
19
+ }
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,34 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'setup'))
2
+
3
+ module Examples
4
+ Setup.start('simple', 'test', 1)
5
+
6
+ # Define the model to let us access the 'users' table through ActiveRecord.
7
+ class User < ActiveRecord::Base; end
8
+
9
+ # Everything is now set up. Remember that the three french shards don't
10
+ # really replicate. XBar is set up to *think* that they do. We know
11
+ # better, and can check that reads and writes go to the shards that
12
+ # we think that they should. They curious results below wouldn't happen
13
+ # if replication is really taking place (modulo the eventual-consistency
14
+ # problem).
15
+
16
+ User.using(:france_central).create!([{name: "central1"}, {name: "central2"}])
17
+ XBar.using(:france_sud) do
18
+ User.create!([{name: "sud1"}, {name: "sud2"}, {name: "sud3"}])
19
+ end
20
+ User.using(:france).create!(name: "nord1")
21
+
22
+ f1 = User.using(:france).all.size # 2
23
+ f2 = User.using(:france).all.size # 3
24
+ f3 = User.using(:france).all.size # 2
25
+ f4 = User.using(:france).all.size # 3
26
+ n1 = User.using(:france_nord).all.size # 1
27
+ c1 = User.using(:france_central).all.size # 2
28
+ s1 = User.using(:france_sud).all.size # 3
29
+
30
+ puts [f1, f2, f3, f4, n1, c1, s1].to_s # [2, 3, 2, 3, 1, 2, 3]
31
+
32
+ Setup.stop(1)
33
+ end
34
+
@@ -0,0 +1,10 @@
1
+ class CreateUsers < ActiveRecord::Migration
2
+ using :france_nord, :france_central, :france_sud
3
+ def change
4
+ create_table :users do |t|
5
+ t.string :name
6
+
7
+ t.timestamps
8
+ end
9
+ end
10
+ end
data/examples/setup.rb ADDED
@@ -0,0 +1,43 @@
1
+ require 'active_support'
2
+ require 'active_record'
3
+ require 'xbar'
4
+
5
+ # This file demonstrates a self-contained use of XBar. The only support
6
+ # is a subdirectory 'config' that holds a JSON configuration file, and
7
+ # a subdirectory 'migrations' that holds the one migration that we plan
8
+ # to use.
9
+
10
+ module Examples
11
+ module Setup
12
+
13
+ MIGRATIONS_ROOT = File.expand_path(File.join(File.dirname(__FILE__), 'migrations'))
14
+
15
+ def self.start(xbar_env, app_env, version = nil)
16
+
17
+ # This must agree with what's in the 'simple' JSON config file. Make
18
+ # sure that we're starting with a clean slate.
19
+ %x{ rm -f /tmp/paris.sqlite3 /tmp/france_1.sqlite3 \
20
+ /tmp/france_2.sqlite3 /tmp/france_3.sqlite3 }
21
+
22
+ # This directory should have a subdirectory called 'config' which
23
+ # actually holds the config files.
24
+ XBar.directory = File.expand_path(File.dirname(__FILE__))
25
+
26
+ # Initialize the mapper with the 'test' environment from the 'simple'
27
+ # configuration file.
28
+ XBar::Mapper.reset(xbar_env: xbar_env, app_env: app_env)
29
+
30
+ # Use a migration to create initial table(s) to work with.
31
+ if version
32
+ ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT, version)
33
+ end
34
+ end
35
+
36
+ def self.stop(version = nil)
37
+ if version
38
+ ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT, version)
39
+ end
40
+ end
41
+
42
+ end
43
+ end