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