ar-octopus 0.4.0 → 0.5.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.
- 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
|