ar-octopus 0.0.2 → 0.0.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.
data/.gitignore CHANGED
@@ -1 +1,2 @@
1
1
  pkg/*.*
2
+ tmp/*
data/Rakefile CHANGED
@@ -1,6 +1,22 @@
1
1
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib'))
2
2
  require 'rubygems'
3
3
  require 'rake'
4
+ require "yaml"
5
+ require "active_support"
6
+ require 'active_support/json'
7
+ begin
8
+ require 'metric_fu'
9
+ MetricFu::Configuration.run do |config|
10
+ config.metrics = [:churn,:flay, :flog, :reek, :roodi, :saikuro]
11
+ config.graphs = [:flog, :flay, :reek, :roodi]
12
+ config.flay = { :dirs_to_flay => ['spec', 'lib'] }
13
+ config.flog = { :dirs_to_flog => ['spec', 'lib'] }
14
+ config.reek = { :dirs_to_reek => ['spec', 'lib'] }
15
+ config.roodi = { :dirs_to_roodi => ['spec', 'lib'] }
16
+ config.churn = { :start_date => "1 year ago", :minimum_churn_count => 10}
17
+ end
18
+ rescue LoadError
19
+ end
4
20
 
5
21
  begin
6
22
  require 'jeweler'
@@ -12,7 +28,7 @@ begin
12
28
  gem.homepage = "http://github.com/tchandy/octopus"
13
29
  gem.authors = ["Thiago Pradi", "Mike Perham", "Amit Agarwal"]
14
30
  gem.add_development_dependency "rspec", ">= 1.2.9"
15
- gem.version = "0.0.2"
31
+ gem.version = "0.0.3"
16
32
  end
17
33
  Jeweler::GemcutterTasks.new
18
34
  rescue LoadError
@@ -76,6 +92,8 @@ namespace :db do
76
92
  [:master, :brazil, :canada, :russia, :alone_shard, :postgresql_shard].each do |shard_symbol|
77
93
  ActiveRecord::Base.using(shard_symbol).connection.create_table(:users) do |u|
78
94
  u.string :name
95
+ u.integer :number
96
+ u.boolean :admin
79
97
  end
80
98
 
81
99
  ActiveRecord::Base.using(shard_symbol).connection.create_table(:clients) do |u|
@@ -95,6 +113,41 @@ namespace :db do
95
113
  ActiveRecord::Base.using(shard_symbol).connection.create_table(:schema_migrations) do |u|
96
114
  u.string :version, :unique => true, :null => false
97
115
  end
116
+
117
+ ActiveRecord::Base.using(shard_symbol).connection.create_table(:computers) do |u|
118
+ u.string :name
119
+ end
120
+
121
+ ActiveRecord::Base.using(shard_symbol).connection.create_table(:keyboards) do |u|
122
+ u.string :name
123
+ u.integer :computer_id
124
+ end
125
+
126
+ ActiveRecord::Base.using(shard_symbol).connection.create_table(:roles) do |u|
127
+ u.string :name
128
+ end
129
+
130
+ ActiveRecord::Base.using(shard_symbol).connection.create_table(:permissions) do |u|
131
+ u.string :name
132
+ end
133
+
134
+ ActiveRecord::Base.using(shard_symbol).connection.create_table(:permissions_roles, :id => false) do |u|
135
+ u.integer :role_id
136
+ u.integer :permission_id
137
+ end
138
+
139
+ ActiveRecord::Base.using(shard_symbol).connection.create_table(:assignments) do |u|
140
+ u.integer :programmer_id
141
+ u.integer :project_id
142
+ end
143
+
144
+ ActiveRecord::Base.using(shard_symbol).connection.create_table(:programmers) do |u|
145
+ u.string :name
146
+ end
147
+
148
+ ActiveRecord::Base.using(shard_symbol).connection.create_table(:projects) do |u|
149
+ u.string :name
150
+ end
98
151
  end
99
152
  end
100
153
 
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{ar-octopus}
8
- s.version = "0.0.2"
8
+ s.version = "0.0.3"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Thiago Pradi", "Mike Perham", "Amit Agarwal"]
12
- s.date = %q{2010-06-14}
12
+ s.date = %q{2010-06-22}
13
13
  s.description = %q{This gem allows you to use sharded databases with ActiveRecord. this also provides a interface for replication and for running migrations with multiples shards.}
14
14
  s.email = %q{tchandy@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -26,7 +26,10 @@ Gem::Specification.new do |s|
26
26
  "doc/libraries.textile",
27
27
  "doc/shards.yml",
28
28
  "lib/octopus.rb",
29
+ "lib/octopus/association.rb",
30
+ "lib/octopus/association_collection.rb",
29
31
  "lib/octopus/controller.rb",
32
+ "lib/octopus/has_and_belongs_to_many_association.rb",
30
33
  "lib/octopus/migration.rb",
31
34
  "lib/octopus/model.rb",
32
35
  "lib/octopus/proxy.rb",
@@ -44,6 +47,7 @@ Gem::Specification.new do |s|
44
47
  "spec/migrations/7_raise_exception_with_invalid_multiple_shard_names.rb",
45
48
  "spec/migrations/8_raise_exception_with_invalid_group_name.rb",
46
49
  "spec/migrations/9_raise_exception_with_multiple_invalid_group_names.rb",
50
+ "spec/octopus/association_spec.rb",
47
51
  "spec/octopus/controller_spec.rb",
48
52
  "spec/octopus/migration_spec.rb",
49
53
  "spec/octopus/model_spec.rb",
@@ -55,7 +59,7 @@ Gem::Specification.new do |s|
55
59
  s.homepage = %q{http://github.com/tchandy/octopus}
56
60
  s.rdoc_options = ["--charset=UTF-8"]
57
61
  s.require_paths = ["lib"]
58
- s.rubygems_version = %q{1.3.6}
62
+ s.rubygems_version = %q{1.3.7}
59
63
  s.summary = %q{Easy Database Sharding for ActiveRecord}
60
64
  s.test_files = [
61
65
  "spec/database_connection.rb",
@@ -71,6 +75,7 @@ Gem::Specification.new do |s|
71
75
  "spec/migrations/7_raise_exception_with_invalid_multiple_shard_names.rb",
72
76
  "spec/migrations/8_raise_exception_with_invalid_group_name.rb",
73
77
  "spec/migrations/9_raise_exception_with_multiple_invalid_group_names.rb",
78
+ "spec/octopus/association_spec.rb",
74
79
  "spec/octopus/controller_spec.rb",
75
80
  "spec/octopus/migration_spec.rb",
76
81
  "spec/octopus/model_spec.rb",
@@ -83,7 +88,7 @@ Gem::Specification.new do |s|
83
88
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
84
89
  s.specification_version = 3
85
90
 
86
- if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
91
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
87
92
  s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
88
93
  else
89
94
  s.add_dependency(%q<rspec>, [">= 1.2.9"])
@@ -2,17 +2,17 @@ require "yaml"
2
2
 
3
3
  module Octopus
4
4
  def self.env()
5
- @@env ||= defined?(Rails) ? Rails.env.to_s : "production"
5
+ @env ||= defined?(Rails) ? Rails.env.to_s : "production"
6
6
  end
7
7
 
8
8
  def self.config()
9
- @@config ||= YAML.load_file(Octopus.directory() + "/config/shards.yml")
9
+ @config ||= YAML.load_file(Octopus.directory() + "/config/shards.yml")
10
10
  end
11
11
 
12
12
  # Returns the Rails.root_to_s when you are using rails
13
13
  # Running the current directory in a generic Ruby process
14
14
  def self.directory()
15
- @@directory ||= defined?(Rails) ? Rails.root.to_s : Dir.pwd
15
+ @directory ||= defined?(Rails) ? Rails.root.to_s : Dir.pwd
16
16
  end
17
17
  end
18
18
 
@@ -20,3 +20,6 @@ require "octopus/proxy"
20
20
  require "octopus/migration"
21
21
  require "octopus/model"
22
22
  require "octopus/controller"
23
+ require "octopus/association"
24
+ require "octopus/association_collection"
25
+ require "octopus/has_and_belongs_to_many_association"
@@ -0,0 +1,109 @@
1
+ module Octopus::Association
2
+ def collection_reader_method(reflection, association_proxy_class)
3
+ define_method(reflection.name) do |*params|
4
+ force_reload = params.first unless params.empty?
5
+ reload_connection()
6
+
7
+ association = association_instance_get(reflection.name)
8
+
9
+ unless association
10
+ association = association_proxy_class.new(self, reflection)
11
+ association_instance_set(reflection.name, association)
12
+ end
13
+
14
+ reflection.klass.uncached { association.reload } if force_reload
15
+
16
+ association
17
+ end
18
+
19
+ define_method("#{reflection.name.to_s.singularize}_ids") do
20
+ reload_connection()
21
+ if send(reflection.name).loaded? || reflection.options[:finder_sql]
22
+ send(reflection.name).map(&:id)
23
+ else
24
+ if reflection.through_reflection && reflection.source_reflection.belongs_to?
25
+ through = reflection.through_reflection
26
+ primary_key = reflection.source_reflection.primary_key_name
27
+ send(through.name).select("DISTINCT #{through.quoted_table_name}.#{primary_key}").map!(&:"#{primary_key}")
28
+ else
29
+ send(reflection.name).select("#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").except(:includes).map!(&:id)
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ def association_constructor_method(constructor, reflection, association_proxy_class)
36
+ define_method("#{constructor}_#{reflection.name}") do |*params|
37
+ reload_connection()
38
+ attributees = params.first unless params.empty?
39
+ replace_existing = params[1].nil? ? true : params[1]
40
+ association = association_instance_get(reflection.name)
41
+
42
+ unless association
43
+ association = association_proxy_class.new(self, reflection)
44
+ association_instance_set(reflection.name, association)
45
+ end
46
+
47
+ if association_proxy_class == ActiveRecord::Associations::HasOneAssociation
48
+ return_val = association.send(constructor, attributees, replace_existing)
49
+ else
50
+ return_val = association.send(constructor, attributees)
51
+ end
52
+
53
+ if have_a_valid_shard?
54
+ return_val.current_shard = self.current_shard
55
+ end
56
+
57
+ return_val
58
+ end
59
+ end
60
+
61
+ def association_accessor_methods(reflection, association_proxy_class)
62
+ define_method(reflection.name) do |*params|
63
+ force_reload = true
64
+ reload_connection()
65
+ association = association_instance_get(reflection.name)
66
+
67
+ if association.nil? || force_reload
68
+ association = association_proxy_class.new(self, reflection)
69
+ retval = have_a_valid_shard? ? reflection.klass.uncached { self.class.connection_proxy.run_query_on_shard(self.current_shard) { association.reload } } : association.reload
70
+ if retval.nil? and association_proxy_class == ActiveRecord::Associations::BelongsToAssociation
71
+ association_instance_set(reflection.name, nil)
72
+ return nil
73
+ end
74
+ association_instance_set(reflection.name, association)
75
+ end
76
+
77
+ association
78
+ #association.target.nil? ? nil : association
79
+ end
80
+
81
+ define_method("loaded_#{reflection.name}?") do
82
+ reload_connection()
83
+ association = association_instance_get(reflection.name)
84
+ association && association.loaded?
85
+ end
86
+
87
+ define_method("#{reflection.name}=") do |new_value|
88
+ reload_connection()
89
+ association = association_instance_get(reflection.name)
90
+
91
+ if association.nil? || association.target != new_value
92
+ association = association_proxy_class.new(self, reflection)
93
+ end
94
+
95
+ association.replace(new_value)
96
+ association_instance_set(reflection.name, new_value.nil? ? nil : association)
97
+ end
98
+
99
+ define_method("set_#{reflection.name}_target") do |target|
100
+ return if target.nil? and association_proxy_class == ActiveRecord::Associations::BelongsToAssociation
101
+ reload_connection()
102
+ association = association_proxy_class.new(self, reflection)
103
+ association.target = target
104
+ association_instance_set(reflection.name, association)
105
+ end
106
+ end
107
+ end
108
+
109
+ ActiveRecord::Base.extend(Octopus::Association)
@@ -0,0 +1,40 @@
1
+ class ActiveRecord::Associations::AssociationCollection
2
+ def should_wrap_the_connection?
3
+ @owner.respond_to?(:current_shard) && @owner.current_shard != nil
4
+ end
5
+
6
+ def count(column_name = nil, options = {})
7
+ if @reflection.options[:counter_sql]
8
+ if should_wrap_the_connection?
9
+ @owner.using(@owner.current_shard) { @reflection.klass.count_by_sql(@counter_sql) }
10
+ else
11
+ @reflection.klass.count_by_sql(@counter_sql)
12
+ end
13
+ else
14
+ column_name, options = nil, column_name if column_name.is_a?(Hash)
15
+
16
+ if @reflection.options[:uniq]
17
+ # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
18
+ column_name = "#{@reflection.quoted_table_name}.#{@reflection.klass.primary_key}" unless column_name
19
+ options.merge!(:distinct => true)
20
+ end
21
+
22
+ value = @reflection.klass.send(:with_scope, construct_scope) do
23
+ if should_wrap_the_connection?
24
+ @owner.using(@owner.current_shard) { @reflection.klass.count(column_name, options) }
25
+ else
26
+ @reflection.klass.count(column_name, options)
27
+ end
28
+ end
29
+
30
+ limit = @reflection.options[:limit]
31
+ offset = @reflection.options[:offset]
32
+
33
+ if limit || offset
34
+ [ [value - offset.to_i, 0].max, limit.to_i ].min
35
+ else
36
+ value
37
+ end
38
+ end
39
+ end
40
+ end
@@ -1,14 +1,6 @@
1
1
  module Octopus::Controller
2
2
  def using(shard, &block)
3
- older_shard = ActiveRecord::Base.connection_proxy.current_shard
4
- ActiveRecord::Base.connection_proxy.block = true
5
- ActiveRecord::Base.connection_proxy.current_shard = shard
6
- begin
7
- yield
8
- ensure
9
- ActiveRecord::Base.connection_proxy.block = false
10
- ActiveRecord::Base.connection_proxy.current_shard = older_shard
11
- end
3
+ ActiveRecord::Base.connection_proxy.run_query_on_shard(shard, &block)
12
4
  end
13
5
  end
14
6
 
@@ -0,0 +1,43 @@
1
+ class ActiveRecord::Associations::HasAndBelongsToManyAssociation
2
+ def should_wrap_the_connection?
3
+ @owner.respond_to?(:current_shard) && @owner.current_shard != nil
4
+ end
5
+
6
+ def insert_record(record, force = true, validate = true)
7
+ if record.new_record?
8
+ if force
9
+ record.save!
10
+ else
11
+ return false unless record.save(:validate => validate)
12
+ end
13
+ end
14
+
15
+ if @reflection.options[:insert_sql]
16
+ @owner.connection.insert(interpolate_sql(@reflection.options[:insert_sql], record))
17
+ else
18
+ relation = Arel::Table.new(@reflection.options[:join_table])
19
+ attributes = columns.inject({}) do |attrs, column|
20
+ case column.name.to_s
21
+ when @reflection.primary_key_name.to_s
22
+ attrs[relation[column.name]] = owner_quoted_id
23
+ when @reflection.association_foreign_key.to_s
24
+ attrs[relation[column.name]] = record.quoted_id
25
+ else
26
+ if record.has_attribute?(column.name)
27
+ value = @owner.send(:quote_value, record[column.name], column)
28
+ attrs[relation[column.name]] = value unless value.nil?
29
+ end
30
+ end
31
+ attrs
32
+ end
33
+
34
+ if should_wrap_the_connection?
35
+ @owner.using(@owner.current_shard) { relation.insert(attributes) }
36
+ else
37
+ relation.insert(attributes)
38
+ end
39
+ end
40
+
41
+ return true
42
+ end
43
+ end
@@ -1,41 +1,34 @@
1
1
  module Octopus::Migration
2
- def self.included(base)
3
- base.extend(ClassMethods)
2
+ def self.extended(base)
4
3
  class << base
5
4
  def connection
6
5
  ActiveRecord::Base.connection_proxy()
7
6
  end
8
-
9
- def connected?
10
- ActiveRecord::Base.connection_proxy().connected?
11
- end
12
7
  end
13
8
  end
14
9
 
15
- module ClassMethods
16
- def using(*args)
17
- if args.size == 1
18
- self.connection().block = true
19
- self.connection().current_shard = args.first
20
- else
21
- self.connection().current_shard = args
22
- end
23
-
24
- return self
10
+ def using(*args)
11
+ if args.size == 1
12
+ self.connection().block = true
13
+ self.connection().current_shard = args.first
14
+ else
15
+ self.connection().current_shard = args
25
16
  end
26
17
 
27
- def using_group(*args)
28
- if args.size == 1
29
- self.connection().block = true
30
- self.connection().current_group = args.first
31
- else
32
- self.connection().current_group = args
33
- end
34
-
35
- return self
18
+ return self
19
+ end
20
+
21
+ def using_group(*args)
22
+ if args.size == 1
23
+ self.connection().block = true
24
+ self.connection().current_group = args.first
25
+ else
26
+ self.connection().current_group = args
36
27
  end
28
+
29
+ return self
37
30
  end
38
31
  end
39
32
 
40
33
 
41
- ActiveRecord::Migration.send(:include, Octopus::Migration)
34
+ ActiveRecord::Migration.extend(Octopus::Migration)
@@ -1,18 +1,54 @@
1
1
  module Octopus::Model
2
- def self.included(base)
3
- base.extend(ClassMethods)
2
+ def self.extended(base)
4
3
  base.send(:include, InstanceMethods)
5
4
  end
6
5
 
7
6
  module InstanceMethods
8
- def connection_proxy
9
- self.class.connection_proxy
7
+ def reload_connection()
8
+ set_connection() if have_a_valid_shard?
10
9
  end
11
-
12
- def using(shard, &block)
10
+
11
+ def update_attribute(name, value)
12
+ reload_connection()
13
+ super(name, value)
14
+ end
15
+
16
+ def update_attributes(attributes)
17
+ reload_connection()
18
+ super(attributes)
19
+ end
20
+
21
+ def update_attributes!(attributes)
22
+ reload_connection()
23
+ super(attributes)
24
+ end
25
+
26
+ def reload
27
+ reload_connection()
28
+ super
29
+ end
30
+
31
+ def hijack_initializer()
32
+ attr_accessor :current_shard
33
+ after_initialize :set_current_shard
34
+ before_save :set_connection
35
+ before_destroy :set_connection
36
+
37
+ def set_current_shard
38
+ if self.class.respond_to?(:connection_proxy) && self.respond_to?(:current_shard)
39
+ if self.new_record?
40
+ self.current_shard = self.class.connection_proxy.current_shard
41
+ else
42
+ self.current_shard = self.class.connection_proxy.last_current_shard
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ def hijack_connection()
13
49
  class << self
14
50
  def connection_proxy
15
- @@connection_proxy ||= Octopus::Proxy.new(Octopus.config())
51
+ Thread.current[:connection_proxy] ||= Octopus::Proxy.new(Octopus.config())
16
52
  end
17
53
 
18
54
  def connection
@@ -22,39 +58,71 @@ module Octopus::Model
22
58
 
23
59
  self.connection_proxy()
24
60
  end
25
-
26
- def connected?
27
- self.connection_proxy().connected?
28
- end
29
61
  end
30
-
31
- self.reset_table_name() if self != ActiveRecord::Base && self.respond_to?(:reset_table_name)
32
-
62
+ end
63
+
64
+ def using(shard, &block)
65
+ hijack_connection()
66
+ clean_table_name()
67
+
33
68
  if block_given?
34
- older_shard = self.connection_proxy.current_shard
35
- self.connection_proxy.block = true
36
- self.connection_proxy.current_shard = shard
37
- begin
38
- yield
39
- ensure
40
- self.connection_proxy.block = false
41
- self.connection_proxy.current_shard = older_shard
42
- end
69
+ self.connection_proxy.run_query_on_shard(shard, &block)
43
70
  else
71
+ hijack_initializer()
44
72
  self.connection_proxy.current_shard = shard
45
73
  self.connection_proxy.using_enabled = true
74
+
46
75
  return self
47
76
  end
48
77
  end
78
+
79
+ def have_a_valid_shard?
80
+ self.respond_to?(:current_shard) && self.current_shard != nil
81
+ end
82
+
83
+ def set_connection(*args)
84
+ if(args.size == 1)
85
+ arg = args.first
86
+ arg.current_shard = self.current_shard if arg.respond_to?(:current_shard) && have_a_valid_shard?
87
+ end
88
+
89
+ self.class.connection_proxy.current_shard = self.current_shard if have_a_valid_shard?
90
+ end
91
+
92
+ def clean_table_name
93
+ self.reset_table_name() if self != ActiveRecord::Base && self.respond_to?(:reset_table_name)
94
+ end
95
+ end
96
+
97
+ include InstanceMethods
98
+
99
+ def replicated_model()
100
+ self.cattr_accessor :replicated
101
+ end
102
+
103
+ def has_many(association_id, options = {}, &extension)
104
+ default_octopus_opts(options)
105
+ super(association_id, options, &extension)
49
106
  end
50
107
 
51
- module ClassMethods
52
- include InstanceMethods
108
+ def has_and_belongs_to_many(association_id, options = {}, &extension)
109
+ default_octopus_opts(options)
110
+ super(association_id, options, &extension)
111
+ end
53
112
 
54
- def replicated_model()
55
- self.cattr_accessor :replicated
113
+ def default_octopus_opts(options)
114
+ if options[:before_add].is_a?(Array)
115
+ options[:before_add] << :set_connection
116
+ else
117
+ options[:before_add] = :set_connection
56
118
  end
57
- end
119
+
120
+ if options[:before_remove].is_a?(Array)
121
+ options[:before_remove] << :set_connection
122
+ else
123
+ options[:before_remove] = :set_connection
124
+ end
125
+ end
58
126
  end
59
127
 
60
- ActiveRecord::Base.send(:include, Octopus::Model)
128
+ ActiveRecord::Base.extend(Octopus::Model)