ar-octopus 0.8.2 → 0.8.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +9 -9
- data/.rspec +1 -1
- data/.rubocop.yml +46 -0
- data/.rubocop_todo.yml +52 -0
- data/.ruby-version +1 -1
- data/.travis.yml +3 -9
- data/Appraisals +4 -0
- data/Rakefile +17 -16
- data/ar-octopus.gemspec +22 -16
- data/gemfiles/rails41.gemfile +7 -0
- data/init.rb +1 -1
- data/lib/ar-octopus.rb +1 -1
- data/lib/octopus.rb +38 -37
- data/lib/octopus/abstract_adapter.rb +0 -2
- data/lib/octopus/association.rb +8 -6
- data/lib/octopus/association_shard_tracking.rb +80 -81
- data/lib/octopus/collection_association.rb +7 -5
- data/lib/octopus/collection_proxy.rb +11 -9
- data/lib/octopus/has_and_belongs_to_many_association.rb +5 -3
- data/lib/octopus/load_balancing.rb +3 -2
- data/lib/octopus/load_balancing/round_robin.rb +12 -8
- data/lib/octopus/migration.rb +117 -108
- data/lib/octopus/model.rb +130 -134
- data/lib/octopus/persistence.rb +1 -1
- data/lib/octopus/proxy.rb +345 -339
- data/lib/octopus/railtie.rb +2 -2
- data/lib/octopus/relation_proxy.rb +6 -1
- data/lib/octopus/scope_proxy.rb +38 -36
- data/lib/octopus/shard_tracking.rb +36 -35
- data/lib/octopus/shard_tracking/attribute.rb +12 -14
- data/lib/octopus/shard_tracking/dynamic.rb +7 -3
- data/lib/octopus/singular_association.rb +5 -3
- data/lib/octopus/slave_group.rb +10 -8
- data/lib/octopus/version.rb +1 -1
- data/rails/init.rb +1 -1
- data/sample_app/autotest/discover.rb +2 -2
- data/sample_app/config/application.rb +1 -1
- data/sample_app/config/boot.rb +1 -1
- data/sample_app/config/environments/test.rb +1 -1
- data/sample_app/config/initializers/session_store.rb +1 -1
- data/sample_app/config/initializers/wrap_parameters.rb +1 -1
- data/sample_app/config/routes.rb +1 -1
- data/sample_app/db/migrate/20100720210335_create_sample_users.rb +2 -2
- data/sample_app/db/schema.rb +10 -10
- data/sample_app/db/seeds.rb +3 -3
- data/sample_app/features/step_definitions/seeds_steps.rb +4 -4
- data/sample_app/features/step_definitions/web_steps.rb +3 -4
- data/sample_app/features/support/env.rb +3 -4
- data/sample_app/features/support/paths.rb +4 -4
- data/sample_app/spec/spec_helper.rb +3 -3
- data/spec/migrations/10_create_users_using_replication.rb +3 -3
- data/spec/migrations/11_add_field_in_all_slaves.rb +3 -3
- data/spec/migrations/12_create_users_using_block.rb +7 -7
- data/spec/migrations/13_create_users_using_block_and_using.rb +4 -4
- data/spec/migrations/14_create_users_on_shards_of_a_group_with_versions.rb +2 -2
- data/spec/migrations/15_create_user_on_shards_of_default_group_with_versions.rb +2 -2
- data/spec/migrations/1_create_users_on_master.rb +3 -3
- data/spec/migrations/2_create_users_on_canada.rb +3 -3
- data/spec/migrations/3_create_users_on_both_shards.rb +3 -3
- data/spec/migrations/4_create_users_on_shards_of_a_group.rb +3 -3
- data/spec/migrations/5_create_users_on_multiples_groups.rb +2 -2
- data/spec/migrations/6_raise_exception_with_invalid_shard_name.rb +3 -3
- data/spec/migrations/7_raise_exception_with_invalid_multiple_shard_names.rb +3 -3
- data/spec/migrations/8_raise_exception_with_invalid_group_name.rb +3 -3
- data/spec/migrations/9_raise_exception_with_multiple_invalid_group_names.rb +4 -4
- data/spec/octopus/association_shard_tracking_spec.rb +413 -417
- data/spec/octopus/collection_proxy_spec.rb +6 -5
- data/spec/octopus/log_subscriber_spec.rb +4 -4
- data/spec/octopus/migration_spec.rb +48 -48
- data/spec/octopus/model_spec.rb +267 -292
- data/spec/octopus/octopus_spec.rb +40 -41
- data/spec/octopus/proxy_spec.rb +124 -124
- data/spec/octopus/relation_proxy_spec.rb +32 -32
- data/spec/octopus/replicated_slave_grouped_spec.rb +23 -23
- data/spec/octopus/replication_spec.rb +61 -66
- data/spec/octopus/scope_proxy_spec.rb +56 -10
- data/spec/octopus/sharded_replicated_slave_grouped_spec.rb +29 -29
- data/spec/octopus/sharded_spec.rb +10 -10
- data/spec/spec_helper.rb +6 -6
- data/spec/support/active_record/connection_adapters/modify_config_adapter.rb +1 -3
- data/spec/support/database_connection.rb +2 -2
- data/spec/support/database_models.rb +16 -17
- data/spec/support/octopus_helper.rb +19 -21
- data/spec/support/query_count.rb +1 -3
- data/spec/support/shared_contexts.rb +3 -3
- data/spec/tasks/octopus.rake_spec.rb +10 -10
- metadata +43 -26
data/lib/octopus/railtie.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
begin
|
2
|
-
require
|
2
|
+
require 'rails/railtie'
|
3
3
|
|
4
4
|
module Octopus
|
5
5
|
class Railtie < Rails::Railtie
|
6
6
|
rake_tasks do
|
7
|
-
Dir[File.join(File.dirname(__FILE__),
|
7
|
+
Dir[File.join(File.dirname(__FILE__), '../tasks/*.rake')].each { |ext| load ext }
|
8
8
|
end
|
9
9
|
end
|
10
10
|
end
|
@@ -18,6 +18,11 @@ module Octopus
|
|
18
18
|
end
|
19
19
|
|
20
20
|
# these methods are not normally sent to method_missing
|
21
|
+
|
22
|
+
def select(*args, &block)
|
23
|
+
method_missing(:select, *args, &block)
|
24
|
+
end
|
25
|
+
|
21
26
|
def inspect
|
22
27
|
method_missing(:inspect)
|
23
28
|
end
|
@@ -34,6 +39,6 @@ module Octopus
|
|
34
39
|
method_missing(:==, other)
|
35
40
|
end
|
36
41
|
end
|
37
|
-
|
42
|
+
alias_method :eql?, :==
|
38
43
|
end
|
39
44
|
end
|
data/lib/octopus/scope_proxy.rb
CHANGED
@@ -1,47 +1,49 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
module Octopus
|
2
|
+
class ScopeProxy
|
3
|
+
include Octopus::ShardTracking::Attribute
|
4
|
+
attr_accessor :klass
|
5
|
+
|
6
|
+
def initialize(shard, klass)
|
7
|
+
@current_shard = shard
|
8
|
+
@klass = klass
|
9
|
+
end
|
4
10
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
11
|
+
def using(shard)
|
12
|
+
fail "Nonexistent Shard Name: #{shard}" if @klass.connection.instance_variable_get(:@shards)[shard].nil?
|
13
|
+
@current_shard = shard
|
14
|
+
self
|
15
|
+
end
|
9
16
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
end
|
17
|
+
# Transaction Method send all queries to a specified shard.
|
18
|
+
def transaction(options = {}, &block)
|
19
|
+
run_on_shard { @klass = klass.transaction(options, &block) }
|
20
|
+
end
|
15
21
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
22
|
+
def connection
|
23
|
+
@klass.connection.current_shard = @current_shard
|
24
|
+
@klass.connection
|
25
|
+
end
|
20
26
|
|
21
|
-
|
22
|
-
|
23
|
-
@klass.connection()
|
24
|
-
end
|
27
|
+
def method_missing(method, *args, &block)
|
28
|
+
result = run_on_shard { @klass.send(method, *args, &block) }
|
25
29
|
|
26
|
-
|
27
|
-
|
30
|
+
if result.respond_to?(:all)
|
31
|
+
@klass = result
|
32
|
+
return self
|
33
|
+
end
|
28
34
|
|
29
|
-
|
30
|
-
@klass = result
|
31
|
-
return self
|
35
|
+
result
|
32
36
|
end
|
33
37
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
def as_json(options = nil)
|
38
|
-
method_missing(:as_json, options)
|
39
|
-
end
|
38
|
+
def as_json(options = nil)
|
39
|
+
method_missing(:as_json, options)
|
40
|
+
end
|
40
41
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
42
|
+
# Delegates to method_missing (instead of @klass) so that User.using(:blah).where(:name => "Mike")
|
43
|
+
# gets run in the correct shard context when #== is evaluated.
|
44
|
+
def ==(other)
|
45
|
+
method_missing(:==, other)
|
46
|
+
end
|
47
|
+
alias_method :eql?, :==
|
45
48
|
end
|
46
|
-
alias :eql? :==
|
47
49
|
end
|
@@ -1,45 +1,46 @@
|
|
1
|
-
module Octopus
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
module ClassMethods
|
7
|
-
# If the class which includes this module responds to the class
|
8
|
-
# method sharded_methods, then automagically alias_method_chain
|
9
|
-
# a sharding-friendly version of each of those methods into existence
|
10
|
-
def sharded_methods(*methods)
|
11
|
-
methods.each { |m| create_sharded_method(m) }
|
1
|
+
module Octopus
|
2
|
+
module ShardTracking
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
12
5
|
end
|
13
6
|
|
14
|
-
|
15
|
-
|
16
|
-
method,
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
7
|
+
module ClassMethods
|
8
|
+
# If the class which includes this module responds to the class
|
9
|
+
# method sharded_methods, then automagically alias_method_chain
|
10
|
+
# a sharding-friendly version of each of those methods into existence
|
11
|
+
def sharded_methods(*methods)
|
12
|
+
methods.each { |m| create_sharded_method(m) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def create_sharded_method(name)
|
16
|
+
name.to_s =~ /([^!?]+)([!?])?/
|
17
|
+
method, punctuation = [Regexp.last_match[1], Regexp.last_match[2]]
|
18
|
+
with = :"#{method}_with_octopus#{punctuation}"
|
19
|
+
without = :"#{method}_without_octopus#{punctuation}"
|
20
|
+
define_method with do |*args, &block|
|
21
|
+
run_on_shard { send(without, *args, &block) }
|
22
|
+
end
|
23
|
+
alias_method_chain name.to_sym, :octopus
|
21
24
|
end
|
22
|
-
alias_method_chain name.to_sym, :octopus
|
23
25
|
end
|
24
|
-
end
|
25
26
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
27
|
+
# Adds run_on_shard method, but does not implement current_shard method
|
28
|
+
def run_on_shard(&block)
|
29
|
+
if (cs = current_shard)
|
30
|
+
r = ActiveRecord::Base.connection_proxy.run_queries_on_shard(cs, &block)
|
31
|
+
# Use a case statement to avoid any path through ActiveRecord::Delegation's
|
32
|
+
# respond_to? code. We want to avoid the respond_to? code because it can have
|
33
|
+
# the side effect of causing a call to load_target
|
34
|
+
# return r
|
35
|
+
case r
|
36
|
+
when ActiveRecord::Relation
|
37
|
+
Octopus::RelationProxy.new(cs, r)
|
38
|
+
else
|
39
|
+
r
|
40
|
+
end
|
38
41
|
else
|
39
|
-
|
42
|
+
yield
|
40
43
|
end
|
41
|
-
else
|
42
|
-
yield
|
43
44
|
end
|
44
45
|
end
|
45
46
|
end
|
@@ -1,24 +1,22 @@
|
|
1
1
|
# Adds current_shard as an attribute; provide a default
|
2
2
|
# implementation of set_current_shard which considers
|
3
3
|
# only the current ActiveRecord::Base.connection_proxy
|
4
|
-
module Octopus
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
4
|
+
module Octopus
|
5
|
+
module ShardTracking
|
6
|
+
module Attribute
|
7
|
+
def self.included(base)
|
8
|
+
base.send(:include, Octopus::ShardTracking)
|
9
|
+
end
|
10
10
|
|
11
|
-
module ClassMethods
|
12
|
-
def track_current_shard_as_attribute
|
13
11
|
attr_accessor :current_shard
|
14
|
-
end
|
15
|
-
end
|
16
12
|
|
17
|
-
|
18
|
-
|
13
|
+
def set_current_shard
|
14
|
+
return unless Octopus.enabled?
|
19
15
|
|
20
|
-
|
21
|
-
|
16
|
+
if ActiveRecord::Base.connection_proxy.block
|
17
|
+
self.current_shard = ActiveRecord::Base.connection_proxy.current_shard
|
18
|
+
end
|
19
|
+
end
|
22
20
|
end
|
23
21
|
end
|
24
22
|
end
|
@@ -1,7 +1,11 @@
|
|
1
1
|
require 'octopus/shard_tracking'
|
2
2
|
|
3
|
-
module Octopus
|
4
|
-
|
5
|
-
|
3
|
+
module Octopus
|
4
|
+
module ShardTracking
|
5
|
+
module Dynamic
|
6
|
+
def self.included(base)
|
7
|
+
base.send(:include, Octopus::ShardTracking)
|
8
|
+
end
|
9
|
+
end
|
6
10
|
end
|
7
11
|
end
|
@@ -1,6 +1,8 @@
|
|
1
|
-
module Octopus
|
2
|
-
|
3
|
-
base
|
1
|
+
module Octopus
|
2
|
+
module SingularAssociation
|
3
|
+
def self.included(base)
|
4
|
+
base.sharded_methods :reader, :writer, :create, :create!, :build
|
5
|
+
end
|
4
6
|
end
|
5
7
|
end
|
6
8
|
|
data/lib/octopus/slave_group.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
1
|
+
module Octopus
|
2
|
+
class SlaveGroup
|
3
|
+
def initialize(slaves)
|
4
|
+
slaves = HashWithIndifferentAccess.new(slaves)
|
5
|
+
slaves_list = slaves.values
|
6
|
+
@load_balancer = Octopus::LoadBalancing::RoundRobin.new(slaves_list)
|
7
|
+
end
|
7
8
|
|
8
|
-
|
9
|
-
|
9
|
+
def next
|
10
|
+
@load_balancer.next
|
11
|
+
end
|
10
12
|
end
|
11
13
|
end
|
data/lib/octopus/version.rb
CHANGED
data/rails/init.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
require
|
1
|
+
require 'octopus'
|
@@ -1,2 +1,2 @@
|
|
1
|
-
Autotest.add_discovery {
|
2
|
-
Autotest.add_discovery {
|
1
|
+
Autotest.add_discovery { 'rails' }
|
2
|
+
Autotest.add_discovery { 'rspec2' }
|
@@ -34,7 +34,7 @@ module SampleApp
|
|
34
34
|
# config.i18n.default_locale = :de
|
35
35
|
|
36
36
|
# Configure the default encoding used in templates for Ruby 1.9.
|
37
|
-
config.encoding =
|
37
|
+
config.encoding = 'utf-8'
|
38
38
|
|
39
39
|
# Configure sensitive parameters which will be filtered from the log file.
|
40
40
|
config.filter_parameters += [:password]
|
data/sample_app/config/boot.rb
CHANGED
@@ -9,7 +9,7 @@ SampleApp::Application.configure do
|
|
9
9
|
|
10
10
|
# Configure static asset server for tests with Cache-Control for performance
|
11
11
|
config.serve_static_assets = true
|
12
|
-
config.static_cache_control =
|
12
|
+
config.static_cache_control = 'public, max-age=3600'
|
13
13
|
|
14
14
|
# Log error messages when you accidentally call methods on nil
|
15
15
|
config.whiny_nils = true
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# Be sure to restart your server when you modify this file.
|
2
2
|
|
3
|
-
SampleApp::Application.config.session_store :cookie_store, key
|
3
|
+
SampleApp::Application.config.session_store :cookie_store, :key => '_sample_app_session'
|
4
4
|
|
5
5
|
# Use the database for sessions instead of the cookie-based default,
|
6
6
|
# which shouldn't be used to store highly confidential information
|
@@ -5,7 +5,7 @@
|
|
5
5
|
|
6
6
|
# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
|
7
7
|
ActiveSupport.on_load(:action_controller) do
|
8
|
-
wrap_parameters format
|
8
|
+
wrap_parameters :format => [:json]
|
9
9
|
end
|
10
10
|
|
11
11
|
# Disable root element in JSON by default.
|
data/sample_app/config/routes.rb
CHANGED
@@ -2,10 +2,10 @@ class CreateSampleUsers < ActiveRecord::Migration
|
|
2
2
|
using(:master, :asia, :europe, :america)
|
3
3
|
|
4
4
|
def self.up
|
5
|
-
User.create!(:name =>
|
5
|
+
User.create!(:name => 'Exception')
|
6
6
|
end
|
7
7
|
|
8
8
|
def self.down
|
9
|
-
User.find_by_name(
|
9
|
+
User.find_by_name('Exception').delete
|
10
10
|
end
|
11
11
|
end
|
data/sample_app/db/schema.rb
CHANGED
@@ -11,19 +11,19 @@
|
|
11
11
|
#
|
12
12
|
# It's strongly recommended to check this file into your version control system.
|
13
13
|
|
14
|
-
ActiveRecord::Schema.define(:version =>
|
14
|
+
ActiveRecord::Schema.define(:version => 20_100_720_210_335) do
|
15
15
|
|
16
|
-
create_table
|
17
|
-
t.string
|
18
|
-
t.integer
|
19
|
-
t.datetime
|
20
|
-
t.datetime
|
16
|
+
create_table 'items', :force => true do |t|
|
17
|
+
t.string 'name'
|
18
|
+
t.integer 'user_id'
|
19
|
+
t.datetime 'created_at', :null => false
|
20
|
+
t.datetime 'updated_at', :null => false
|
21
21
|
end
|
22
22
|
|
23
|
-
create_table
|
24
|
-
t.string
|
25
|
-
t.datetime
|
26
|
-
t.datetime
|
23
|
+
create_table 'users', :force => true do |t|
|
24
|
+
t.string 'name'
|
25
|
+
t.datetime 'created_at', :null => false
|
26
|
+
t.datetime 'updated_at', :null => false
|
27
27
|
end
|
28
28
|
|
29
29
|
end
|
data/sample_app/db/seeds.rb
CHANGED
@@ -6,11 +6,11 @@
|
|
6
6
|
# cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }])
|
7
7
|
# Mayor.create(:name => 'Daley', :city => cities.first)
|
8
8
|
Octopus.using(:asia) do
|
9
|
-
User.create!(:name =>
|
9
|
+
User.create!(:name => 'Asia User')
|
10
10
|
end
|
11
11
|
|
12
12
|
Octopus.using(:america) do
|
13
|
-
|
13
|
+
User.create([{ :name => 'America User 1' }, { :name => 'America User 2' }])
|
14
14
|
end
|
15
15
|
|
16
|
-
User.create!(:name =>
|
16
|
+
User.create!(:name => 'Teste')
|
@@ -3,11 +3,11 @@ Then /^the "([^"]*)" shard should have one user named "([^"]*)"$/ do |shard_name
|
|
3
3
|
end
|
4
4
|
|
5
5
|
Then /^the version of "([^"]*)" shard should be "([^"]*)"$/ do |shard_name, version|
|
6
|
-
ab = ActiveRecord::Base.using(shard_name.to_sym).connection.select_value(
|
7
|
-
version =
|
6
|
+
ab = ActiveRecord::Base.using(shard_name.to_sym).connection.select_value('select * from schema_migrations order by version desc limit 1;')
|
7
|
+
version = '' if version == 'nil'
|
8
8
|
ab.to_s.should == version
|
9
9
|
end
|
10
10
|
|
11
11
|
When /^I run inside my Rails project "([^"]*)" with enviroment "([^"]*)"$/ do |command, enviroment|
|
12
|
-
run("cd #{Rails.root
|
13
|
-
end
|
12
|
+
run("cd #{Rails.root} && RAILS_ENV=#{enviroment} #{command}")
|
13
|
+
end
|
@@ -4,10 +4,9 @@
|
|
4
4
|
# instead of editing this one. Cucumber will automatically load all features/**/*.rb
|
5
5
|
# files.
|
6
6
|
|
7
|
-
|
8
7
|
require 'uri'
|
9
8
|
require 'cgi'
|
10
|
-
require File.expand_path(File.join(File.dirname(__FILE__),
|
9
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'support', 'paths'))
|
11
10
|
|
12
11
|
module WithinHelpers
|
13
12
|
def with_scope(locator)
|
@@ -62,7 +61,7 @@ end
|
|
62
61
|
When /^(?:|I )fill in the following(?: within "([^"]*)")?:$/ do |selector, fields|
|
63
62
|
with_scope(selector) do
|
64
63
|
fields.rows_hash.each do |name, value|
|
65
|
-
When %
|
64
|
+
When %(I fill in "#{name}" with "#{value}")
|
66
65
|
end
|
67
66
|
end
|
68
67
|
end
|
@@ -205,7 +204,7 @@ Then /^(?:|I )should have the following query string:$/ do |expected_pairs|
|
|
205
204
|
query = URI.parse(current_url).query
|
206
205
|
actual_params = query ? CGI.parse(query) : {}
|
207
206
|
expected_params = {}
|
208
|
-
expected_pairs.rows_hash.each_pair{|k,v| expected_params[k] = v.split(',')}
|
207
|
+
expected_pairs.rows_hash.each_pair { |k, v| expected_params[k] = v.split(',') }
|
209
208
|
|
210
209
|
if actual_params.respond_to? :should
|
211
210
|
actual_params.should == expected_params
|