ar-octopus 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +11 -0
- data/.travis.yml +22 -0
- data/Appraisals +18 -0
- data/Gemfile +3 -12
- data/README.mkdn +63 -24
- data/Rakefile +70 -92
- data/ar-octopus.gemspec +25 -198
- data/lib/ar-octopus.rb +1 -0
- data/lib/octopus.rb +73 -25
- data/lib/octopus/association.rb +6 -5
- data/lib/octopus/association_collection.rb +58 -4
- data/lib/octopus/has_and_belongs_to_many_association.rb +4 -4
- data/lib/octopus/logger.rb +9 -4
- data/lib/octopus/migration.rb +155 -50
- data/lib/octopus/model.rb +98 -34
- data/lib/octopus/proxy.rb +124 -53
- data/lib/octopus/rails2/association.rb +46 -93
- data/lib/octopus/rails2/persistence.rb +1 -1
- data/lib/octopus/rails2/scope.rb +17 -0
- data/lib/octopus/rails3.1/singular_association.rb +34 -0
- data/lib/octopus/rails3.2/persistence.rb +12 -0
- data/lib/octopus/rails3/abstract_adapter.rb +39 -0
- data/lib/octopus/rails3/arel.rb +5 -5
- data/lib/octopus/rails3/log_subscriber.rb +22 -0
- data/lib/octopus/rails3/persistence.rb +10 -5
- data/lib/octopus/railtie.rb +13 -0
- data/lib/octopus/scope_proxy.rb +22 -16
- data/lib/octopus/version.rb +3 -0
- data/lib/tasks/octopus.rake +20 -0
- data/sample_app/Gemfile +2 -2
- data/sample_app/config/initializers/inflections.rb +1 -1
- data/sample_app/config/initializers/secret_token.rb +1 -1
- data/sample_app/db/migrate/20100720172730_create_items.rb +1 -1
- data/sample_app/db/migrate/20100720210335_create_sample_users.rb +1 -1
- data/sample_app/db/seeds.rb +1 -1
- data/sample_app/features/migrate.feature +12 -12
- data/sample_app/features/seed.feature +3 -3
- data/sample_app/features/step_definitions/web_steps.rb +5 -5
- data/sample_app/features/support/env.rb +8 -8
- data/sample_app/lib/tasks/cucumber.rake +2 -2
- data/sample_app/public/javascripts/effects.js +1 -1
- data/spec/config/shards.yml +38 -28
- data/spec/migrations/11_add_field_in_all_slaves.rb +1 -1
- data/spec/migrations/12_create_users_using_block.rb +2 -2
- data/spec/migrations/13_create_users_using_block_and_using.rb +2 -2
- data/spec/migrations/14_create_users_on_shards_of_a_group_with_versions.rb +11 -0
- data/spec/migrations/1_create_users_on_master.rb +1 -1
- data/spec/migrations/2_create_users_on_canada.rb +1 -1
- data/spec/migrations/3_create_users_on_both_shards.rb +1 -1
- data/spec/migrations/4_create_users_on_shards_of_a_group.rb +1 -1
- data/spec/migrations/5_create_users_on_multiples_groups.rb +1 -1
- data/spec/migrations/6_raise_exception_with_invalid_shard_name.rb +1 -1
- data/spec/migrations/7_raise_exception_with_invalid_multiple_shard_names.rb +1 -1
- data/spec/migrations/8_raise_exception_with_invalid_group_name.rb +1 -1
- data/spec/migrations/9_raise_exception_with_multiple_invalid_group_names.rb +1 -1
- data/spec/octopus/association_spec.rb +88 -70
- data/spec/octopus/log_subscriber_spec.rb +22 -0
- data/spec/octopus/logger_spec.rb +28 -15
- data/spec/octopus/migration_spec.rb +47 -43
- data/spec/octopus/model_spec.rb +179 -13
- data/spec/octopus/octopus_spec.rb +26 -4
- data/spec/octopus/proxy_spec.rb +61 -23
- data/spec/octopus/{replication_specs.rb → replication_spec.rb} +33 -26
- data/spec/octopus/scope_proxy_spec.rb +3 -3
- data/spec/octopus/sharded_spec.rb +9 -9
- data/spec/spec_helper.rb +10 -12
- data/spec/support/active_record/connection_adapters/modify_config_adapter.rb +17 -0
- data/spec/support/database_connection.rb +2 -0
- data/spec/{database_models.rb → support/database_models.rb} +27 -2
- data/spec/support/octopus_helper.rb +50 -0
- data/spec/tasks/octopus.rake_spec.rb +36 -0
- metadata +188 -169
- data/Gemfile.lock +0 -68
- data/lib/octopus/rails3/association.rb +0 -112
- data/spec/database_connection.rb +0 -4
- data/spec/octopus/controller_spec.rb +0 -34
- data/spec/octopus_helper.rb +0 -37
@@ -0,0 +1,17 @@
|
|
1
|
+
module Octopus
|
2
|
+
module Rails2
|
3
|
+
module Scope
|
4
|
+
def self.included(base)
|
5
|
+
base.instance_eval do
|
6
|
+
alias_method_chain :proxy_found, :octopus
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def proxy_found_with_octopus
|
11
|
+
load_found
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
ActiveRecord::NamedScope::Scope.send(:include, Octopus::Rails2::Scope)
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Octopus::SingularAssociation
|
2
|
+
def self.included(base)
|
3
|
+
base.instance_eval do
|
4
|
+
alias_method_chain :reader, :octopus
|
5
|
+
alias_method_chain :writer, :octopus
|
6
|
+
alias_method_chain :create, :octopus
|
7
|
+
alias_method_chain :create!, :octopus
|
8
|
+
alias_method_chain :build, :octopus
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def reader_with_octopus(*args)
|
13
|
+
owner.reload_connection_safe { reader_without_octopus(*args) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def writer_with_octopus(*args)
|
17
|
+
owner.reload_connection_safe { writer_without_octopus(*args) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def create_with_octopus(*args)
|
21
|
+
owner.reload_connection_safe { create_without_octopus(*args) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def create_with_octopus!(*args)
|
25
|
+
owner.reload_connection_safe { create_without_octopus!(*args) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def build_with_octopus(*args)
|
29
|
+
owner.reload_connection_safe { build_without_octopus(*args) }
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
ActiveRecord::Associations::SingularAssociation.send(:include, Octopus::SingularAssociation)
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# Implementation courtesy of db-charmer.
|
2
|
+
module Octopus
|
3
|
+
module AbstractAdapter
|
4
|
+
module OctopusShard
|
5
|
+
|
6
|
+
class InstrumenterDecorator < ActiveSupport::BasicObject
|
7
|
+
def initialize(adapter, instrumenter)
|
8
|
+
@adapter = adapter
|
9
|
+
@instrumenter = instrumenter
|
10
|
+
end
|
11
|
+
|
12
|
+
def instrument(name, payload = {}, &block)
|
13
|
+
payload[:octopus_shard] ||= @adapter.octopus_shard
|
14
|
+
@instrumenter.instrument(name, payload, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def method_missing(meth, *args, &block)
|
18
|
+
@instrumenter.send(meth, *args, &block)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.included(base)
|
23
|
+
base.alias_method_chain :initialize, :octopus_shard
|
24
|
+
end
|
25
|
+
|
26
|
+
def octopus_shard
|
27
|
+
@config[:octopus_shard]
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize_with_octopus_shard(*args)
|
31
|
+
initialize_without_octopus_shard(*args)
|
32
|
+
@instrumenter = InstrumenterDecorator.new(self, @instrumenter)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
ActiveRecord::ConnectionAdapters::AbstractAdapter.send(:include, Octopus::AbstractAdapter::OctopusShard)
|
data/lib/octopus/rails3/arel.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
class Arel::Visitors::ToSql
|
2
2
|
def quote value, column = nil
|
3
|
-
|
3
|
+
@connection.quote value, column
|
4
4
|
end
|
5
|
-
|
5
|
+
|
6
6
|
def quote_table_name name
|
7
|
-
|
7
|
+
@connection.quote_table_name(name)
|
8
8
|
end
|
9
9
|
|
10
10
|
def quote_column_name name
|
11
|
-
Arel::Nodes::SqlLiteral === name ? name :
|
11
|
+
Arel::Nodes::SqlLiteral === name ? name : @connection.quote_column_name(name)
|
12
12
|
end
|
13
|
-
end
|
13
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Implementation courtesy of db-charmer.
|
2
|
+
module Octopus
|
3
|
+
module LogSubscriber
|
4
|
+
def self.included(base)
|
5
|
+
base.send(:attr_accessor, :octopus_shard)
|
6
|
+
base.alias_method_chain :sql, :octopus_shard
|
7
|
+
base.alias_method_chain :debug, :octopus_shard
|
8
|
+
end
|
9
|
+
|
10
|
+
def sql_with_octopus_shard(event)
|
11
|
+
self.octopus_shard = event.payload[:octopus_shard]
|
12
|
+
sql_without_octopus_shard(event)
|
13
|
+
end
|
14
|
+
|
15
|
+
def debug_with_octopus_shard(msg)
|
16
|
+
conn = octopus_shard ? color("[Shard: #{octopus_shard}]", ActiveSupport::LogSubscriber::GREEN, true) : ''
|
17
|
+
debug_without_octopus_shard(conn + msg)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
ActiveRecord::LogSubscriber.send(:include, Octopus::LogSubscriber)
|
@@ -1,24 +1,24 @@
|
|
1
1
|
module Octopus
|
2
2
|
module Rails3
|
3
3
|
module Persistence
|
4
|
-
def update_attribute(
|
4
|
+
def update_attribute(*args)
|
5
5
|
reload_connection()
|
6
6
|
super
|
7
7
|
end
|
8
8
|
|
9
|
-
def update_attributes(
|
9
|
+
def update_attributes(*args)
|
10
10
|
reload_connection()
|
11
11
|
super
|
12
12
|
end
|
13
13
|
|
14
|
-
def update_attributes!(
|
14
|
+
def update_attributes!(*args)
|
15
15
|
reload_connection()
|
16
16
|
super
|
17
17
|
end
|
18
18
|
|
19
|
-
def reload(
|
19
|
+
def reload(*args)
|
20
20
|
reload_connection()
|
21
|
-
super
|
21
|
+
super
|
22
22
|
end
|
23
23
|
|
24
24
|
def delete
|
@@ -30,6 +30,11 @@ module Octopus
|
|
30
30
|
reload_connection()
|
31
31
|
super
|
32
32
|
end
|
33
|
+
|
34
|
+
def touch(name=nil)
|
35
|
+
reload_connection()
|
36
|
+
super
|
37
|
+
end
|
33
38
|
end
|
34
39
|
end
|
35
40
|
end
|
data/lib/octopus/scope_proxy.rb
CHANGED
@@ -1,40 +1,46 @@
|
|
1
1
|
class Octopus::ScopeProxy
|
2
2
|
attr_accessor :shard, :klass
|
3
|
-
|
3
|
+
|
4
4
|
def initialize(shard, klass)
|
5
5
|
@shard = shard
|
6
6
|
@klass = klass
|
7
7
|
end
|
8
|
-
|
8
|
+
|
9
9
|
def using(shard)
|
10
10
|
raise "Nonexistent Shard Name: #{shard}" if @klass.connection.instance_variable_get(:@shards)[shard].nil?
|
11
11
|
@shard = shard
|
12
12
|
return self
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
# Transaction Method send all queries to a specified shard.
|
16
16
|
def transaction(options = {}, &block)
|
17
17
|
@klass.connection.run_queries_on_shard(@shard) do
|
18
18
|
@klass = @klass.connection().transaction(options, &block)
|
19
|
-
end
|
19
|
+
end
|
20
20
|
end
|
21
|
-
|
21
|
+
|
22
22
|
def connection
|
23
23
|
@klass.connection().current_shard = @shard
|
24
24
|
@klass.connection()
|
25
25
|
end
|
26
|
-
|
27
|
-
def method_missing(method, *args, &block)
|
28
|
-
@klass.connection.run_queries_on_shard(@shard) do
|
29
|
-
@klass
|
26
|
+
|
27
|
+
def method_missing(method, *args, &block)
|
28
|
+
result = @klass.connection.run_queries_on_shard(@shard) do
|
29
|
+
@klass.send(method, *args, &block)
|
30
30
|
end
|
31
|
-
|
32
|
-
|
33
|
-
|
31
|
+
|
32
|
+
if result.respond_to?(:scoped)
|
33
|
+
@klass = result
|
34
|
+
return self
|
35
|
+
end
|
36
|
+
|
37
|
+
result
|
34
38
|
end
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
+
|
40
|
+
# Delegates to method_missing (instead of @klass) so that User.using(:blah).where(:name => "Mike")
|
41
|
+
# gets run in the correct shard context when #== is evaluated.
|
42
|
+
def ==(*args)
|
43
|
+
method_missing(:==, *args)
|
39
44
|
end
|
45
|
+
alias :eql? :==
|
40
46
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
namespace :octopus do
|
2
|
+
desc "Copy schema version information from master to all shards"
|
3
|
+
task :copy_schema_versions => :environment do
|
4
|
+
abort("Octopus is not enabled for this environment") unless Octopus.enabled?
|
5
|
+
|
6
|
+
connection = ActiveRecord::Base.connection
|
7
|
+
|
8
|
+
current_version = ActiveRecord::Migrator.current_version
|
9
|
+
migrations_paths = if Octopus.rails31? || Octopus.rails32?
|
10
|
+
ActiveRecord::Migrator.migrations_paths
|
11
|
+
else
|
12
|
+
ActiveRecord::Migrator.migrations_path
|
13
|
+
end
|
14
|
+
|
15
|
+
connection.send_queries_to_multiple_shards(connection.shard_names) do
|
16
|
+
ActiveRecord::Schema.initialize_schema_migrations_table
|
17
|
+
ActiveRecord::Schema.assume_migrated_upto_version(current_version, migrations_paths)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/sample_app/Gemfile
CHANGED
@@ -8,13 +8,13 @@ gem 'rails', '3.0.5'
|
|
8
8
|
gem 'sqlite3-ruby', :require => 'sqlite3'
|
9
9
|
gem 'ar-octopus', :git => 'git://github.com/tchandy/octopus.git', :require => "octopus"
|
10
10
|
|
11
|
-
group :test do
|
11
|
+
group :test do
|
12
12
|
gem 'capybara'
|
13
13
|
gem 'database_cleaner'
|
14
14
|
gem 'cucumber-rails'
|
15
15
|
gem 'cucumber'
|
16
16
|
gem 'spork'
|
17
|
-
gem 'launchy'
|
17
|
+
gem 'launchy'
|
18
18
|
gem "rspec-rails", ">= 2.0.0.beta.16"
|
19
19
|
gem 'ruby-debug' if RUBY_VERSION < "1.9"
|
20
20
|
gem "aruba"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# Be sure to restart your server when you modify this file.
|
2
2
|
|
3
|
-
# Add new inflection rules using the following format
|
3
|
+
# Add new inflection rules using the following format
|
4
4
|
# (all these examples are active by default):
|
5
5
|
# ActiveSupport::Inflector.inflections do |inflect|
|
6
6
|
# inflect.plural /^(ox)$/i, '\1en'
|
@@ -2,6 +2,6 @@
|
|
2
2
|
|
3
3
|
# Your secret key for verifying the integrity of signed cookies.
|
4
4
|
# If you change this key, all old signed cookies will become invalid!
|
5
|
-
# Make sure the secret is at least 30 characters and all random,
|
5
|
+
# Make sure the secret is at least 30 characters and all random,
|
6
6
|
# no regular words or you'll be exposed to dictionary attacks.
|
7
7
|
Rails.application.config.secret_token = '1b60fd7d627c1749f508e1578d14555c4c2122a8988e34f94c8e9dbe62df7f688462445badf79594a193542a32d0b1fdbfdf2c42539e212cbf6ad9b1c769c6f6'
|
data/sample_app/db/seeds.rb
CHANGED
@@ -4,16 +4,16 @@ Feature: rake db:migrate
|
|
4
4
|
I want to use the rake db:migrate command
|
5
5
|
|
6
6
|
Scenario: db:migrate should work with octopus
|
7
|
-
When I run inside my Rails project "rake db:migrate" with enviroment "development"
|
7
|
+
When I run inside my Rails project "rake db:migrate" with enviroment "development"
|
8
8
|
Then the output should contain "CreateUsers: migrating - Shard: master"
|
9
9
|
Then the output should contain "CreateUsers: migrating - Shard: asia"
|
10
10
|
Then the output should contain "CreateUsers: migrating - Shard: europe"
|
11
11
|
Then the output should contain "CreateUsers: migrating - Shard: america"
|
12
12
|
|
13
13
|
Scenario: db:migrate:redo should work with octopus
|
14
|
-
When I run inside my Rails project "rake db:migrate VERSION=20100720172715" with enviroment "development"
|
15
|
-
When I run inside my Rails project "rake db:migrate VERSION=20100720172730" with enviroment "development"
|
16
|
-
When I run inside my Rails project "rake db:migrate:redo" with enviroment "development"
|
14
|
+
When I run inside my Rails project "rake db:migrate VERSION=20100720172715" with enviroment "development"
|
15
|
+
When I run inside my Rails project "rake db:migrate VERSION=20100720172730" with enviroment "development"
|
16
|
+
When I run inside my Rails project "rake db:migrate:redo" with enviroment "development"
|
17
17
|
Then the output should contain "CreateItems: reverting - Shard: master"
|
18
18
|
Then the output should contain "CreateItems: reverting - Shard: asia"
|
19
19
|
Then the output should contain "CreateItems: reverting - Shard: europe"
|
@@ -22,9 +22,9 @@ Feature: rake db:migrate
|
|
22
22
|
Then the output should contain "CreateItems: migrating - Shard: asia"
|
23
23
|
Then the output should contain "CreateItems: migrating - Shard: europe"
|
24
24
|
Then the output should contain "CreateItems: migrating - Shard: america"
|
25
|
-
|
25
|
+
|
26
26
|
Scenario: db:migrate finishing the migration
|
27
|
-
When I run inside my Rails project "rake db:migrate" with enviroment "development"
|
27
|
+
When I run inside my Rails project "rake db:migrate" with enviroment "development"
|
28
28
|
Then the output should contain "CreateSampleUsers: migrating - Shard: america"
|
29
29
|
Then the output should contain "CreateSampleUsers: migrating - Shard: master"
|
30
30
|
Then the output should contain "CreateSampleUsers: migrating - Shard: asia"
|
@@ -34,12 +34,12 @@ Feature: rake db:migrate
|
|
34
34
|
Then the version of "america" shard should be "nil"
|
35
35
|
Then the version of "europe" shard should be "nil"
|
36
36
|
Then the version of "asia" shard should be "nil"
|
37
|
-
|
37
|
+
|
38
38
|
Scenario: after running rake db:migrate
|
39
|
-
When I run inside my Rails project "rake db:abort_if_pending_migrations" with enviroment "development"
|
39
|
+
When I run inside my Rails project "rake db:abort_if_pending_migrations" with enviroment "development"
|
40
40
|
Then the output should contain "pending migrations"
|
41
|
-
When I run inside my Rails project "rake db:migrate" with enviroment "development"
|
42
|
-
When I run inside my Rails project "rake db:abort_if_pending_migrations RAILS_ENV=development" with enviroment "development"
|
41
|
+
When I run inside my Rails project "rake db:migrate" with enviroment "development"
|
42
|
+
When I run inside my Rails project "rake db:abort_if_pending_migrations RAILS_ENV=development" with enviroment "development"
|
43
43
|
Then the output should not contain "pending migrations"
|
44
|
-
|
45
|
-
|
44
|
+
|
45
|
+
|
@@ -4,12 +4,12 @@ Feature: rake db:seed
|
|
4
4
|
I want to use the rake db:seed command
|
5
5
|
|
6
6
|
Scenario: db:seed should fail
|
7
|
-
When I run inside my Rails project "rake db:seed" with enviroment "development"
|
7
|
+
When I run inside my Rails project "rake db:seed" with enviroment "development"
|
8
8
|
Then the output should contain "pending migrations"
|
9
|
-
|
9
|
+
|
10
10
|
Scenario: db:seed should work with octopus
|
11
11
|
When I run inside my Rails project "rake db:migrate" with enviroment "development"
|
12
|
-
When I run inside my Rails project "rake db:seed" with enviroment "development"
|
12
|
+
When I run inside my Rails project "rake db:seed" with enviroment "development"
|
13
13
|
Then the "asia" shard should have one user named "Asia User"
|
14
14
|
Then the "america" shard should have one user named "America User 1"
|
15
15
|
Then the "america" shard should have one user named "America User 2"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril.
|
2
|
-
# It is recommended to regenerate this file in the future when you upgrade to a
|
3
|
-
# newer version of cucumber-rails. Consider adding your own code to a new file
|
2
|
+
# It is recommended to regenerate this file in the future when you upgrade to a
|
3
|
+
# newer version of cucumber-rails. Consider adding your own code to a new file
|
4
4
|
# instead of editing this one. Cucumber will automatically load all features/**/*.rb
|
5
5
|
# files.
|
6
6
|
|
@@ -191,7 +191,7 @@ Then /^the "([^"]*)" checkbox(?: within "([^"]*)")? should not be checked$/ do |
|
|
191
191
|
end
|
192
192
|
end
|
193
193
|
end
|
194
|
-
|
194
|
+
|
195
195
|
Then /^(?:|I )should be on (.+)$/ do |page_name|
|
196
196
|
current_path = URI.parse(current_url).path
|
197
197
|
if current_path.respond_to? :should
|
@@ -205,8 +205,8 @@ Then /^(?:|I )should have the following query string:$/ do |expected_pairs|
|
|
205
205
|
query = URI.parse(current_url).query
|
206
206
|
actual_params = query ? CGI.parse(query) : {}
|
207
207
|
expected_params = {}
|
208
|
-
expected_pairs.rows_hash.each_pair{|k,v| expected_params[k] = v.split(',')}
|
209
|
-
|
208
|
+
expected_pairs.rows_hash.each_pair{|k,v| expected_params[k] = v.split(',')}
|
209
|
+
|
210
210
|
if actual_params.respond_to? :should
|
211
211
|
actual_params.should == expected_params
|
212
212
|
else
|