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.
Files changed (77) hide show
  1. data/.gitignore +11 -0
  2. data/.travis.yml +22 -0
  3. data/Appraisals +18 -0
  4. data/Gemfile +3 -12
  5. data/README.mkdn +63 -24
  6. data/Rakefile +70 -92
  7. data/ar-octopus.gemspec +25 -198
  8. data/lib/ar-octopus.rb +1 -0
  9. data/lib/octopus.rb +73 -25
  10. data/lib/octopus/association.rb +6 -5
  11. data/lib/octopus/association_collection.rb +58 -4
  12. data/lib/octopus/has_and_belongs_to_many_association.rb +4 -4
  13. data/lib/octopus/logger.rb +9 -4
  14. data/lib/octopus/migration.rb +155 -50
  15. data/lib/octopus/model.rb +98 -34
  16. data/lib/octopus/proxy.rb +124 -53
  17. data/lib/octopus/rails2/association.rb +46 -93
  18. data/lib/octopus/rails2/persistence.rb +1 -1
  19. data/lib/octopus/rails2/scope.rb +17 -0
  20. data/lib/octopus/rails3.1/singular_association.rb +34 -0
  21. data/lib/octopus/rails3.2/persistence.rb +12 -0
  22. data/lib/octopus/rails3/abstract_adapter.rb +39 -0
  23. data/lib/octopus/rails3/arel.rb +5 -5
  24. data/lib/octopus/rails3/log_subscriber.rb +22 -0
  25. data/lib/octopus/rails3/persistence.rb +10 -5
  26. data/lib/octopus/railtie.rb +13 -0
  27. data/lib/octopus/scope_proxy.rb +22 -16
  28. data/lib/octopus/version.rb +3 -0
  29. data/lib/tasks/octopus.rake +20 -0
  30. data/sample_app/Gemfile +2 -2
  31. data/sample_app/config/initializers/inflections.rb +1 -1
  32. data/sample_app/config/initializers/secret_token.rb +1 -1
  33. data/sample_app/db/migrate/20100720172730_create_items.rb +1 -1
  34. data/sample_app/db/migrate/20100720210335_create_sample_users.rb +1 -1
  35. data/sample_app/db/seeds.rb +1 -1
  36. data/sample_app/features/migrate.feature +12 -12
  37. data/sample_app/features/seed.feature +3 -3
  38. data/sample_app/features/step_definitions/web_steps.rb +5 -5
  39. data/sample_app/features/support/env.rb +8 -8
  40. data/sample_app/lib/tasks/cucumber.rake +2 -2
  41. data/sample_app/public/javascripts/effects.js +1 -1
  42. data/spec/config/shards.yml +38 -28
  43. data/spec/migrations/11_add_field_in_all_slaves.rb +1 -1
  44. data/spec/migrations/12_create_users_using_block.rb +2 -2
  45. data/spec/migrations/13_create_users_using_block_and_using.rb +2 -2
  46. data/spec/migrations/14_create_users_on_shards_of_a_group_with_versions.rb +11 -0
  47. data/spec/migrations/1_create_users_on_master.rb +1 -1
  48. data/spec/migrations/2_create_users_on_canada.rb +1 -1
  49. data/spec/migrations/3_create_users_on_both_shards.rb +1 -1
  50. data/spec/migrations/4_create_users_on_shards_of_a_group.rb +1 -1
  51. data/spec/migrations/5_create_users_on_multiples_groups.rb +1 -1
  52. data/spec/migrations/6_raise_exception_with_invalid_shard_name.rb +1 -1
  53. data/spec/migrations/7_raise_exception_with_invalid_multiple_shard_names.rb +1 -1
  54. data/spec/migrations/8_raise_exception_with_invalid_group_name.rb +1 -1
  55. data/spec/migrations/9_raise_exception_with_multiple_invalid_group_names.rb +1 -1
  56. data/spec/octopus/association_spec.rb +88 -70
  57. data/spec/octopus/log_subscriber_spec.rb +22 -0
  58. data/spec/octopus/logger_spec.rb +28 -15
  59. data/spec/octopus/migration_spec.rb +47 -43
  60. data/spec/octopus/model_spec.rb +179 -13
  61. data/spec/octopus/octopus_spec.rb +26 -4
  62. data/spec/octopus/proxy_spec.rb +61 -23
  63. data/spec/octopus/{replication_specs.rb → replication_spec.rb} +33 -26
  64. data/spec/octopus/scope_proxy_spec.rb +3 -3
  65. data/spec/octopus/sharded_spec.rb +9 -9
  66. data/spec/spec_helper.rb +10 -12
  67. data/spec/support/active_record/connection_adapters/modify_config_adapter.rb +17 -0
  68. data/spec/support/database_connection.rb +2 -0
  69. data/spec/{database_models.rb → support/database_models.rb} +27 -2
  70. data/spec/support/octopus_helper.rb +50 -0
  71. data/spec/tasks/octopus.rake_spec.rb +36 -0
  72. metadata +188 -169
  73. data/Gemfile.lock +0 -68
  74. data/lib/octopus/rails3/association.rb +0 -112
  75. data/spec/database_connection.rb +0 -4
  76. data/spec/octopus/controller_spec.rb +0 -34
  77. data/spec/octopus_helper.rb +0 -37
@@ -2,7 +2,7 @@ module Octopus
2
2
  module Rails2
3
3
  module Persistence
4
4
  def self.included(base)
5
- base.instance_eval do
5
+ base.instance_eval do
6
6
  alias_method_chain :destroy, :octopus
7
7
  alias_method_chain :delete, :octopus
8
8
  alias_method_chain :reload, :octopus
@@ -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,12 @@
1
+ module Octopus
2
+ module Rails32
3
+ module Persistence
4
+ def update_column(*args)
5
+ reload_connection()
6
+ super
7
+ end
8
+ end
9
+ end
10
+ end
11
+
12
+ ActiveRecord::Base.send(:include, Octopus::Rails32::Persistence)
@@ -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)
@@ -1,13 +1,13 @@
1
1
  class Arel::Visitors::ToSql
2
2
  def quote value, column = nil
3
- ActiveRecord::Base.connection.quote value, column
3
+ @connection.quote value, column
4
4
  end
5
-
5
+
6
6
  def quote_table_name name
7
- ActiveRecord::Base.connection.quote_table_name(name)
7
+ @connection.quote_table_name(name)
8
8
  end
9
9
 
10
10
  def quote_column_name name
11
- Arel::Nodes::SqlLiteral === name ? name : ActiveRecord::Base.connection.quote_column_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(name, value)
4
+ def update_attribute(*args)
5
5
  reload_connection()
6
6
  super
7
7
  end
8
8
 
9
- def update_attributes(attributes)
9
+ def update_attributes(*args)
10
10
  reload_connection()
11
11
  super
12
12
  end
13
13
 
14
- def update_attributes!(attributes)
14
+ def update_attributes!(*args)
15
15
  reload_connection()
16
16
  super
17
17
  end
18
18
 
19
- def reload(options = nil)
19
+ def reload(*args)
20
20
  reload_connection()
21
- super(options)
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
@@ -0,0 +1,13 @@
1
+ begin
2
+ require "rails/railtie"
3
+
4
+ module Octopus
5
+ class Railtie < Rails::Railtie
6
+ rake_tasks do
7
+ Dir[File.join(File.dirname(__FILE__), "../tasks/*.rake")].each { |ext| load ext }
8
+ end
9
+ end
10
+ end
11
+ rescue LoadError
12
+ # nop
13
+ end
@@ -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 = @klass.send(method, *args, &block)
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
- return @klass if @klass.is_a?(ActiveRecord::Base) or @klass.is_a?(Array) or @klass.is_a?(Fixnum) or @klass.nil?
33
- return self
31
+
32
+ if result.respond_to?(:scoped)
33
+ @klass = result
34
+ return self
35
+ end
36
+
37
+ result
34
38
  end
35
-
36
- def ==(other)
37
- @shard == other.shard
38
- @klass == other.klass
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,3 @@
1
+ module Octopus
2
+ VERSION = '0.5.0'
3
+ 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
@@ -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'
@@ -1,6 +1,6 @@
1
1
  class CreateItems < ActiveRecord::Migration
2
2
  using(:master, :asia, :europe, :america)
3
-
3
+
4
4
  def self.up
5
5
  create_table :items do |t|
6
6
  t.string :name
@@ -1,6 +1,6 @@
1
1
  class CreateSampleUsers < ActiveRecord::Migration
2
2
  using(:master, :asia, :europe, :america)
3
-
3
+
4
4
  def self.up
5
5
  User.create!(:name => "Exception")
6
6
  end
@@ -9,7 +9,7 @@ Octopus.using(:asia) do
9
9
  User.create!(:name => "Asia User")
10
10
  end
11
11
 
12
- Octopus.using(:america) do
12
+ Octopus.using(:america) do
13
13
  users_america = User.create([{ :name => 'America User 1' }, { :name => 'America User 2' }])
14
14
  end
15
15
 
@@ -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