ar-octopus 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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)