dynashard 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem 'activerecord', '>= 3.0'
4
+
5
+ group :development do
6
+ gem "shoulda", ">= 0"
7
+ gem "bundler", "~> 1.0.0"
8
+ gem "jeweler", "~> 1.5.2"
9
+ gem "rcov", ">= 0"
10
+ end
11
+
12
+ group :test do
13
+ gem 'rspec', '>= 2.0'
14
+ gem 'sqlite3-ruby', :require => 'sqlite3'
15
+ gem 'factory_girl_rails'
16
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,100 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ abstract (1.0.0)
5
+ actionmailer (3.0.1)
6
+ actionpack (= 3.0.1)
7
+ mail (~> 2.2.5)
8
+ actionpack (3.0.1)
9
+ activemodel (= 3.0.1)
10
+ activesupport (= 3.0.1)
11
+ builder (~> 2.1.2)
12
+ erubis (~> 2.6.6)
13
+ i18n (~> 0.4.1)
14
+ rack (~> 1.2.1)
15
+ rack-mount (~> 0.6.12)
16
+ rack-test (~> 0.5.4)
17
+ tzinfo (~> 0.3.23)
18
+ activemodel (3.0.1)
19
+ activesupport (= 3.0.1)
20
+ builder (~> 2.1.2)
21
+ i18n (~> 0.4.1)
22
+ activerecord (3.0.1)
23
+ activemodel (= 3.0.1)
24
+ activesupport (= 3.0.1)
25
+ arel (~> 1.0.0)
26
+ tzinfo (~> 0.3.23)
27
+ activeresource (3.0.1)
28
+ activemodel (= 3.0.1)
29
+ activesupport (= 3.0.1)
30
+ activesupport (3.0.1)
31
+ arel (1.0.1)
32
+ activesupport (~> 3.0.0)
33
+ builder (2.1.2)
34
+ diff-lcs (1.1.2)
35
+ erubis (2.6.6)
36
+ abstract (>= 1.0.0)
37
+ factory_girl (1.3.2)
38
+ factory_girl_rails (1.0)
39
+ factory_girl (~> 1.3)
40
+ rails (>= 3.0.0.beta4)
41
+ git (1.2.5)
42
+ i18n (0.4.2)
43
+ jeweler (1.5.2)
44
+ bundler (~> 1.0.0)
45
+ git (>= 1.2.5)
46
+ rake
47
+ mail (2.2.9)
48
+ activesupport (>= 2.3.6)
49
+ i18n (~> 0.4.1)
50
+ mime-types (~> 1.16)
51
+ treetop (~> 1.4.8)
52
+ mime-types (1.16)
53
+ polyglot (0.3.1)
54
+ rack (1.2.1)
55
+ rack-mount (0.6.13)
56
+ rack (>= 1.0.0)
57
+ rack-test (0.5.6)
58
+ rack (>= 1.0)
59
+ rails (3.0.1)
60
+ actionmailer (= 3.0.1)
61
+ actionpack (= 3.0.1)
62
+ activerecord (= 3.0.1)
63
+ activeresource (= 3.0.1)
64
+ activesupport (= 3.0.1)
65
+ bundler (~> 1.0.0)
66
+ railties (= 3.0.1)
67
+ railties (3.0.1)
68
+ actionpack (= 3.0.1)
69
+ activesupport (= 3.0.1)
70
+ rake (>= 0.8.4)
71
+ thor (~> 0.14.0)
72
+ rake (0.8.7)
73
+ rcov (0.9.9)
74
+ rspec (2.1.0)
75
+ rspec-core (~> 2.1.0)
76
+ rspec-expectations (~> 2.1.0)
77
+ rspec-mocks (~> 2.1.0)
78
+ rspec-core (2.1.0)
79
+ rspec-expectations (2.1.0)
80
+ diff-lcs (~> 1.1.2)
81
+ rspec-mocks (2.1.0)
82
+ shoulda (2.11.3)
83
+ sqlite3-ruby (1.3.2)
84
+ thor (0.14.4)
85
+ treetop (1.4.8)
86
+ polyglot (>= 0.3.1)
87
+ tzinfo (0.3.23)
88
+
89
+ PLATFORMS
90
+ ruby
91
+
92
+ DEPENDENCIES
93
+ activerecord (>= 3.0)
94
+ bundler (~> 1.0.0)
95
+ factory_girl_rails
96
+ jeweler (~> 1.5.2)
97
+ rcov
98
+ rspec (>= 2.0)
99
+ shoulda
100
+ sqlite3-ruby
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Nick Hengeveld
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # Dynashard - Dynamic sharding for ActiveRecord
2
+
3
+ This package provides database sharding functionality for ActiveRecord models.
4
+
5
+ Sharding is disabled by default and is enabled with +Dynashard.enable+. This allows
6
+ sharding behavior to be enabled globally or only for specific environments so for
7
+ example production environments could be sharded while development environments could
8
+ use a single database.
9
+
10
+ Models may be configured to determine the appropriate shard (database connection) to
11
+ use based on context defined prior to performing queries.
12
+
13
+ class Widget < ActiveRecord::Base
14
+ shard :by => :user
15
+ end
16
+
17
+ class WidgetController < ApplicationController
18
+ around_filter :set_shard_context
19
+
20
+ def index
21
+ # Widgets will be loaded using the connection for the current user's shard
22
+ @widgets = Widget.find(:all)
23
+ end
24
+
25
+ private
26
+
27
+ def set_shard_context
28
+ Dynashard.with_context(:user => current_user.shard) do
29
+ yield
30
+ end
31
+ end
32
+ end
33
+
34
+ Associated models may be configured to use different shards determined by the
35
+ association's owner.
36
+
37
+ class Company < ActiveRecord::Base
38
+ shard :associated, :using => :shard
39
+
40
+ has_many :customers
41
+
42
+ def shard
43
+ # logic to find the company's shard
44
+ end
45
+ end
46
+
47
+ class Customer < ActiveRecord::Base
48
+ belongs_to :company
49
+ shard :by => :company
50
+ end
51
+
52
+ > c = Company.find(:first)
53
+ => #<Company id:1>
54
+
55
+ Company is loaded using the default ActiveRecord connection.
56
+
57
+ > c.customers
58
+ => [#<Dynashard::Shard0::Customer id: 1>, #<Dynashard::Shard0::Customer id: 2>]
59
+
60
+ Customers are loaded using the connection for the Company's shard. Associated models
61
+ are returned as shard-specific subclasses of the association class.
62
+
63
+ > c.customers.create(:name => 'Always right')
64
+ => #<Dynashard::Shard0::Customer id: 3>
65
+
66
+ New associations are saved on the Company's shard.
67
+
68
+ ## TODO: add gotcha section, eg:
69
+
70
+ - uniqueness validations should be scoped by whatever is sharding
71
+ - ways to shoot yourself in the foot with non-sharding association
72
+ owners of sharded models
data/Rakefile ADDED
@@ -0,0 +1,45 @@
1
+ $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ require "rspec/core/rake_task"
6
+ begin
7
+ Bundler.setup(:default, :development)
8
+ rescue Bundler::BundlerError => e
9
+ $stderr.puts e.message
10
+ $stderr.puts "Run `bundle install` to install missing gems"
11
+ exit e.status_code
12
+ end
13
+ require 'rake'
14
+
15
+ RSpec::Core::RakeTask.new(:spec)
16
+
17
+ task :default => :spec
18
+
19
+ require 'jeweler'
20
+ Jeweler::Tasks.new do |gem|
21
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
22
+ gem.name = "dynashard"
23
+ gem.homepage = "http://github.com/nickh/dynashard"
24
+ gem.license = "MIT"
25
+ gem.summary = 'Dynamic sharding for ActiveRecord'
26
+ gem.description = 'Dynashard allows you to shard your ActiveRecord models. Models can be configured to shard based on context that can be defined dynamically.'
27
+ gem.email = "nickh@verticalresponse.com"
28
+ gem.authors = ["Nick Hengeveld"]
29
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
30
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
31
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
32
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
33
+ gem.add_runtime_dependency 'activerecord', '>= 3.0'
34
+ end
35
+ Jeweler::RubygemsDotOrgTasks.new
36
+
37
+ require 'rake/rdoctask'
38
+ Rake::RDocTask.new do |rdoc|
39
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
40
+
41
+ rdoc.rdoc_dir = 'rdoc'
42
+ rdoc.title = "dynashard #{version}"
43
+ rdoc.rdoc_files.include('README*')
44
+ rdoc.rdoc_files.include('lib/**/*.rb')
45
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,22 @@
1
+ module Dynashard
2
+ module ArelEngineExtensions
3
+ def self.included(base)
4
+ base.alias_method_chain :connection, :dynashard
5
+ end
6
+
7
+ def connection_with_dynashard
8
+ if @ar && @ar.respond_to?(:dynashard_klass)
9
+ @ar.dynashard_klass.connection
10
+ elsif @ar && @ar.sharding_enabled?
11
+ spec = Dynashard.shard_context[@ar.dynashard_context]
12
+ raise "Missing #{@ar.dynashard_context} shard context" if spec.nil?
13
+ spec = spec.call if spec.respond_to?(:call)
14
+ Dynashard.class_for(spec).connection
15
+ else
16
+ connection_without_dynashard
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ Arel::Sql::Engine.send(:include, Dynashard::ArelEngineExtensions)
@@ -0,0 +1,19 @@
1
+ module Dynashard
2
+ module ProxyExtensions
3
+ def self.included(base)
4
+ base.alias_method_chain :initialize, :dynashard
5
+ end
6
+
7
+ # Initialize an association proxy. If the proxy owner class is configured to
8
+ # shard its associations and the reflection klass is sharded, use a custom
9
+ # reflection with a sharded class.
10
+ def initialize_with_dynashard(owner, reflection)
11
+ if owner.class.shards_associated? && reflection.klass.sharding_enabled?
12
+ reflection = Dynashard.reflection_for(owner, reflection)
13
+ end
14
+ initialize_without_dynashard(owner, reflection)
15
+ end
16
+ end
17
+ end
18
+
19
+ ActiveRecord::Associations::AssociationProxy.send(:include, Dynashard::ProxyExtensions)
@@ -0,0 +1,45 @@
1
+ module Dynashard
2
+ module ConnectionHandlerExtensions
3
+ def self.included(base)
4
+ base.alias_method_chain :retrieve_connection_pool, :dynashard
5
+ end
6
+
7
+ # Set an connection pool entry for the model class pointing at
8
+ # the shard class's pool.
9
+ # :nodoc:
10
+ # This is required for places that examine the connection pool to
11
+ # determine whether to use a given class's connection or a parent
12
+ # class's connection (eg. ActiveRecord::Base.arel_engine)
13
+ def dynashard_pool_alias(model_class, shard_class)
14
+ @connection_pools[model_class] = @connection_pools[shard_class]
15
+ end
16
+
17
+ # Return a connection pool for the specified class. If sharding
18
+ # is enabled, the correct pool for the configured sharding context
19
+ # will be used.
20
+ #
21
+ # :nodoc:
22
+ # Reflection classes generated for an association proxy will
23
+ # respond to :dynashard_klass and return a class with an
24
+ # established connection to the correct shard.
25
+ #
26
+ # Sharded models will have a dynashard context defined, which
27
+ # can be used as a key to the Dynashard.shard_context hash to
28
+ # access the shard's connection specification.
29
+ def retrieve_connection_pool_with_dynashard(klass)
30
+ if klass.respond_to?(:dynashard_klass)
31
+ retrieve_connection_pool_without_dynashard(klass.dynashard_klass)
32
+ elsif klass.sharding_enabled?
33
+ spec = Dynashard.shard_context[klass.dynashard_context]
34
+ raise "Missing #{klass.dynashard_context} shard context" if spec.nil?
35
+ spec = spec.call if spec.respond_to?(:call)
36
+ shard_klass = Dynashard.class_for(spec)
37
+ retrieve_connection_pool_without_dynashard(shard_klass)
38
+ else
39
+ retrieve_connection_pool_without_dynashard(klass)
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ ActiveRecord::ConnectionAdapters::ConnectionHandler.send(:include, Dynashard::ConnectionHandlerExtensions)
@@ -0,0 +1,92 @@
1
+ module Dynashard
2
+ module ActiveRecordExtensions
3
+ def self.extended(base)
4
+ base.extend(ClassMethods)
5
+
6
+ # Change ActiveRecord::Base.arel_engine to create an engine for sharded models
7
+ # rather than using the ActiveRecord::Base class.
8
+ base.module_eval do
9
+ def self.arel_engine
10
+ if sharding_enabled?
11
+ Arel::Sql::Engine.new(self)
12
+ elsif self == ActiveRecord::Base
13
+ Arel::Table.engine
14
+ else
15
+ connection_handler.connection_pools[name] ? Arel::Sql::Engine.new(self) : superclass.arel_engine
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ module ClassMethods
22
+ # Configure sharding for the current model. The following options are supported:
23
+ # * +:by => :context+ - the database connection for this model will be determined using a connection
24
+ # proxy with the specified context.
25
+ # * +:associated, :using => :method+ - the database connection for associated models with sharding
26
+ # enabled will be determined using the association's defined context after first setting that
27
+ # context using the specified method on the current instance
28
+ #
29
+ # class MyModel < ActiveRecord::Base
30
+ # shard :by => :context
31
+ # shard :assocated, :using => :sharding_method
32
+ # end
33
+ #
34
+ # def sharding_method
35
+ # # return a valid shard descriptor:
36
+ # # - a string key into the configurations in databases.yml
37
+ # # - a hash with connection parameters
38
+ # # - an object that responds to :call with one of the above
39
+ # end
40
+ def shard(*args)
41
+ if args.first == :associated
42
+ using = args.last[:using] if args.last.respond_to?(:[])
43
+ raise ArgumentError.new(":associated specified without :using") if using.nil?
44
+ @dynashard_association_using = using
45
+ else
46
+ shard_by = args.first[:by] if args.first.respond_to?(:[])
47
+ raise ArgumentError.new("Invalid options") if shard_by.nil?
48
+ @dynashard_context = shard_by
49
+ end
50
+ end
51
+
52
+ # Returns true if sharding has been globally enabled and has been configured for this model
53
+ def sharding_enabled?
54
+ Dynashard.enabled? && !@dynashard_context.nil?
55
+ end
56
+
57
+ # Returns true if sharding has been globally enabled and is configured to be used for
58
+ # sharded models associated with this model
59
+ def shards_associated?
60
+ Dynashard.enabled? && !@dynashard_association_using.nil?
61
+ end
62
+
63
+ # Returns true if the class was generated by Dynashard
64
+ def dynashard_model?
65
+ false
66
+ end
67
+
68
+ # Return a subclass configured to connect to the appropriate shard
69
+ def dynashard_sharded_subclass
70
+ if sharding_enabled?
71
+ spec = Dynashard.shard_context[dynashard_context]
72
+ raise "Missing #{dynashard_context} shard context" if spec.nil?
73
+ spec = spec.call if spec.respond_to?(:call)
74
+ shard_klass = Dynashard.class_for(spec)
75
+ Dynashard.sharded_model_class(shard_klass, self)
76
+ end
77
+ end
78
+
79
+ # Returns the shard context for this model
80
+ def dynashard_context
81
+ @dynashard_context
82
+ end
83
+
84
+ # Returns the method used to set the context used for associated models
85
+ def dynashard_association_using
86
+ @dynashard_association_using
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ ActiveRecord::Base.extend(Dynashard::ActiveRecordExtensions)
@@ -0,0 +1,23 @@
1
+ module Dynashard
2
+ module ValidationExtensions
3
+ def self.included(base)
4
+ base.alias_method_chain :find_finder_class_for, :dynashard
5
+ end
6
+
7
+ # Return the class that should be used to find other instances of
8
+ # the specified record's model class. If the record is an instance
9
+ # of a sharded model, it should be used; otherwise the default
10
+ # behavior should be used.
11
+ def find_finder_class_for_with_dynashard(record)
12
+ if record.class.dynashard_model?
13
+ record.class
14
+ elsif record.class.sharding_enabled?
15
+ record.class.dynashard_sharded_subclass
16
+ else
17
+ find_finder_class_for_without_dynashard(record)
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ ActiveRecord::Validations::UniquenessValidator.send(:include, Dynashard::ValidationExtensions)
data/lib/dynashard.rb ADDED
@@ -0,0 +1,167 @@
1
+ # = Dynashard - Dynamic sharding for ActiveRecord
2
+ #
3
+ # This package provides database sharding functionality for ActiveRecord models.
4
+ #
5
+ # Sharding is disabled by default and is enabled with +Dynashard.enable+. This allows
6
+ # sharding behavior to be enabled globally or only for specific environments so for
7
+ # example production environments could be sharded while development environments could
8
+ # use a single database.
9
+ #
10
+ # Models may be configured to determine the appropriate shard (database connection) to
11
+ # use based on context defined prior to performing queries.
12
+ #
13
+ # class Widget < ActiveRecord::Base
14
+ # shard :by => :user
15
+ # end
16
+ #
17
+ # class WidgetController < ApplicationController
18
+ # around_filter :set_shard_context
19
+ #
20
+ # def index
21
+ # # Widgets will be loaded using the connection for the current user's shard
22
+ # @widgets = Widget.find(:all)
23
+ # end
24
+ #
25
+ # private
26
+ #
27
+ # def set_shard_context
28
+ # Dynashard.with_context(:user => current_user.shard) do
29
+ # yield
30
+ # end
31
+ # end
32
+ # end
33
+ #
34
+ # Associated models may be configured to use different shards determined by the
35
+ # association's owner.
36
+ #
37
+ # class Company < ActiveRecord::Base
38
+ # shard :associated, :using => :shard
39
+ #
40
+ # has_many :customers
41
+ #
42
+ # def shard
43
+ # # logic to find the company's shard
44
+ # end
45
+ # end
46
+ #
47
+ # class Customer < ActiveRecord::Base
48
+ # belongs_to :company
49
+ # shard :by => :company
50
+ # end
51
+ #
52
+ # > c = Company.find(:first)
53
+ # => #<Company id:1>
54
+ #
55
+ # Company is loaded using the default ActiveRecord connection.
56
+ #
57
+ # > c.customers
58
+ # => [#<Dynashard::Shard0::Customer id: 1>, #<Dynashard::Shard0::Customer id: 2>]
59
+ #
60
+ # Customers are loaded using the connection for the Company's shard. Associated models
61
+ # are returned as shard-specific subclasses of the association class.
62
+ #
63
+ # > c.customers.create(:name => 'Always right')
64
+ # => #<Dynashard::Shard0::Customer id: 3>
65
+ #
66
+ # New associations are saved on the Company's shard.
67
+ #
68
+ # TODO: add gotcha section, eg:
69
+ # - uniqueness validations should be scoped by whatever is sharding
70
+
71
+ module Dynashard
72
+ # Enable sharding for all models configured to do so
73
+ def self.enable
74
+ @enabled = true
75
+ end
76
+
77
+ # Disable sharding for all models configured to do so
78
+ def self.disable
79
+ @enabled = false
80
+ end
81
+
82
+ # Return true if sharding is globally enabled
83
+ def self.enabled?
84
+ @enabled == true
85
+ end
86
+
87
+ # Execute a block within a given sharding context
88
+ def self.with_context(new_context, &block)
89
+ orig_context = shard_context.dup
90
+ shard_context.merge! new_context
91
+ result = nil
92
+ begin
93
+ result = yield
94
+ ensure
95
+ shard_context.replace orig_context
96
+ end
97
+ result
98
+ end
99
+
100
+ # Return a threadsafe(?) current mapping of shard context to connection spec
101
+ def self.shard_context
102
+ Thread.current[:shard_context] ||= {}
103
+ end
104
+
105
+ # Return a class with an established connection to a database shard
106
+ def self.class_for(spec)
107
+ @class_cache ||= {}
108
+ @class_cache[spec] ||= new_shard_class(spec)
109
+ @class_cache[spec]
110
+ end
111
+
112
+ # Return a reflection with a sharded class
113
+ def self.reflection_for(owner, reflection)
114
+ reflection_copy = reflection.dup
115
+ shard_klass = Dynashard.class_for(owner.send(owner.class.dynashard_association_using))
116
+ klass = sharded_model_class(shard_klass, reflection.klass)
117
+ reflection_copy.instance_variable_set('@klass', klass)
118
+ reflection_copy.instance_variable_set('@class_name', klass.name)
119
+
120
+ reflection_copy
121
+ end
122
+
123
+ # Return a model subclass configured to use a specific shard
124
+ def self.sharded_model_class(shard_klass, base_klass)
125
+ class_name = "#{shard_klass.name}::#{base_klass.name}"
126
+ unless shard_klass.constants.include?(base_klass.name.to_sym)
127
+ class_eval <<EOE
128
+ class #{class_name} < #{base_klass.name}
129
+ @@dynashard_klass = #{shard_klass.name}
130
+
131
+ def self.dynashard_model?()
132
+ true
133
+ end
134
+
135
+ def self.dynashard_klass()
136
+ @@dynashard_klass
137
+ end
138
+
139
+ def self.connection
140
+ dynashard_klass.connection
141
+ end
142
+ end
143
+ EOE
144
+ klass = class_name.constantize
145
+ klass.connection_handler.dynashard_pool_alias(klass.name, shard_klass.name)
146
+ end
147
+ class_name.constantize
148
+ end
149
+
150
+ private
151
+
152
+ def self.new_shard_class(spec)
153
+ shard_number = @class_cache.size
154
+ klass_name = "Shard#{shard_number}"
155
+ unless const_defined?(klass_name)
156
+ module_eval("class #{klass_name} < ActiveRecord::Base ; end")
157
+ "Dynashard::#{klass_name}".constantize.establish_connection(spec)
158
+ end
159
+ "Dynashard::#{klass_name}".constantize
160
+ end
161
+ end
162
+
163
+ require 'dynashard/model'
164
+ require 'dynashard/associations'
165
+ require 'dynashard/connection_handler'
166
+ require 'dynashard/validations'
167
+ require 'dynashard/arel_sql_engine'
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Dynashard::ArelEngineExtensions' do
4
+ before(:each) do
5
+ @mock_record = mock()
6
+ @engine = Arel::Sql::Engine.new(@mock_record)
7
+ end
8
+
9
+ describe '#connection_with_dynashard' do
10
+ context 'with a sharding association owner' do
11
+ it "returns the generated model subclass connection" do
12
+ @mock_record.should_receive(:dynashard_klass).once.and_return(mock(:connection => :subclass_connection))
13
+ @engine.should_receive(:connection_without_dynashard).never
14
+ @engine.connection.should == :subclass_connection
15
+ end
16
+ end
17
+
18
+ context 'with a sharding model' do
19
+ before(:each) do
20
+ @mock_record.should_receive(:sharding_enabled?).and_return(true)
21
+ @mock_record.should_receive(:dynashard_context).at_least(:once).and_return(:dynashard_context)
22
+ end
23
+
24
+ context 'and no defined sharding context' do
25
+ it 'raises an exception' do
26
+ Dynashard.shard_context[:dynashard_context] = nil
27
+ lambda do
28
+ @engine.connection
29
+ end.should raise_error
30
+ end
31
+ end
32
+
33
+ context 'and a defined sharding context' do
34
+ it 'returns the generated shard class connection' do
35
+ Dynashard.should_receive(:class_for).with(:shard_spec).and_return(mock(:connection => :shard_connection))
36
+ Dynashard.shard_context[:dynashard_context] = :shard_spec
37
+ @engine.should_receive(:connection_without_dynashard).never
38
+ @engine.connection.should == :shard_connection
39
+ end
40
+ end
41
+ end
42
+
43
+ context 'with a non-sharding model' do
44
+ it 'returns the default connection' do
45
+ @mock_record.should_receive(:sharding_enabled?).and_return(false)
46
+ @engine.should_receive(:connection_without_dynashard).and_return(:model_connection)
47
+ @engine.connection.should == :model_connection
48
+ end
49
+ end
50
+ end
51
+ end