decisiv-sharded_database 0.1.5.1 → 0.2.0.1
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/README.rdoc +69 -8
- data/lib/sharded_database/aggregate.rb +47 -17
- data/lib/sharded_database/aggregate_proxy.rb +6 -11
- data/lib/sharded_database.rb +2 -1
- data/test/lib/database.yml +4 -3
- data/test/lib/models.rb +22 -18
- data/test/lib/test_case.rb +16 -16
- data/test/sharded_database/association_test.rb +4 -4
- data/test/sharded_database/connection_test.rb +24 -4
- data/test/sharded_database/instance_test.rb +13 -9
- metadata +1 -1
data/README.rdoc
CHANGED
@@ -9,7 +9,7 @@ The first step was solved by creating a SQL view to pool all production records
|
|
9
9
|
|
10
10
|
== Uses
|
11
11
|
|
12
|
-
Currently, sharded_database works by reflecting on a set of returned records, and taking the result of #
|
12
|
+
Currently, sharded_database works by reflecting on a set of returned records, and taking the result of #sharded_connection_klass method to delegate the connection to - for each instance. The path we chose had us creating a view that would return aggregate records, with an added 'source' column specifying the originating database. We then rigged our #sharded_connection_klass to return an abstract connection model depending on the value. Other ideas for implementation include an after_create callback that writes the source database / ID to a centralized table.
|
13
13
|
|
14
14
|
|
15
15
|
== Example and Usage
|
@@ -20,13 +20,16 @@ Setup your Aggregate model, inheriting from a class that establishes a connectio
|
|
20
20
|
|
21
21
|
class AggregateFoo < ActiveRecord::Base
|
22
22
|
include ShardedDatabase::Aggregate
|
23
|
+
source_class 'Foo'
|
23
24
|
|
24
|
-
def
|
25
|
-
|
25
|
+
def sharded_connection_klass
|
26
|
+
"Connection::#{source.classify}".constantize
|
26
27
|
end
|
27
28
|
end
|
28
29
|
|
29
|
-
The
|
30
|
+
The constant returned by #sharded_connection_klass is expected to be an subclass of ActiveRecord::Base and respond to #connection. It is suggested that you use the preserve_attributes class method in the aggregate class and use that attribute to augment and/or find the constant somehow.
|
31
|
+
|
32
|
+
As of 0.2.0, the class method #source_class is required - sharded_database no longer guesses for the correct source class!
|
30
33
|
|
31
34
|
|
32
35
|
=== Loading Aggregate Records
|
@@ -43,12 +46,70 @@ Updating an attribute on the first record in the above array would update the co
|
|
43
46
|
Associations are also taken into account, and any associations that are defined on a Foo model will be sourced correctly. I have only tested this one level deep, though.
|
44
47
|
|
45
48
|
|
46
|
-
=== Accessing
|
49
|
+
=== Accessing Non-Proxyable AggregateFoo Objects
|
50
|
+
|
51
|
+
Easily done. Simply add a :aggregate_proxy option set to false to your finders.
|
52
|
+
|
53
|
+
AggreateFoo.all(:aggregate_proxy => false)
|
54
|
+
|
55
|
+
|
56
|
+
== Working without an AggregateFoo table
|
57
|
+
|
58
|
+
In the event that you need to load a Foo object directly, and you may or may not know the appropriate connection at runtime, sharded_database is able to apply itself to an ActiveRecord model directly.
|
59
|
+
|
60
|
+
This is done by accepting a :connection key in the options hash for a #find method. The value of :connection can either be an abstract ActiveRecord class, a Proc, or a Symbol, which will attempt to call a method of the same name on the class doing the finding. The latter two options are also supplied the #find arguments, which is useful when wanting to dynamically determine a connection.
|
61
|
+
|
62
|
+
|
63
|
+
=== Setup
|
64
|
+
|
65
|
+
Any class that includes ShardedDatabase::Aggregate and defines a source_class will have that class be given this functionality. To add manually:
|
66
|
+
|
67
|
+
class Bar < ActiveRecord::Base
|
68
|
+
include ShardedDatabase::ModelWithConnection
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
=== Usage
|
73
|
+
|
74
|
+
As mentioned above, there are three acceptable values for the :connection key. Borrowing heavily from ActionController's callback setup, acceptable values can either be an ActiveRecord class, or a Proc or class method name (represented as a symbol) that will ultimately return an ActiveRecord class. Both the Proc and method will be passed a splat of the attributes passed to #find.
|
75
|
+
|
76
|
+
The method implementation will be demonstrated in the Example, but the other implementations are as follows:
|
77
|
+
|
78
|
+
<b>Supplying a class</b>
|
79
|
+
Bar.find(:first, :order => 'created_at asc', :connection => BarConnection)
|
80
|
+
|
81
|
+
<b>Supplying a Proc</b>
|
82
|
+
Bar.find(123, :connection => lambda { |*args| (args.first % 2) == 1 ? Connection::One : Connection::Two })
|
83
|
+
|
84
|
+
...Which would use Connection::One, as % 2 of 123 is 1.
|
85
|
+
|
86
|
+
|
87
|
+
=== Example
|
88
|
+
|
89
|
+
A real world situation would be something akin to Flickr. Data is sharded over N number of databases, and the criteria sent to your finder method would be used to connect to the right datastore.
|
90
|
+
|
91
|
+
Imagine you have a photo sharing application, and due to the amount of photos managed, you're forced to shard the 'photos' database across 3 database servers. To do this with sharded_database, you'd do something like:
|
92
|
+
|
93
|
+
class Photo < ActiveRecord::Base
|
94
|
+
|
95
|
+
def self.determine_database(*attrs) # [:all, { :conditions => { :name => 'mountain' } }]
|
96
|
+
case first_letter = attrs.last[:conditions][:name].first[0,1] # get the first letter
|
97
|
+
when a..f : Connection::AToF
|
98
|
+
when g..p : Connection::GToP
|
99
|
+
when q..z : Connection::QtoZ
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
Photo.all(:conditions => { :name => 'mountain' }, :connection => :determine_database)
|
105
|
+
|
106
|
+
Which would return any photo named 'mountain' against the database Connection::GToP is connected to.
|
47
107
|
|
48
|
-
Easily done. Simply add a :raw option set to true to your finder queries.
|
49
108
|
|
50
|
-
|
109
|
+
== TODO
|
51
110
|
|
111
|
+
- Apply associations to objects found using the :connection key
|
112
|
+
- Fix association application to regenerate a proper reflection/association
|
52
113
|
|
53
114
|
|
54
|
-
Copyright © 2008, Brennan Dunn, Decisiv Inc. Released under the MIT license.
|
115
|
+
Copyright © 2008, Brennan Dunn, Decisiv Inc. Released under the MIT license.
|
@@ -7,55 +7,86 @@ module ShardedDatabase
|
|
7
7
|
klass.extend ClassMethods
|
8
8
|
klass.send :include, InstanceMethods
|
9
9
|
klass.class_eval do
|
10
|
-
cattr_accessor :
|
11
|
-
@connection_field = :oem
|
10
|
+
cattr_accessor :foreign_id
|
12
11
|
@foreign_id = :other_id
|
13
12
|
|
14
13
|
class << self
|
15
|
-
alias_method_chain :find, :
|
14
|
+
alias_method_chain :find, :aggregate_proxy
|
16
15
|
end
|
17
16
|
end
|
18
17
|
end
|
19
18
|
|
20
19
|
module ClassMethods
|
21
20
|
|
22
|
-
def
|
23
|
-
|
24
|
-
|
21
|
+
def find_with_aggregate_proxy(*args)
|
22
|
+
without_aggregate_proxy = args.last.is_a?(Hash) && args.last.delete(:aggregate_proxy).is_a?(FalseClass)
|
23
|
+
if without_aggregate_proxy
|
24
|
+
temporarily_undef_method(:after_find) { find_without_aggregate_proxy(*args) }
|
25
|
+
else
|
26
|
+
find_without_aggregate_proxy(*args)
|
27
|
+
end
|
25
28
|
end
|
26
|
-
|
29
|
+
|
27
30
|
def preserve_attributes(*attrs)
|
28
31
|
@preserved_attributes = attrs.map(&:to_s)
|
29
32
|
end
|
33
|
+
|
34
|
+
def source_class(klass_name)
|
35
|
+
@source_class = klass_name
|
36
|
+
apply_model_with_connection_to(klass_name)
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def apply_model_with_connection_to(klass_name)
|
43
|
+
require_dependency klass_name.underscore unless defined?(klass_name.constantize) # ensure that the source class has been loaded
|
44
|
+
klass_name.constantize.send :include, ShardedDatabase::ModelWithConnection
|
45
|
+
end
|
30
46
|
|
31
47
|
end
|
32
48
|
|
33
49
|
module InstanceMethods
|
34
50
|
|
35
|
-
def
|
36
|
-
|
51
|
+
def sharded_connection_klass
|
52
|
+
raise NotImplementedError,
|
53
|
+
"You must implement your own #sharded_connection_klass method that returns an ActiveRecord::Base subclass which yeilds a connection."
|
37
54
|
end
|
38
55
|
|
39
56
|
def after_find
|
40
|
-
@klass =
|
57
|
+
@klass = sharded_connection_klass
|
41
58
|
@connection = @klass.respond_to?(:connection) ? @klass.connection : raise(ShardedDatabase::NoConnectionError, 'Connection class does not respond to :connection')
|
42
59
|
@foreign_id = self[self.class.foreign_id.to_sym]
|
43
60
|
|
44
61
|
metaclass.delegate :connection, :to => @klass
|
45
|
-
|
62
|
+
|
63
|
+
preserve_attributes
|
64
|
+
apply_proxy
|
65
|
+
channel_associations_to_proper_connection
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def preserve_attributes
|
46
73
|
(self.class.instance_variable_get("@preserved_attributes") || []).each do |attr|
|
47
|
-
metaclass.send :alias_method, "
|
74
|
+
metaclass.send :alias_method, "original_#{attr}", attr
|
48
75
|
end
|
49
|
-
|
76
|
+
end
|
77
|
+
|
78
|
+
def apply_proxy
|
50
79
|
class << self
|
51
80
|
alias_method :proxy_class, :class
|
52
|
-
|
81
|
+
|
53
82
|
include AggregateProxy
|
54
83
|
instance_methods.each do |m|
|
55
|
-
undef_method(m) unless m =~ /^__|proxy_|inspect/
|
84
|
+
undef_method(m) unless m =~ /^__|proxy_|original_|inspect/
|
56
85
|
end
|
57
86
|
end
|
58
|
-
|
87
|
+
end
|
88
|
+
|
89
|
+
def channel_associations_to_proper_connection
|
59
90
|
self.class.reflect_on_all_associations.each do |a|
|
60
91
|
metaclass.send :alias_method, "proxy_#{a.name}".to_sym, a.name.to_sym
|
61
92
|
metaclass.send :undef_method, a.name
|
@@ -83,7 +114,6 @@ module ShardedDatabase
|
|
83
114
|
end
|
84
115
|
}, __FILE__, __LINE__
|
85
116
|
end
|
86
|
-
|
87
117
|
end
|
88
118
|
|
89
119
|
end
|
@@ -9,20 +9,21 @@ module ShardedDatabase
|
|
9
9
|
load_target.inspect.gsub(/#<([\w\:]+)\s(.*?)>/) { "#<#{$1}(#{@klass.name}) #{$2}>" }
|
10
10
|
end
|
11
11
|
|
12
|
+
def respond_to?(method)
|
13
|
+
load_target.respond_to?(method) || super
|
14
|
+
end
|
15
|
+
|
12
16
|
|
13
17
|
private
|
14
18
|
|
15
19
|
def load_target
|
16
20
|
@target ||= begin
|
17
|
-
klass = (self.proxy_class.
|
18
|
-
borrow_connection(klass, @klass) { |k| k.find(@foreign_id) }
|
21
|
+
klass = (self.proxy_class.instance_variable_get('@source_class')).constantize
|
22
|
+
ModelWithConnection.borrow_connection(klass, @klass) { |k| k.find(@foreign_id) }
|
19
23
|
end
|
20
24
|
end
|
21
25
|
|
22
26
|
def method_missing(method, *args, &block)
|
23
|
-
if association_method?(method)
|
24
|
-
#apply_connection_to_association(method)
|
25
|
-
end
|
26
27
|
load_target.respond_to?(method) ? load_target.send(method, *args, &block) : super
|
27
28
|
end
|
28
29
|
|
@@ -30,11 +31,5 @@ module ShardedDatabase
|
|
30
31
|
load_target.class.reflect_on_all_associations.any? { |a| a.name == method.to_sym }
|
31
32
|
end
|
32
33
|
|
33
|
-
def borrow_connection(requesting_class, target_class, &block)
|
34
|
-
eigen = requesting_class.metaclass
|
35
|
-
eigen.delegate :connection, :to => target_class
|
36
|
-
yield(requesting_class)
|
37
|
-
end
|
38
|
-
|
39
34
|
end
|
40
35
|
end
|
data/lib/sharded_database.rb
CHANGED
data/test/lib/database.yml
CHANGED
@@ -5,16 +5,17 @@ master:
|
|
5
5
|
password:
|
6
6
|
host: localhost
|
7
7
|
|
8
|
-
|
8
|
+
shard_one:
|
9
9
|
adapter: mysql
|
10
10
|
database: sharded_database_one
|
11
11
|
username: root
|
12
12
|
password:
|
13
13
|
host: localhost
|
14
14
|
|
15
|
-
|
15
|
+
shard_two:
|
16
16
|
adapter: mysql
|
17
17
|
database: sharded_database_two
|
18
18
|
username: root
|
19
19
|
password:
|
20
|
-
host: localhost
|
20
|
+
host: localhost
|
21
|
+
|
data/test/lib/models.rb
CHANGED
@@ -1,42 +1,33 @@
|
|
1
1
|
module Connection ; end
|
2
2
|
|
3
3
|
class Connection::One < ActiveRecord::Base
|
4
|
-
establish_connection :
|
4
|
+
establish_connection :shard_one
|
5
5
|
self.abstract_class = true
|
6
6
|
end
|
7
7
|
|
8
8
|
class Connection::Two < ActiveRecord::Base
|
9
|
-
establish_connection :
|
9
|
+
establish_connection :shard_two
|
10
10
|
self.abstract_class = true
|
11
11
|
end
|
12
12
|
|
13
|
-
Connections = { :one => Connection::One, :two => Connection::Two }
|
14
|
-
|
15
13
|
class GlobalConnection < ActiveRecord::Base
|
16
14
|
establish_connection :master
|
17
15
|
self.abstract_class = true
|
18
16
|
end
|
19
17
|
|
20
|
-
class AggregateEstimate < GlobalConnection
|
21
|
-
belongs_to :gun
|
22
|
-
include ShardedDatabase::Aggregate
|
23
|
-
self.foreign_id = :other_id
|
24
|
-
preserve_attributes :source
|
25
|
-
|
26
|
-
def determine_connection
|
27
|
-
Connections[source.to_sym]
|
28
|
-
end
|
29
|
-
|
30
|
-
end
|
31
|
-
|
32
18
|
class Company < ActiveRecord::Base
|
33
19
|
has_many :items
|
34
20
|
end
|
35
21
|
|
36
|
-
class
|
22
|
+
class Employee < ActiveRecord::Base
|
37
23
|
belongs_to :company
|
38
24
|
has_many :items
|
39
25
|
|
26
|
+
def self.pick_connection(*args)
|
27
|
+
id_to_find = args.first
|
28
|
+
(id_to_find % 2) == 1 ? Connection::One : Connection::Two
|
29
|
+
end
|
30
|
+
|
40
31
|
def call_company
|
41
32
|
company
|
42
33
|
end
|
@@ -44,5 +35,18 @@ class Estimate < ActiveRecord::Base
|
|
44
35
|
end
|
45
36
|
|
46
37
|
class Item < ActiveRecord::Base
|
47
|
-
belongs_to :
|
38
|
+
belongs_to :employee
|
39
|
+
end
|
40
|
+
|
41
|
+
class AggregateEmployee < GlobalConnection
|
42
|
+
belongs_to :gun
|
43
|
+
include ShardedDatabase::Aggregate
|
44
|
+
self.foreign_id = :other_id
|
45
|
+
source_class 'Employee'
|
46
|
+
preserve_attributes :source
|
47
|
+
|
48
|
+
def sharded_connection_klass
|
49
|
+
"Connection::#{source.classify}".constantize
|
50
|
+
end
|
51
|
+
|
48
52
|
end
|
data/test/lib/test_case.rb
CHANGED
@@ -21,11 +21,11 @@ module ShardedDatabase
|
|
21
21
|
SQLite3::Database.new(file)
|
22
22
|
end
|
23
23
|
|
24
|
-
#
|
24
|
+
# Setup master table
|
25
25
|
::ActiveRecord::Base.establish_connection :master
|
26
26
|
::ActiveRecord::Base.class_eval do
|
27
27
|
silence do
|
28
|
-
connection.create_table :
|
28
|
+
connection.create_table :aggregate_employees, :force => true do |t|
|
29
29
|
t.string :source
|
30
30
|
t.integer :other_id
|
31
31
|
t.timestamp :created_at
|
@@ -33,18 +33,18 @@ module ShardedDatabase
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
-
#
|
36
|
+
# Setup sharded DBs for employees
|
37
37
|
%w(one two).each do |num|
|
38
|
-
::ActiveRecord::Base.establish_connection "#{num}
|
38
|
+
::ActiveRecord::Base.establish_connection "shard_#{num}".to_sym
|
39
39
|
::ActiveRecord::Base.class_eval do
|
40
40
|
silence do
|
41
|
-
connection.create_table :
|
41
|
+
connection.create_table :employees, :force => true do |t|
|
42
42
|
t.belongs_to :company
|
43
43
|
t.string :name
|
44
44
|
end
|
45
45
|
|
46
46
|
connection.create_table :items, :force => true do |t|
|
47
|
-
t.belongs_to :
|
47
|
+
t.belongs_to :employee
|
48
48
|
t.string :name
|
49
49
|
end
|
50
50
|
|
@@ -62,19 +62,19 @@ module ShardedDatabase
|
|
62
62
|
one_company = Class.new(Connection::One) { set_table_name 'companies' }
|
63
63
|
@company_1 = one_company.create :name => 'One Company'
|
64
64
|
|
65
|
-
|
66
|
-
@one_1 =
|
65
|
+
one_employee = Class.new(Connection::One) { set_table_name 'employees' ; has_many(:items) ; belongs_to(:company) }
|
66
|
+
@one_1 = one_employee.create :name => 'One Employee', :company_id => @company_1.id
|
67
67
|
|
68
|
-
|
69
|
-
@two_1 =
|
70
|
-
@two_2 =
|
68
|
+
two_employee = Class.new(Connection::Two) { set_table_name 'employees' ; has_many(:items) }
|
69
|
+
@two_1 = two_employee.create :name => 'One Employee 1'
|
70
|
+
@two_2 = two_employee.create :name => 'Two Employee 2'
|
71
71
|
|
72
|
-
one_item = Class.new(Connection::One) { set_table_name 'items' ; belongs_to(:
|
73
|
-
one_item.create :name => 'One Test Item', :
|
72
|
+
one_item = Class.new(Connection::One) { set_table_name 'items' ; belongs_to(:employee) }
|
73
|
+
one_item.create :name => 'One Test Item', :employee_id => @one_1.id
|
74
74
|
|
75
|
-
|
76
|
-
|
77
|
-
|
75
|
+
AggregateEmployee.create :source => 'one', :other_id => @one_1.id
|
76
|
+
AggregateEmployee.create :source => 'two', :other_id => @two_1.id
|
77
|
+
AggregateEmployee.create :source => 'two', :other_id => @two_2.id
|
78
78
|
end
|
79
79
|
|
80
80
|
end
|
@@ -4,25 +4,25 @@ class AssociationTest < ShardedDatabase::TestCase
|
|
4
4
|
|
5
5
|
def setup
|
6
6
|
setup_environment
|
7
|
-
@parent =
|
7
|
+
@parent = AggregateEmployee.find_by_source('one')
|
8
8
|
end
|
9
9
|
|
10
10
|
context 'Connection delegation on has_many associations' do
|
11
11
|
|
12
12
|
should 'fetch items from the parent instance connection' do
|
13
13
|
assert ! @parent.items.empty?
|
14
|
-
assert_connection :
|
14
|
+
assert_connection :shard_one, @parent.items.first
|
15
15
|
end
|
16
16
|
|
17
17
|
should 'keep its connection when bubbling up to an associations parent' do
|
18
|
-
assert_equal @parent, @parent.items.first.
|
18
|
+
assert_equal @parent, @parent.items.first.employee
|
19
19
|
end
|
20
20
|
|
21
21
|
end
|
22
22
|
|
23
23
|
context '[UNFINISHED] Connection delegation on belongs_to associations' do
|
24
24
|
|
25
|
-
should 'fetch the associated company for an
|
25
|
+
should 'fetch the associated company for an employee from the respective connection' do
|
26
26
|
assert_instance_of Company, @parent.company
|
27
27
|
end
|
28
28
|
|
@@ -5,18 +5,38 @@ class ConnectionTest < ShardedDatabase::TestCase
|
|
5
5
|
|
6
6
|
|
7
7
|
should 'foundationally support instances having a different connection than their parent class' do
|
8
|
-
assert_not_equal
|
8
|
+
assert_not_equal AggregateEmployee.first.connection, AggregateEmployee.connection
|
9
9
|
end
|
10
10
|
|
11
11
|
should 'have instances of the same source share the same connection' do
|
12
|
-
first, second =
|
12
|
+
first, second = AggregateEmployee.find_all_by_source('two', :limit => 2)
|
13
13
|
|
14
|
-
assert_connection :
|
14
|
+
assert_connection :shard_two, first, second
|
15
15
|
assert_equal first.connection.object_id, second.connection.object_id # ensure that delegation is working correctly
|
16
16
|
end
|
17
17
|
|
18
18
|
should 'display instance connection when inspecting' do
|
19
|
-
assert_match %{(Connection::One)},
|
19
|
+
assert_match %{(Connection::One)}, AggregateEmployee.find_by_source('one').inspect
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'loading records when given a :connection key in the options hash' do
|
23
|
+
|
24
|
+
should 'allow for an ActiveRecord::Base class to be supplied' do
|
25
|
+
object = Employee.first(:connection => Connection::One)
|
26
|
+
assert_connection :shard_one, object
|
27
|
+
end
|
28
|
+
|
29
|
+
should 'allow for a Proc object to be supplied' do
|
30
|
+
proc = lambda { |*args| (args.first % 2) == 1 ? Connection::One : Connection::Two }
|
31
|
+
|
32
|
+
assert_connection :shard_one, Employee.find(1, :connection => proc)
|
33
|
+
assert_connection :shard_two, Employee.find(2, :connection => proc)
|
34
|
+
end
|
35
|
+
|
36
|
+
should 'allow for a symbol to be supplied (which calls a method)' do
|
37
|
+
assert_connection :shard_one, Employee.find(1, :connection => :pick_connection)
|
38
|
+
end
|
39
|
+
|
20
40
|
end
|
21
41
|
|
22
42
|
end
|
@@ -4,14 +4,14 @@ class InstanceTest < ShardedDatabase::TestCase
|
|
4
4
|
def setup ; setup_environment ; end
|
5
5
|
|
6
6
|
|
7
|
-
context 'Loading
|
7
|
+
context 'Loading non-proxyable aggregate objects' do
|
8
8
|
|
9
9
|
setup do
|
10
|
-
@aggregates =
|
10
|
+
@aggregates = AggregateEmployee.all(:aggregate_proxy => false)
|
11
11
|
end
|
12
12
|
|
13
|
-
should 'all be
|
14
|
-
assert @aggregates.all? { |a| a.is_a?(
|
13
|
+
should 'all be AggregateEmployee instances' do
|
14
|
+
assert @aggregates.all? { |a| a.is_a?(AggregateEmployee) }
|
15
15
|
end
|
16
16
|
|
17
17
|
end
|
@@ -19,21 +19,25 @@ class InstanceTest < ShardedDatabase::TestCase
|
|
19
19
|
context "A transformed aggregate instance" do
|
20
20
|
|
21
21
|
setup do
|
22
|
-
@
|
22
|
+
@employee = AggregateEmployee.first
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
should 'channel calls to #class to the proxy class' do
|
26
|
-
assert @
|
26
|
+
assert @employee.is_a?(Employee)
|
27
27
|
end
|
28
28
|
|
29
29
|
should 'have the same attribute fields as the proxy class' do
|
30
|
-
assert_same_elements @
|
30
|
+
assert_same_elements @employee.attributes.keys, Employee.column_names
|
31
31
|
end
|
32
32
|
|
33
33
|
should 'preserve attributes supplied to #preserve_attributes' do
|
34
|
-
assert_equal 'one', @
|
34
|
+
assert_equal 'one', @employee.original_source
|
35
35
|
end
|
36
36
|
|
37
|
+
should 'have #respond_to? proxy to the instance' do
|
38
|
+
assert @employee.respond_to?(:call_company)
|
39
|
+
end
|
40
|
+
|
37
41
|
end
|
38
42
|
|
39
43
|
end
|