ar-octopus 0.8.1 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +8 -8
  2. data/README.mkdn +80 -55
  3. data/lib/octopus.rb +20 -6
  4. data/lib/octopus/{rails3/abstract_adapter.rb → abstract_adapter.rb} +1 -4
  5. data/lib/octopus/association.rb +5 -99
  6. data/lib/octopus/association_shard_tracking.rb +105 -0
  7. data/lib/octopus/collection_association.rb +9 -0
  8. data/lib/octopus/collection_proxy.rb +14 -0
  9. data/lib/octopus/has_and_belongs_to_many_association.rb +2 -12
  10. data/lib/octopus/load_balancing.rb +3 -0
  11. data/lib/octopus/load_balancing/round_robin.rb +15 -0
  12. data/lib/octopus/{rails3/log_subscriber.rb → log_subscriber.rb} +0 -0
  13. data/lib/octopus/model.rb +74 -85
  14. data/lib/octopus/{rails3/persistence.rb → persistence.rb} +0 -0
  15. data/lib/octopus/proxy.rb +166 -29
  16. data/lib/octopus/relation_proxy.rb +39 -0
  17. data/lib/octopus/scope_proxy.rb +7 -10
  18. data/lib/octopus/shard_tracking.rb +45 -0
  19. data/lib/octopus/shard_tracking/attribute.rb +24 -0
  20. data/lib/octopus/shard_tracking/dynamic.rb +7 -0
  21. data/lib/octopus/singular_association.rb +7 -0
  22. data/lib/octopus/slave_group.rb +11 -0
  23. data/lib/octopus/version.rb +1 -1
  24. data/spec/config/shards.yml +53 -0
  25. data/spec/octopus/{association_spec.rb → association_shard_tracking_spec.rb} +1 -1
  26. data/spec/octopus/collection_proxy_spec.rb +15 -0
  27. data/spec/octopus/model_spec.rb +2 -2
  28. data/spec/octopus/octopus_spec.rb +34 -0
  29. data/spec/octopus/relation_proxy_spec.rb +77 -0
  30. data/spec/octopus/replicated_slave_grouped_spec.rb +64 -0
  31. data/spec/octopus/sharded_replicated_slave_grouped_spec.rb +55 -0
  32. data/spec/support/octopus_helper.rb +1 -0
  33. metadata +26 -9
  34. data/lib/octopus/association_collection.rb +0 -49
  35. data/lib/octopus/rails3/singular_association.rb +0 -34
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MTRmYWZkMTg0YzU4MTM4YmNjYTM3MDE3MDVjZjQ1MjdiYmE2NDkwMg==
4
+ YzBiMGIyMGY1ZGEyOWRlMGY0MjAyNjljYWQ4ZTI2NmFhZmE0NzIzZg==
5
5
  data.tar.gz: !binary |-
6
- ODhhZTJjNDQzZDVhMWZhYzc1NzRjYWNjOTdlYTQ5MTNlYzk5YWJkMA==
6
+ MDM0Yzg3ODhlMjNjODEzYjcyYTFmOTFmYmFhNmNjOTk2NmI0Y2YwZg==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- Y2JiNmI0MjEzYWRjYTFhYjMwN2IyYWQwMTQyMDI1M2UyYTdiNzU3NGY4ZDlm
10
- MGYwNDQ2NGViMjhjOGRjMTZiNjdjN2VhODZiOGQ3ZWQ2Njg5OTFjZWE1ZDQ5
11
- ZThjNjUxOTNhYzYyNTMwMjk3MjkwYmYzNmU3YjA0Y2MzMjMyZmE=
9
+ ZjFlODczZWIwYTM3MWNiZWE3ZjYwMTRiZTRhOTlhODMwZmVkZmU5ZWM1MTMy
10
+ ODRlNzllMjgwY2Q4NzY3NjhiMzBhNTk4ZGRhNDNiYWRmZWE0NDBlM2UxZDE3
11
+ ZTYyNjYyZmM1MTIwNTdmODkwNzM1MmMwYzU2NTY0MDI2Mzg5NzU=
12
12
  data.tar.gz: !binary |-
13
- MjU1YzZlNzczNGM2OGQ2YzMxYmZhMWE4M2QzYTQ5ZjM1NjEwYTQ4MDJiMDY5
14
- YzM0Mjg4OTc4MmI4ZGYxMTlhNDJmYWMxMzA3MjU5ZDdmNjIwNmNmNmNlMmY3
15
- OWZjY2RkNTViY2MzOTdjMjU5NTg2NjFkNDcyZmRiMWU5NDA2MWM=
13
+ MmM5NGFiZWE5MGE3ZWM2N2M0OGFkOGNjOWJiZmZlOWVhOTA5YjYzNmQ1Yzlh
14
+ ZDlkMWNiNDMwYjQ3ZWEzYzY5MDFkOTAxOTNhZTk0NTk0YTkyYjc4NGQzYTM4
15
+ ZjE2Yjg5ZmEzYmMxOTRjODgxMDc4YjA3Y2VjNDE1NjZhNTEwODE=
data/README.mkdn CHANGED
@@ -21,13 +21,17 @@ When using replication, all writes queries will be sent to master, and read quer
21
21
  When using sharding, you need to specify which shard to send the query. Octopus supports selecting the shard inside a controller, or manually in each object. More could be found at <a href="http://wiki.github.com/tchandy/octopus/sharding"> Wiki</a>
22
22
 
23
23
  ### Replication + Sharding
24
- Replication + Sharding isn't supported yet. This is on our TODO list and will be done ASAP. If you need, feel free to fork and implement it.
24
+ When using replication and sharding concurrently, you must specify a shard, and can optionally specify a <a href="https://github.com/tchandy/octopus/wiki/Slave-Groups">slave group</a>.
25
+ All write queries will be sent to each shard's master. If the slave group is specified read queries will be sent to slaves in it, or else to shard's master.
26
+ More info could be found at <a href="https://github.com/tchandy/octopus/wiki/Slave-Groups"> Wiki</a>
25
27
 
26
28
  ## Install
27
29
 
28
30
  Add this line to Gemfile:
29
31
 
30
- gem 'ar-octopus'
32
+ ```
33
+ gem 'ar-octopus'
34
+ ```
31
35
 
32
36
  Currently, Octopus doesn't support Rails 2. If you need support for rails 2, please use the version 0.5.0.
33
37
 
@@ -42,7 +46,9 @@ information for each shard is stored within that shard's database.
42
46
  If you are upgrading from < 0.5.0 run the `copy_schema_versions` rake task to copy the schema version information in the
43
47
  master database to each of the shards:
44
48
 
45
- rake octopus:copy_schema_versions
49
+ ```bash
50
+ rake octopus:copy_schema_versions
51
+ ```
46
52
 
47
53
  Once the task completes migrations will operate normally and schema information will be stored in each shard database
48
54
  going forward.
@@ -55,62 +61,73 @@ First, you need to create a config file, shards.yml, inside your config/ directo
55
61
 
56
62
  Octopus adds a method to each AR Class and object: the using method is used to select the shard like this:
57
63
 
58
- User.where(:name => "Thiago").limit(3).using(:slave_one)
64
+ ```ruby
65
+ User.where(:name => "Thiago").limit(3).using(:slave_one)
66
+ ```
59
67
 
60
68
  Octopus also supports queries within a block. When you pass a block to the using method, all queries inside the block will be sent to the specified shard.
61
69
 
62
- Octopus.using(:slave_two) do
63
- User.create(:name => "Mike")
64
- end
70
+ ```ruby
71
+ Octopus.using(:slave_two) do
72
+ User.create(:name => "Mike")
73
+ end
74
+ ```
65
75
 
66
76
  Each model instance knows which shard it came from so this will work automatically:
67
77
 
68
- # This will find the user in the shard1
69
- @user = User.using(:shard1).find_by_name("Joao")
78
+ ```ruby
79
+ # This will find the user in the shard1
80
+ @user = User.using(:shard1).find_by_name("Joao")
70
81
 
71
- # This will find the user in the master database
72
- @user2 = User.find_by_name("Jose")
82
+ # This will find the user in the master database
83
+ @user2 = User.find_by_name("Jose")
73
84
 
74
- #Sets the name
75
- @user.name = "Mike"
85
+ #Sets the name
86
+ @user.name = "Mike"
76
87
 
77
- # Save the user in the correct shard, shard1.
78
- @user.save
88
+ # Save the user in the correct shard, shard1.
89
+ @user.save
90
+ ```
79
91
 
80
92
  ### Migrations
81
93
 
82
94
  In migrations, you also have access to the using method. The syntax is basically the same. This migration will run in the brazil and canada shards.
83
95
 
84
- class CreateUsersOnBothShards < ActiveRecord::Migration
85
- using(:brazil, :canada)
96
+ ```ruby
97
+ class CreateUsersOnBothShards < ActiveRecord::Migration
98
+ using(:brazil, :canada)
86
99
 
87
- def self.up
88
- User.create!(:name => "Both")
89
- end
100
+ def self.up
101
+ User.create!(:name => "Both")
102
+ end
90
103
 
91
- def self.down
92
- User.delete_all
93
- end
94
- end
104
+ def self.down
105
+ User.delete_all
106
+ end
107
+ end
108
+ ```
95
109
 
96
110
  You also could send a migration to a group of shards. This migration will be sent to all shards that belongs to history_shards group, specified in shards.yml:
97
111
 
98
- class CreateUsersOnMultiplesGroups < ActiveRecord::Migration
99
- using_group(:history_shards)
112
+ ```ruby
113
+ class CreateUsersOnMultiplesGroups < ActiveRecord::Migration
114
+ using_group(:history_shards)
100
115
 
101
- def self.up
102
- User.create!(:name => "MultipleGroup")
103
- end
116
+ def self.up
117
+ User.create!(:name => "MultipleGroup")
118
+ end
104
119
 
105
- def self.down
106
- User.delete_all
107
- end
108
- end
120
+ def self.down
121
+ User.delete_all
122
+ end
123
+ end
124
+ ```
109
125
 
110
126
  You can specify a `default_migration_group` for migrations, so that modifications to each individual migration file are not needed:
111
-
112
- octopus:
113
- default_migration_group: europe_databases
127
+ ```yaml
128
+ octopus:
129
+ default_migration_group: europe_databases
130
+ ```
114
131
 
115
132
  There is no need for a corresponding `default_migration_shard` - simply define that database to be your master. You might want this setting if all of your databases have identical schemas, but are not replicated.
116
133
 
@@ -118,13 +135,15 @@ There is no need for a corresponding `default_migration_shard` - simply define t
118
135
 
119
136
  If you want to send a specified action, or all actions from a controller, to a specific shard, use this syntax:
120
137
 
121
- class ApplicationController < ActionController::Base
122
- around_filter :select_shard
138
+ ```ruby
139
+ class ApplicationController < ActionController::Base
140
+ around_filter :select_shard
123
141
 
124
- def select_shard(&block)
125
- Octopus.using(:brazil, &block)
126
- end
127
- end
142
+ def select_shard(&block)
143
+ Octopus.using(:brazil, &block)
144
+ end
145
+ end
146
+ ```
128
147
 
129
148
  To see the complete list of features and syntax, please check out our <a href="http://wiki.github.com/tchandy/octopus/"> Wiki</a>
130
149
  Want to see sample rails applications using octopus features? please check it out: <a href="http://github.com/tchandy/octopus_sharding_example">Sharding Example</a> and <a href="http://github.com/tchandy/octopus_replication_example">Replication Example</a>. Also, we have an example that shows how to use Octopus without Rails: <a href="http://github.com/tchandy/octopus_sinatra"> Octopus + Sinatra Example</a>.
@@ -132,27 +151,33 @@ Want to see sample rails applications using octopus features? please check it ou
132
151
  ## Mixing Octopus with the Rails multiple database model
133
152
  If you want to set a custom connection to a specific model, use the syntax `octopus_establish_connection` syntax:
134
153
 
135
- #This class sets its own connection
136
- class CustomConnection < ActiveRecord::Base
137
- octopus_establish_connection(:adapter => "mysql", :database => "octopus_shard2")
138
- end
154
+ ```ruby
155
+ #This class sets its own connection
156
+ class CustomConnection < ActiveRecord::Base
157
+ octopus_establish_connection(:adapter => "mysql", :database => "octopus_shard2")
158
+ end
159
+ ```
139
160
 
140
161
  ## Contributing with Octopus
141
162
  Contributors are welcome! To run the test suite, you need mysql, postgresql and sqlite3 installed. This is what you need to setup your Octopus development environment:
142
163
 
143
- git clone http://github.com/tchandy/octopus.git
144
- cd octopus
145
- bundle install
146
- bundle exec rake db:prepare
147
- bundle exec rake appraisal:install
148
- bundle exec rake spec
164
+ ```bash
165
+ git clone http://github.com/tchandy/octopus.git
166
+ cd octopus
167
+ bundle install
168
+ bundle exec rake db:prepare
169
+ bundle exec rake appraisal:install
170
+ bundle exec rake spec
171
+ ```
149
172
 
150
173
  This command will run the spec suite for all rails versions supported.
151
174
  To run our integrations tests inside sample_app, you need to following commands:
152
175
 
153
- cd sample_app
154
- bundle install
155
- cucumber
176
+ ```bash
177
+ cd sample_app
178
+ bundle install
179
+ cucumber
180
+ ```
156
181
 
157
182
  If you are having issues running the octopus spec suite, verify your database users and passwords match those inside the config files and your permissions are correct.
158
183
 
data/lib/octopus.rb CHANGED
@@ -90,18 +90,30 @@ module Octopus
90
90
  yield
91
91
  end
92
92
  end
93
+
94
+ def self.fully_replicated(&block)
95
+ old_fully_replicated = Thread.current["octopus.fully_replicated"]
96
+ Thread.current["octopus.fully_replicated"] = true
97
+ yield
98
+ ensure
99
+ Thread.current["octopus.fully_replicated"] = old_fully_replicated
100
+ end
93
101
  end
94
102
 
103
+ require "octopus/shard_tracking"
104
+ require "octopus/shard_tracking/attribute"
105
+ require "octopus/shard_tracking/dynamic"
95
106
 
96
107
  require "octopus/model"
97
108
  require "octopus/migration"
98
- require "octopus/association_collection"
99
- require "octopus/has_and_belongs_to_many_association"
100
109
  require "octopus/association"
101
- require "octopus/rails3/persistence"
102
- require "octopus/rails3/log_subscriber"
103
- require "octopus/rails3/abstract_adapter"
104
- require "octopus/rails3/singular_association"
110
+ require "octopus/collection_association"
111
+ require "octopus/has_and_belongs_to_many_association"
112
+ require "octopus/association_shard_tracking"
113
+ require "octopus/persistence"
114
+ require "octopus/log_subscriber"
115
+ require "octopus/abstract_adapter"
116
+ require "octopus/singular_association"
105
117
 
106
118
  if defined?(::Rails)
107
119
  require "octopus/railtie"
@@ -109,4 +121,6 @@ end
109
121
 
110
122
 
111
123
  require "octopus/proxy"
124
+ require "octopus/collection_proxy"
125
+ require "octopus/relation_proxy"
112
126
  require "octopus/scope_proxy"
@@ -3,10 +3,7 @@ module Octopus
3
3
  module AbstractAdapter
4
4
  module OctopusShard
5
5
 
6
- parent = ActiveSupport::BasicObject
7
- if Octopus.rails4?
8
- parent = ActiveSupport::ProxyObject
9
- end
6
+ parent = Octopus.rails3? ? ActiveSupport::BasicObject : ActiveSupport::ProxyObject
10
7
 
11
8
  class InstrumenterDecorator < parent
12
9
  def initialize(adapter, instrumenter)
@@ -1,105 +1,11 @@
1
1
  module Octopus::Association
2
- def self.extended(base)
3
- base.send(:include, InstanceMethods)
2
+ def self.included(base)
3
+ base.send(:include, Octopus::ShardTracking::Dynamic)
4
4
  end
5
5
 
6
- module QueryOnCurrentShard
7
-
8
- METHODS= %w[
9
- all
10
- average
11
- count
12
- empty?
13
- exists?
14
- find
15
- find_by_sql
16
- first
17
- last
18
- maximum
19
- minimum
20
- pluck
21
- scoping
22
- size
23
- sum
24
- to_a
25
- ]
26
-
27
- METHODS.each do |m|
28
- define_method m.to_sym do |*args,&block|
29
- if self.respond_to?(:proxy_association) and self.proxy_association
30
- self.proxy_association.owner.run_on_shard { super(*args, &block) }
31
- else
32
- super(*args, &block)
33
- end
34
- end
35
- end
36
-
37
- end
38
-
39
- module InstanceMethods
40
- def set_connection_on_association(record)
41
- return unless ::Octopus.enabled?
42
- return if !self.class.connection.respond_to?(:current_shard) || !self.respond_to?(:current_shard)
43
- if !record.current_shard.nil? && !self.current_shard.nil? && record.current_shard != self.current_shard
44
- raise "Association Error: Records are from different shards"
45
- end
46
-
47
- record.current_shard = self.class.connection.current_shard = self.current_shard if should_set_current_shard?
48
- end
49
- end
50
-
51
- if Octopus.rails4?
52
- def has_many(association_id, scope=nil, options={}, &extension)
53
- if options == {} && scope.is_a?(Hash)
54
- default_octopus_opts(scope)
55
- else
56
- default_octopus_opts(options)
57
- end
58
- super
59
- end
60
- else
61
- def has_many(association_id, options={}, &extension)
62
- default_octopus_opts(options)
63
- super
64
- end
65
- end
66
-
67
-
68
- if Octopus.rails4?
69
- def has_and_belongs_to_many(association_id, scope=nil, options={}, &extension)
70
- if options == {} && scope.is_a?(Hash)
71
- default_octopus_opts(scope)
72
- else
73
- default_octopus_opts(options)
74
- end
75
- super
76
- end
77
- else
78
- def has_and_belongs_to_many(association_id, options={}, &extension)
79
- default_octopus_opts(options)
80
- super
81
- end
82
- end
83
-
84
- def default_octopus_opts(options)
85
- if options[:before_add].is_a?(Array)
86
- options[:before_add] << :set_connection_on_association
87
- elsif options[:before_add].is_a?(Symbol)
88
- options[:before_add] = [:set_connection_on_association, options[:before_add]]
89
- else
90
- options[:before_add] = :set_connection_on_association
91
- end
92
-
93
- if options[:before_remove].is_a?(Array)
94
- options[:before_remove] << :set_connection_on_association
95
- elsif options[:before_remove].is_a?(Symbol)
96
- options[:before_remove] = [:set_connection_on_association, options[:before_remove]]
97
- else
98
- options[:before_remove] = :set_connection_on_association
99
- end
100
-
101
- options[:extend] = [ Octopus::Association::QueryOnCurrentShard, options[:extend] ].flatten.compact
6
+ def current_shard
7
+ owner.current_shard
102
8
  end
103
9
  end
104
10
 
105
- ActiveRecord::Base.extend(Octopus::Association)
11
+ ActiveRecord::Associations::Association.send(:include, Octopus::Association)
@@ -0,0 +1,105 @@
1
+ module Octopus::AssociationShardTracking
2
+ def self.extended(base)
3
+ base.send(:include, InstanceMethods)
4
+ end
5
+
6
+ module QueryOnCurrentShard
7
+
8
+ METHODS= %w[
9
+ all
10
+ average
11
+ count
12
+ empty?
13
+ exists?
14
+ find
15
+ find_by_sql
16
+ first
17
+ last
18
+ maximum
19
+ minimum
20
+ pluck
21
+ scoping
22
+ size
23
+ sum
24
+ to_a
25
+ ]
26
+
27
+ METHODS.each do |m|
28
+ define_method m.to_sym do |*args,&block|
29
+ if self.respond_to?(:proxy_association) and self.proxy_association
30
+ self.proxy_association.owner.run_on_shard { super(*args, &block) }
31
+ else
32
+ super(*args, &block)
33
+ end
34
+ end
35
+ end
36
+
37
+ end
38
+
39
+ module InstanceMethods
40
+ def set_connection_on_association(record)
41
+ return unless ::Octopus.enabled?
42
+ return if !self.class.connection.respond_to?(:current_shard) || !self.respond_to?(:current_shard)
43
+ if !record.current_shard.nil? && !self.current_shard.nil? && record.current_shard != self.current_shard
44
+ raise "Association Error: Records are from different shards"
45
+ end
46
+
47
+ record.current_shard = self.class.connection.current_shard = self.current_shard if should_set_current_shard?
48
+ end
49
+ end
50
+
51
+ if Octopus.rails4?
52
+ def has_many(association_id, scope=nil, options={}, &extension)
53
+ if options == {} && scope.is_a?(Hash)
54
+ default_octopus_opts(scope)
55
+ else
56
+ default_octopus_opts(options)
57
+ end
58
+ super
59
+ end
60
+ else
61
+ def has_many(association_id, options={}, &extension)
62
+ default_octopus_opts(options)
63
+ super
64
+ end
65
+ end
66
+
67
+
68
+ if Octopus.rails4?
69
+ def has_and_belongs_to_many(association_id, scope=nil, options={}, &extension)
70
+ if options == {} && scope.is_a?(Hash)
71
+ default_octopus_opts(scope)
72
+ else
73
+ default_octopus_opts(options)
74
+ end
75
+ super
76
+ end
77
+ else
78
+ def has_and_belongs_to_many(association_id, options={}, &extension)
79
+ default_octopus_opts(options)
80
+ super
81
+ end
82
+ end
83
+
84
+ def default_octopus_opts(options)
85
+ if options[:before_add].is_a?(Array)
86
+ options[:before_add] << :set_connection_on_association
87
+ elsif options[:before_add].is_a?(Symbol)
88
+ options[:before_add] = [:set_connection_on_association, options[:before_add]]
89
+ else
90
+ options[:before_add] = :set_connection_on_association
91
+ end
92
+
93
+ if options[:before_remove].is_a?(Array)
94
+ options[:before_remove] << :set_connection_on_association
95
+ elsif options[:before_remove].is_a?(Symbol)
96
+ options[:before_remove] = [:set_connection_on_association, options[:before_remove]]
97
+ else
98
+ options[:before_remove] = :set_connection_on_association
99
+ end
100
+
101
+ options[:extend] = [ Octopus::AssociationShardTracking::QueryOnCurrentShard, options[:extend] ].flatten.compact
102
+ end
103
+ end
104
+
105
+ ActiveRecord::Base.extend(Octopus::AssociationShardTracking)