ar-octopus 0.8.2 → 0.8.3
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.
- 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
|