dynashard 0.1.0

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/.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