sam-dm-core 0.9.6
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/.autotest +26 -0
- data/CONTRIBUTING +51 -0
- data/FAQ +92 -0
- data/History.txt +145 -0
- data/MIT-LICENSE +22 -0
- data/Manifest.txt +125 -0
- data/QUICKLINKS +12 -0
- data/README.txt +143 -0
- data/Rakefile +30 -0
- data/SPECS +63 -0
- data/TODO +1 -0
- data/lib/dm-core.rb +224 -0
- data/lib/dm-core/adapters.rb +4 -0
- data/lib/dm-core/adapters/abstract_adapter.rb +202 -0
- data/lib/dm-core/adapters/data_objects_adapter.rb +707 -0
- data/lib/dm-core/adapters/mysql_adapter.rb +136 -0
- data/lib/dm-core/adapters/postgres_adapter.rb +188 -0
- data/lib/dm-core/adapters/sqlite3_adapter.rb +105 -0
- data/lib/dm-core/associations.rb +199 -0
- data/lib/dm-core/associations/many_to_many.rb +147 -0
- data/lib/dm-core/associations/many_to_one.rb +107 -0
- data/lib/dm-core/associations/one_to_many.rb +309 -0
- data/lib/dm-core/associations/one_to_one.rb +61 -0
- data/lib/dm-core/associations/relationship.rb +218 -0
- data/lib/dm-core/associations/relationship_chain.rb +81 -0
- data/lib/dm-core/auto_migrations.rb +113 -0
- data/lib/dm-core/collection.rb +638 -0
- data/lib/dm-core/dependency_queue.rb +31 -0
- data/lib/dm-core/hook.rb +11 -0
- data/lib/dm-core/identity_map.rb +45 -0
- data/lib/dm-core/is.rb +16 -0
- data/lib/dm-core/logger.rb +232 -0
- data/lib/dm-core/migrations/destructive_migrations.rb +17 -0
- data/lib/dm-core/migrator.rb +29 -0
- data/lib/dm-core/model.rb +471 -0
- data/lib/dm-core/naming_conventions.rb +84 -0
- data/lib/dm-core/property.rb +673 -0
- data/lib/dm-core/property_set.rb +162 -0
- data/lib/dm-core/query.rb +625 -0
- data/lib/dm-core/repository.rb +159 -0
- data/lib/dm-core/resource.rb +637 -0
- data/lib/dm-core/scope.rb +58 -0
- data/lib/dm-core/support.rb +7 -0
- data/lib/dm-core/support/array.rb +13 -0
- data/lib/dm-core/support/assertions.rb +8 -0
- data/lib/dm-core/support/errors.rb +23 -0
- data/lib/dm-core/support/kernel.rb +7 -0
- data/lib/dm-core/support/symbol.rb +41 -0
- data/lib/dm-core/transaction.rb +267 -0
- data/lib/dm-core/type.rb +160 -0
- data/lib/dm-core/type_map.rb +80 -0
- data/lib/dm-core/types.rb +19 -0
- data/lib/dm-core/types/boolean.rb +7 -0
- data/lib/dm-core/types/discriminator.rb +34 -0
- data/lib/dm-core/types/object.rb +24 -0
- data/lib/dm-core/types/paranoid_boolean.rb +34 -0
- data/lib/dm-core/types/paranoid_datetime.rb +33 -0
- data/lib/dm-core/types/serial.rb +9 -0
- data/lib/dm-core/types/text.rb +10 -0
- data/lib/dm-core/version.rb +3 -0
- data/script/all +5 -0
- data/script/performance.rb +203 -0
- data/script/profile.rb +87 -0
- data/spec/integration/association_spec.rb +1371 -0
- data/spec/integration/association_through_spec.rb +203 -0
- data/spec/integration/associations/many_to_many_spec.rb +449 -0
- data/spec/integration/associations/many_to_one_spec.rb +163 -0
- data/spec/integration/associations/one_to_many_spec.rb +151 -0
- data/spec/integration/auto_migrations_spec.rb +398 -0
- data/spec/integration/collection_spec.rb +1069 -0
- data/spec/integration/data_objects_adapter_spec.rb +32 -0
- data/spec/integration/dependency_queue_spec.rb +58 -0
- data/spec/integration/model_spec.rb +127 -0
- data/spec/integration/mysql_adapter_spec.rb +85 -0
- data/spec/integration/postgres_adapter_spec.rb +731 -0
- data/spec/integration/property_spec.rb +233 -0
- data/spec/integration/query_spec.rb +506 -0
- data/spec/integration/repository_spec.rb +57 -0
- data/spec/integration/resource_spec.rb +475 -0
- data/spec/integration/sqlite3_adapter_spec.rb +352 -0
- data/spec/integration/sti_spec.rb +208 -0
- data/spec/integration/strategic_eager_loading_spec.rb +138 -0
- data/spec/integration/transaction_spec.rb +75 -0
- data/spec/integration/type_spec.rb +271 -0
- data/spec/lib/logging_helper.rb +18 -0
- data/spec/lib/mock_adapter.rb +27 -0
- data/spec/lib/model_loader.rb +91 -0
- data/spec/lib/publicize_methods.rb +28 -0
- data/spec/models/vehicles.rb +34 -0
- data/spec/models/zoo.rb +47 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +86 -0
- data/spec/unit/adapters/abstract_adapter_spec.rb +133 -0
- data/spec/unit/adapters/adapter_shared_spec.rb +15 -0
- data/spec/unit/adapters/data_objects_adapter_spec.rb +628 -0
- data/spec/unit/adapters/postgres_adapter_spec.rb +133 -0
- data/spec/unit/associations/many_to_many_spec.rb +17 -0
- data/spec/unit/associations/many_to_one_spec.rb +152 -0
- data/spec/unit/associations/one_to_many_spec.rb +393 -0
- data/spec/unit/associations/one_to_one_spec.rb +7 -0
- data/spec/unit/associations/relationship_spec.rb +71 -0
- data/spec/unit/associations_spec.rb +242 -0
- data/spec/unit/auto_migrations_spec.rb +111 -0
- data/spec/unit/collection_spec.rb +182 -0
- data/spec/unit/data_mapper_spec.rb +35 -0
- data/spec/unit/identity_map_spec.rb +126 -0
- data/spec/unit/is_spec.rb +80 -0
- data/spec/unit/migrator_spec.rb +33 -0
- data/spec/unit/model_spec.rb +339 -0
- data/spec/unit/naming_conventions_spec.rb +36 -0
- data/spec/unit/property_set_spec.rb +83 -0
- data/spec/unit/property_spec.rb +753 -0
- data/spec/unit/query_spec.rb +530 -0
- data/spec/unit/repository_spec.rb +93 -0
- data/spec/unit/resource_spec.rb +626 -0
- data/spec/unit/scope_spec.rb +142 -0
- data/spec/unit/transaction_spec.rb +493 -0
- data/spec/unit/type_map_spec.rb +114 -0
- data/spec/unit/type_spec.rb +119 -0
- data/tasks/ci.rb +68 -0
- data/tasks/dm.rb +63 -0
- data/tasks/doc.rb +20 -0
- data/tasks/gemspec.rb +23 -0
- data/tasks/hoe.rb +46 -0
- data/tasks/install.rb +20 -0
- metadata +216 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
|
2
|
+
|
3
|
+
if [ HAS_SQLITE3, HAS_MYSQL, HAS_POSTGRES ].include?(ADAPTER)
|
4
|
+
describe DataMapper::Adapters::DataObjectsAdapter, "with #{ADAPTER}" do
|
5
|
+
describe 'a connection' do
|
6
|
+
before do
|
7
|
+
@adapter = DataMapper::Repository.adapters[ADAPTER]
|
8
|
+
@transaction = DataMapper::Transaction.new(@adapter)
|
9
|
+
|
10
|
+
@command = mock('command', :execute_non_query => nil)
|
11
|
+
@connection = mock('connection', :create_command => @command)
|
12
|
+
DataObjects::Connection.stub!(:new).and_return(@connection)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should close automatically when no longer needed' do
|
16
|
+
@connection.should_receive(:close)
|
17
|
+
@adapter.execute('SELECT 1')
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should not close when a current transaction is active' do
|
21
|
+
@connection.should_receive(:create_command).with('SELECT 1').twice.and_return(@command)
|
22
|
+
@connection.should_not_receive(:close)
|
23
|
+
|
24
|
+
@transaction.begin
|
25
|
+
@transaction.within do
|
26
|
+
@adapter.execute('SELECT 1')
|
27
|
+
@adapter.execute('SELECT 1')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
|
2
|
+
|
3
|
+
describe "DataMapper::DependencyQueue" do
|
4
|
+
before :each do
|
5
|
+
@q = DataMapper::DependencyQueue.new
|
6
|
+
@dependencies = @q.instance_variable_get("@dependencies")
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#initialize" do
|
10
|
+
describe "@dependencies" do
|
11
|
+
it "should be a hash after initialize" do
|
12
|
+
@dependencies.should be_a_kind_of(Hash)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should set value to [] when new key is accessed" do
|
16
|
+
@dependencies['New Key'].should == []
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "#add" do
|
22
|
+
it "should store the supplied callback in @dependencies" do
|
23
|
+
@q.add('MissingConstant') { true }
|
24
|
+
@dependencies['MissingConstant'].first.call.should == true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#resolve!" do
|
29
|
+
describe "(when dependency is not defined)" do
|
30
|
+
it "should not alter @dependencies" do
|
31
|
+
@q.add('MissingConstant') { true }
|
32
|
+
old_dependencies = @dependencies.dup
|
33
|
+
@q.resolve!
|
34
|
+
old_dependencies.should == @dependencies
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "(when dependency is defined)" do
|
39
|
+
before :each do
|
40
|
+
@q.add('MissingConstant') { |klass| klass.instance_variable_set("@resolved", true) } # add before MissingConstant is loaded
|
41
|
+
|
42
|
+
class MissingConstant
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should execute stored callbacks" do
|
47
|
+
@q.resolve!
|
48
|
+
MissingConstant.instance_variable_get("@resolved").should == true
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should clear @dependencies" do
|
52
|
+
@q.resolve!
|
53
|
+
@dependencies['MissingConstant'].should be_empty
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
|
2
|
+
|
3
|
+
if ADAPTER
|
4
|
+
module ModelSpec
|
5
|
+
class STI
|
6
|
+
include DataMapper::Resource
|
7
|
+
|
8
|
+
def self.default_repository_name
|
9
|
+
ADAPTER
|
10
|
+
end
|
11
|
+
|
12
|
+
property :id, Serial
|
13
|
+
property :name, String
|
14
|
+
property :type, Discriminator
|
15
|
+
end
|
16
|
+
|
17
|
+
class STIDescendant < STI
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "DataMapper::Model with #{ADAPTER}" do
|
22
|
+
before do
|
23
|
+
repository(ADAPTER) do
|
24
|
+
ModelSpec::STI.auto_migrate!
|
25
|
+
end
|
26
|
+
|
27
|
+
@planet = DataMapper::Model.new('planet') do
|
28
|
+
def self.default_repository_name; ADAPTER end
|
29
|
+
property :name, String, :key => true
|
30
|
+
property :distance, Integer
|
31
|
+
end
|
32
|
+
|
33
|
+
@moon = DataMapper::Model.new('moon') do
|
34
|
+
def self.default_repository_name; ADAPTER end
|
35
|
+
property :id, DM::Serial
|
36
|
+
property :name, String
|
37
|
+
end
|
38
|
+
|
39
|
+
@planet.auto_migrate!(ADAPTER)
|
40
|
+
@moon.auto_migrate!(ADAPTER)
|
41
|
+
|
42
|
+
repository(ADAPTER) do
|
43
|
+
@moon.create(:name => "Charon")
|
44
|
+
@moon.create(:name => "Phobos")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '.new' do
|
49
|
+
it 'should be able to persist' do
|
50
|
+
repository(ADAPTER) do
|
51
|
+
pluto = @planet.new
|
52
|
+
pluto.name = 'Pluto'
|
53
|
+
pluto.distance = 1_000_000
|
54
|
+
pluto.save
|
55
|
+
|
56
|
+
clone = @planet.get!('Pluto')
|
57
|
+
clone.name.should == 'Pluto'
|
58
|
+
clone.distance.should == 1_000_000
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe ".get" do
|
64
|
+
include LoggingHelper
|
65
|
+
|
66
|
+
it "should typecast key" do
|
67
|
+
resource = nil
|
68
|
+
lambda {
|
69
|
+
repository(ADAPTER) do
|
70
|
+
resource = @moon.get("1")
|
71
|
+
end
|
72
|
+
}.should_not raise_error
|
73
|
+
resource.should be_kind_of(DataMapper::Resource)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should use the identity map within a repository block" do
|
77
|
+
logger do |log|
|
78
|
+
repository(ADAPTER) do
|
79
|
+
@moon.get("1")
|
80
|
+
@moon.get(1)
|
81
|
+
end
|
82
|
+
log.readlines.size.should == 1
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should not use the identity map outside a repository block" do
|
87
|
+
logger do |log|
|
88
|
+
@moon.get(1)
|
89
|
+
@moon.get(1)
|
90
|
+
log.readlines.size.should == 2
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe ".base_model" do
|
96
|
+
describe "(when called on base model)" do
|
97
|
+
it "should refer to itself" do
|
98
|
+
ModelSpec::STI.base_model.should == ModelSpec::STI
|
99
|
+
end
|
100
|
+
end
|
101
|
+
describe "(when called on descendant model)" do
|
102
|
+
it "should refer to the base model" do
|
103
|
+
ModelSpec::STIDescendant.base_model.should == ModelSpec::STI.base_model
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'should provide #load' do
|
109
|
+
ModelSpec::STI.should respond_to(:load)
|
110
|
+
end
|
111
|
+
|
112
|
+
describe '#load' do
|
113
|
+
it 'should load resources with nil discriminator fields' do
|
114
|
+
resource = ModelSpec::STI.create(:name => 'resource')
|
115
|
+
query = ModelSpec::STI.all.query
|
116
|
+
fields = query.fields
|
117
|
+
|
118
|
+
fields.should == ModelSpec::STI.properties(ADAPTER).slice(:id, :name, :type)
|
119
|
+
|
120
|
+
# would blow up prior to fix
|
121
|
+
lambda {
|
122
|
+
ModelSpec::STI.load([ resource.id, resource.name, nil ], query)
|
123
|
+
}.should_not raise_error(NoMethodError)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
|
2
|
+
|
3
|
+
if HAS_MYSQL
|
4
|
+
describe DataMapper::Adapters::MysqlAdapter do
|
5
|
+
before :all do
|
6
|
+
@adapter = repository(:mysql).adapter
|
7
|
+
end
|
8
|
+
|
9
|
+
before :all do
|
10
|
+
class Sputnik
|
11
|
+
include DataMapper::Resource
|
12
|
+
|
13
|
+
property :id, Serial
|
14
|
+
property :name, DM::Text
|
15
|
+
property :object, Object
|
16
|
+
|
17
|
+
auto_migrate!(:mysql)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should handle Object type" do
|
22
|
+
time = Time.now
|
23
|
+
repository(:mysql) do
|
24
|
+
Sputnik.create(:name => "Sputnik", :object => time)
|
25
|
+
Sputnik.first.object.should == time
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "auto migrating" do
|
30
|
+
it "#upgrade_model should work" do
|
31
|
+
@adapter.destroy_model_storage(repository(:mysql), Sputnik)
|
32
|
+
@adapter.storage_exists?("sputniks").should == false
|
33
|
+
Sputnik.auto_migrate!(:mysql)
|
34
|
+
@adapter.storage_exists?("sputniks").should == true
|
35
|
+
@adapter.field_exists?("sputniks", "new_prop").should == false
|
36
|
+
Sputnik.property :new_prop, Integer
|
37
|
+
Sputnik.auto_upgrade!(:mysql)
|
38
|
+
@adapter.field_exists?("sputniks", "new_prop").should == true
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "querying metadata" do
|
43
|
+
it "#storage_exists? should return true for tables that exist" do
|
44
|
+
@adapter.storage_exists?("sputniks").should == true
|
45
|
+
end
|
46
|
+
|
47
|
+
it "#storage_exists? should return false for tables that don't exist" do
|
48
|
+
@adapter.storage_exists?("space turds").should == false
|
49
|
+
end
|
50
|
+
|
51
|
+
it "#field_exists? should return true for columns that exist" do
|
52
|
+
@adapter.field_exists?("sputniks", "name").should == true
|
53
|
+
end
|
54
|
+
|
55
|
+
it "#storage_exists? should return false for tables that don't exist" do
|
56
|
+
@adapter.field_exists?("sputniks", "plur").should == false
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "handling transactions" do
|
61
|
+
before do
|
62
|
+
@transaction = DataMapper::Transaction.new(@adapter)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should rollback changes when #rollback_transaction is called" do
|
66
|
+
repository(:mysql) do
|
67
|
+
@transaction.commit do |trans|
|
68
|
+
Sputnik.create(:name => 'my pretty sputnik')
|
69
|
+
trans.rollback
|
70
|
+
end
|
71
|
+
Sputnik.all(:name => 'my pretty sputnik').should be_empty
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should commit changes when #commit_transaction is called" do
|
76
|
+
repository(:mysql) do
|
77
|
+
@transaction.commit do
|
78
|
+
Sputnik.create(:name => 'my pretty sputnik')
|
79
|
+
end
|
80
|
+
Sputnik.all(:name => 'my pretty sputnik').size.should == 1
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,731 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
|
2
|
+
|
3
|
+
if HAS_POSTGRES
|
4
|
+
describe DataMapper::Adapters::PostgresAdapter do
|
5
|
+
before :all do
|
6
|
+
@adapter = repository(:postgres).adapter
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "auto migrating" do
|
10
|
+
before :all do
|
11
|
+
class Sputnik
|
12
|
+
include DataMapper::Resource
|
13
|
+
|
14
|
+
property :id, Serial
|
15
|
+
property :name, DM::Text
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it "#upgrade_model should work" do
|
20
|
+
@adapter.destroy_model_storage(repository(:postgres), Sputnik)
|
21
|
+
@adapter.storage_exists?("sputniks").should be_false
|
22
|
+
Sputnik.auto_migrate!(:postgres)
|
23
|
+
@adapter.storage_exists?("sputniks").should be_true
|
24
|
+
@adapter.field_exists?("sputniks", "new_prop").should be_false
|
25
|
+
Sputnik.property :new_prop, DM::Serial
|
26
|
+
@adapter.send(:drop_sequence, repository(:postgres), Sputnik.new_prop)
|
27
|
+
Sputnik.auto_upgrade!(:postgres)
|
28
|
+
@adapter.field_exists?("sputniks", "new_prop").should == true
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe '#312' do
|
33
|
+
it "should behave sanely for time fields" do
|
34
|
+
|
35
|
+
class Thing
|
36
|
+
include DataMapper::Resource
|
37
|
+
property :id, Integer, :serial => true
|
38
|
+
property :created_at, Time
|
39
|
+
end
|
40
|
+
|
41
|
+
Thing.auto_migrate!(:postgres)
|
42
|
+
|
43
|
+
repository(:postgres) do
|
44
|
+
time_now = Time.now
|
45
|
+
|
46
|
+
t = Thing.new
|
47
|
+
t.created_at = time_now
|
48
|
+
|
49
|
+
t.save
|
50
|
+
|
51
|
+
t1 = Thing.first
|
52
|
+
t1.created_at.should == time_now
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "querying metadata" do
|
59
|
+
before :all do
|
60
|
+
class Sputnik
|
61
|
+
include DataMapper::Resource
|
62
|
+
|
63
|
+
property :id, Serial
|
64
|
+
property :name, DM::Text
|
65
|
+
end
|
66
|
+
|
67
|
+
Sputnik.auto_migrate!(:postgres)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "#storage_exists? should return true for tables that exist" do
|
71
|
+
@adapter.storage_exists?("sputniks").should == true
|
72
|
+
end
|
73
|
+
|
74
|
+
it "#storage_exists? should return false for tables that don't exist" do
|
75
|
+
@adapter.storage_exists?("space turds").should == false
|
76
|
+
end
|
77
|
+
|
78
|
+
it "#field_exists? should return true for columns that exist" do
|
79
|
+
@adapter.field_exists?("sputniks", "name").should == true
|
80
|
+
end
|
81
|
+
|
82
|
+
it "#field_exists? should return false for columns that don't exist" do
|
83
|
+
@adapter.field_exists?("sputniks", "plur").should == false
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "handling transactions" do
|
88
|
+
before :all do
|
89
|
+
class Sputnik
|
90
|
+
include DataMapper::Resource
|
91
|
+
|
92
|
+
property :id, Serial
|
93
|
+
property :name, DM::Text
|
94
|
+
end
|
95
|
+
|
96
|
+
Sputnik.auto_migrate!(:postgres)
|
97
|
+
end
|
98
|
+
|
99
|
+
before do
|
100
|
+
@transaction = DataMapper::Transaction.new(@adapter)
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should rollback changes when #rollback_transaction is called" do
|
104
|
+
@transaction.commit do |trans|
|
105
|
+
@adapter.execute("INSERT INTO sputniks (name) VALUES ('my pretty sputnik')")
|
106
|
+
trans.rollback
|
107
|
+
end
|
108
|
+
@adapter.query("SELECT * FROM sputniks WHERE name = 'my pretty sputnik'").empty?.should == true
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should commit changes when #commit_transaction is called" do
|
112
|
+
@transaction.commit do
|
113
|
+
@adapter.execute("INSERT INTO sputniks (name) VALUES ('my pretty sputnik')")
|
114
|
+
end
|
115
|
+
@adapter.query("SELECT * FROM sputniks WHERE name = 'my pretty sputnik'").size.should == 1
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe "reading & writing a database" do
|
120
|
+
before :all do
|
121
|
+
class User
|
122
|
+
include DataMapper::Resource
|
123
|
+
|
124
|
+
property :id, Serial
|
125
|
+
property :name, DM::Text
|
126
|
+
end
|
127
|
+
|
128
|
+
class Voyager
|
129
|
+
include DataMapper::Resource
|
130
|
+
storage_names[:postgres] = 'sattellites.voyagers'
|
131
|
+
|
132
|
+
property :id, Serial
|
133
|
+
property :age, Integer
|
134
|
+
end
|
135
|
+
|
136
|
+
# Voyager.auto_migrate!(:postgres)
|
137
|
+
end
|
138
|
+
|
139
|
+
before do
|
140
|
+
User.auto_migrate!(:postgres)
|
141
|
+
|
142
|
+
@adapter.execute("INSERT INTO users (name) VALUES ('Paul')")
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should be able to specify a schema name as part of the storage name" do
|
146
|
+
pending "This works, but no create-schema support in PostgresAdapter to easily test with"
|
147
|
+
lambda do
|
148
|
+
repository(:postgres) do
|
149
|
+
Voyager.create(:age => 1_000)
|
150
|
+
end
|
151
|
+
end.should_not raise_error
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'should be able to #execute an arbitrary query' do
|
155
|
+
result = @adapter.execute("INSERT INTO users (name) VALUES ('Sam')")
|
156
|
+
|
157
|
+
result.affected_rows.should == 1
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'should be able to #query' do
|
161
|
+
result = @adapter.query("SELECT * FROM users")
|
162
|
+
|
163
|
+
result.should be_kind_of(Array)
|
164
|
+
row = result.first
|
165
|
+
row.should be_kind_of(Struct)
|
166
|
+
row.members.should == %w{id name}
|
167
|
+
|
168
|
+
row.id.should == 1
|
169
|
+
row.name.should == 'Paul'
|
170
|
+
end
|
171
|
+
|
172
|
+
it 'should return an empty array if #query found no rows' do
|
173
|
+
@adapter.execute("DELETE FROM users")
|
174
|
+
|
175
|
+
result = nil
|
176
|
+
lambda { result = @adapter.query("SELECT * FROM users") }.should_not raise_error
|
177
|
+
|
178
|
+
result.should be_kind_of(Array)
|
179
|
+
result.size.should == 0
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
describe "CRUD for serial Key" do
|
184
|
+
before :all do
|
185
|
+
class VideoGame
|
186
|
+
include DataMapper::Resource
|
187
|
+
|
188
|
+
property :id, Serial
|
189
|
+
property :name, String
|
190
|
+
property :object, Object
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
before do
|
195
|
+
VideoGame.auto_migrate!(:postgres)
|
196
|
+
end
|
197
|
+
|
198
|
+
it 'should be able to create a record' do
|
199
|
+
time = Time.now
|
200
|
+
game = VideoGame.new(:name => 'System Shock', :object => time)
|
201
|
+
repository(:postgres) do
|
202
|
+
game.save
|
203
|
+
game.should_not be_a_new_record
|
204
|
+
game.should_not be_dirty
|
205
|
+
|
206
|
+
saved = VideoGame.first(:name => game.name)
|
207
|
+
saved.id.should == game.id
|
208
|
+
saved.object.should == time
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'should be able to read a record' do
|
213
|
+
name = 'Wing Commander: Privateer'
|
214
|
+
id = @adapter.execute('INSERT INTO "video_games" ("name") VALUES (?) RETURNING id', name).insert_id
|
215
|
+
|
216
|
+
game = repository(:postgres) do
|
217
|
+
VideoGame.get(id)
|
218
|
+
end
|
219
|
+
|
220
|
+
game.name.should == name
|
221
|
+
game.should_not be_dirty
|
222
|
+
game.should_not be_a_new_record
|
223
|
+
end
|
224
|
+
|
225
|
+
it 'should be able to update a record' do
|
226
|
+
name = 'Resistance: Fall of Mon'
|
227
|
+
id = @adapter.execute('INSERT INTO "video_games" ("name") VALUES (?) RETURNING id', name).insert_id
|
228
|
+
|
229
|
+
game = repository(:postgres) do
|
230
|
+
VideoGame.get(id)
|
231
|
+
end
|
232
|
+
|
233
|
+
game.should_not be_a_new_record
|
234
|
+
|
235
|
+
game.should_not be_dirty
|
236
|
+
game.name = game.name.sub(/Mon/, 'Man')
|
237
|
+
game.should be_dirty
|
238
|
+
|
239
|
+
repository(:postgres) do
|
240
|
+
game.save
|
241
|
+
end
|
242
|
+
|
243
|
+
game.should_not be_dirty
|
244
|
+
|
245
|
+
clone = repository(:postgres) do
|
246
|
+
VideoGame.get(id)
|
247
|
+
end
|
248
|
+
|
249
|
+
clone.name.should == game.name
|
250
|
+
end
|
251
|
+
|
252
|
+
it 'should be able to delete a record' do
|
253
|
+
name = 'Zelda'
|
254
|
+
id = @adapter.execute('INSERT INTO "video_games" ("name") VALUES (?) RETURNING id', name).insert_id
|
255
|
+
|
256
|
+
game = repository(:postgres) do
|
257
|
+
VideoGame.get(id)
|
258
|
+
end
|
259
|
+
|
260
|
+
game.name.should == name
|
261
|
+
|
262
|
+
repository(:postgres) do
|
263
|
+
game.destroy.should be_true
|
264
|
+
end
|
265
|
+
|
266
|
+
game.should be_a_new_record
|
267
|
+
game.should be_dirty
|
268
|
+
end
|
269
|
+
|
270
|
+
it 'should respond to Resource#get' do
|
271
|
+
name = 'Contra'
|
272
|
+
id = @adapter.execute('INSERT INTO "video_games" ("name") VALUES (?) RETURNING id', name).insert_id
|
273
|
+
|
274
|
+
contra = repository(:postgres) { VideoGame.get(id) }
|
275
|
+
|
276
|
+
contra.should_not be_nil
|
277
|
+
contra.should_not be_dirty
|
278
|
+
contra.should_not be_a_new_record
|
279
|
+
contra.id.should == id
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
describe "CRUD for Composite Key" do
|
284
|
+
before :all do
|
285
|
+
class BankCustomer
|
286
|
+
include DataMapper::Resource
|
287
|
+
|
288
|
+
property :bank, String, :key => true
|
289
|
+
property :account_number, String, :key => true
|
290
|
+
property :name, String
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
before do
|
295
|
+
BankCustomer.auto_migrate!(:postgres)
|
296
|
+
end
|
297
|
+
|
298
|
+
it 'should be able to create a record' do
|
299
|
+
customer = BankCustomer.new(:bank => 'Community Bank', :account_number => '123456', :name => 'David Hasselhoff')
|
300
|
+
repository(:postgres) do
|
301
|
+
customer.save
|
302
|
+
end
|
303
|
+
|
304
|
+
customer.should_not be_a_new_record
|
305
|
+
customer.should_not be_dirty
|
306
|
+
|
307
|
+
row = @adapter.query('SELECT "bank", "account_number" FROM "bank_customers" WHERE "name" = ?', customer.name).first
|
308
|
+
row.bank.should == customer.bank
|
309
|
+
row.account_number.should == customer.account_number
|
310
|
+
end
|
311
|
+
|
312
|
+
it 'should be able to read a record' do
|
313
|
+
bank, account_number, name = 'Chase', '4321', 'Super Wonderful'
|
314
|
+
@adapter.execute('INSERT INTO "bank_customers" ("bank", "account_number", "name") VALUES (?, ?, ?)', bank, account_number, name)
|
315
|
+
|
316
|
+
repository(:postgres) do
|
317
|
+
BankCustomer.get(bank, account_number).name.should == name
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
it 'should be able to update a record' do
|
322
|
+
bank, account_number, name = 'Wells Fargo', '00101001', 'Spider Pig'
|
323
|
+
@adapter.execute('INSERT INTO "bank_customers" ("bank", "account_number", "name") VALUES (?, ?, ?)', bank, account_number, name)
|
324
|
+
|
325
|
+
customer = repository(:postgres) do
|
326
|
+
BankCustomer.get(bank, account_number)
|
327
|
+
end
|
328
|
+
|
329
|
+
customer.name = 'Bat-Pig'
|
330
|
+
|
331
|
+
customer.should_not be_a_new_record
|
332
|
+
customer.should be_dirty
|
333
|
+
|
334
|
+
customer.save
|
335
|
+
|
336
|
+
customer.should_not be_dirty
|
337
|
+
|
338
|
+
clone = repository(:postgres) do
|
339
|
+
BankCustomer.get(bank, account_number)
|
340
|
+
end
|
341
|
+
|
342
|
+
clone.name.should == customer.name
|
343
|
+
end
|
344
|
+
|
345
|
+
it 'should be able to delete a record' do
|
346
|
+
bank, account_number, name = 'Megacorp', 'ABC', 'Flash Gordon'
|
347
|
+
@adapter.execute('INSERT INTO "bank_customers" ("bank", "account_number", "name") VALUES (?, ?, ?)', bank, account_number, name)
|
348
|
+
|
349
|
+
customer = repository(:postgres) do
|
350
|
+
BankCustomer.get(bank, account_number)
|
351
|
+
end
|
352
|
+
|
353
|
+
customer.name.should == name
|
354
|
+
|
355
|
+
repository(:postgres) do
|
356
|
+
customer.destroy.should be_true
|
357
|
+
end
|
358
|
+
|
359
|
+
customer.should be_a_new_record
|
360
|
+
customer.should be_dirty
|
361
|
+
end
|
362
|
+
|
363
|
+
it 'should respond to Resource#get' do
|
364
|
+
bank, account_number, name = 'Conchords', '1100101', 'Robo Boogie'
|
365
|
+
@adapter.execute('INSERT INTO "bank_customers" ("bank", "account_number", "name") VALUES (?, ?, ?)', bank, account_number, name)
|
366
|
+
|
367
|
+
robots = repository(:postgres) { BankCustomer.get(bank, account_number) }
|
368
|
+
|
369
|
+
robots.should_not be_nil
|
370
|
+
robots.should_not be_dirty
|
371
|
+
robots.should_not be_a_new_record
|
372
|
+
robots.bank.should == bank
|
373
|
+
robots.account_number.should == account_number
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
describe "Ordering a Query" do
|
378
|
+
before :all do
|
379
|
+
class SailBoat
|
380
|
+
include DataMapper::Resource
|
381
|
+
property :id, Serial
|
382
|
+
property :name, String
|
383
|
+
property :port, String
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
before do
|
388
|
+
SailBoat.auto_migrate!(:postgres)
|
389
|
+
|
390
|
+
repository(:postgres) do
|
391
|
+
SailBoat.create(:id => 1, :name => "A", :port => "C")
|
392
|
+
SailBoat.create(:id => 2, :name => "B", :port => "B")
|
393
|
+
SailBoat.create(:id => 3, :name => "C", :port => "A")
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
it "should order results" do
|
398
|
+
repository(:postgres) do
|
399
|
+
result = SailBoat.all(:order => [
|
400
|
+
DataMapper::Query::Direction.new(SailBoat.properties[:name], :asc)
|
401
|
+
])
|
402
|
+
result[0].id.should == 1
|
403
|
+
|
404
|
+
result = SailBoat.all(:order => [
|
405
|
+
DataMapper::Query::Direction.new(SailBoat.properties[:port], :asc)
|
406
|
+
])
|
407
|
+
result[0].id.should == 3
|
408
|
+
|
409
|
+
result = SailBoat.all(:order => [
|
410
|
+
DataMapper::Query::Direction.new(SailBoat.properties[:name], :asc),
|
411
|
+
DataMapper::Query::Direction.new(SailBoat.properties[:port], :asc)
|
412
|
+
])
|
413
|
+
result[0].id.should == 1
|
414
|
+
|
415
|
+
result = SailBoat.all(:order => [
|
416
|
+
SailBoat.properties[:name],
|
417
|
+
DataMapper::Query::Direction.new(SailBoat.properties[:port], :asc)
|
418
|
+
])
|
419
|
+
result[0].id.should == 1
|
420
|
+
end
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
describe "Lazy Loaded Properties" do
|
425
|
+
before :all do
|
426
|
+
class SailBoat
|
427
|
+
include DataMapper::Resource
|
428
|
+
property :id, Serial
|
429
|
+
property :notes, String, :lazy => [:notes]
|
430
|
+
property :trip_report, String, :lazy => [:notes,:trip]
|
431
|
+
property :miles, Integer, :lazy => [:trip]
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
before do
|
436
|
+
SailBoat.auto_migrate!(:postgres)
|
437
|
+
|
438
|
+
repository(:postgres) do
|
439
|
+
SailBoat.create(:id => 1, :notes=>'Note',:trip_report=>'Report',:miles=>23)
|
440
|
+
SailBoat.create(:id => 2, :notes=>'Note',:trip_report=>'Report',:miles=>23)
|
441
|
+
SailBoat.create(:id => 3, :notes=>'Note',:trip_report=>'Report',:miles=>23)
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
it "should lazy load" do
|
446
|
+
result = repository(:postgres) { SailBoat.all.to_a }
|
447
|
+
|
448
|
+
result[0].attribute_loaded?(:notes).should be_false
|
449
|
+
result[0].attribute_loaded?(:trip_report).should be_false
|
450
|
+
result[1].attribute_loaded?(:notes).should be_false
|
451
|
+
|
452
|
+
result[1].notes.should_not be_nil
|
453
|
+
|
454
|
+
result[1].attribute_loaded?(:notes).should be_true
|
455
|
+
result[1].attribute_loaded?(:trip_report).should be_true
|
456
|
+
result[1].attribute_loaded?(:miles).should be_false
|
457
|
+
|
458
|
+
result = repository(:postgres) { SailBoat.all.to_a }
|
459
|
+
|
460
|
+
result[0].attribute_loaded?(:trip_report).should be_false
|
461
|
+
result[0].attribute_loaded?(:miles).should be_false
|
462
|
+
|
463
|
+
result[1].trip_report.should_not be_nil
|
464
|
+
result[2].attribute_loaded?(:miles).should be_true
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
describe "finders" do
|
469
|
+
before :all do
|
470
|
+
class SerialFinderSpec
|
471
|
+
include DataMapper::Resource
|
472
|
+
|
473
|
+
property :id, Serial
|
474
|
+
property :sample, String
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
before do
|
479
|
+
SerialFinderSpec.auto_migrate!(:postgres)
|
480
|
+
|
481
|
+
repository(:postgres) do
|
482
|
+
100.times do
|
483
|
+
SerialFinderSpec.create(:sample => rand.to_s)
|
484
|
+
end
|
485
|
+
end
|
486
|
+
end
|
487
|
+
|
488
|
+
it "should return all available rows" do
|
489
|
+
repository(:postgres) do
|
490
|
+
SerialFinderSpec.all.should have(100).entries
|
491
|
+
end
|
492
|
+
end
|
493
|
+
|
494
|
+
it "should allow limit and offset" do
|
495
|
+
repository(:postgres) do
|
496
|
+
SerialFinderSpec.all(:limit => 50).should have(50).entries
|
497
|
+
|
498
|
+
SerialFinderSpec.all(:limit => 20, :offset => 40).map { |entry| entry.id }.should == SerialFinderSpec.all[40...60].map { |entry| entry.id }
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
502
|
+
it "should lazy-load missing attributes" do
|
503
|
+
sfs = repository(:postgres) do
|
504
|
+
SerialFinderSpec.first(:fields => [ :id ])
|
505
|
+
end
|
506
|
+
|
507
|
+
sfs.should be_a_kind_of(SerialFinderSpec)
|
508
|
+
sfs.should_not be_a_new_record
|
509
|
+
|
510
|
+
sfs.attribute_loaded?(:sample).should be_false
|
511
|
+
sfs.sample
|
512
|
+
sfs.attribute_loaded?(:sample).should be_true
|
513
|
+
end
|
514
|
+
|
515
|
+
it "should translate an Array to an IN clause" do
|
516
|
+
ids = repository(:postgres) do
|
517
|
+
SerialFinderSpec.all(:limit => 10).map { |entry| entry.id }
|
518
|
+
end
|
519
|
+
|
520
|
+
results = repository(:postgres) do
|
521
|
+
SerialFinderSpec.all(:id => ids)
|
522
|
+
end
|
523
|
+
|
524
|
+
results.size.should == 10
|
525
|
+
results.map { |entry| entry.id }.should == ids
|
526
|
+
end
|
527
|
+
end
|
528
|
+
|
529
|
+
describe "belongs_to associations" do
|
530
|
+
before :all do
|
531
|
+
class Engine
|
532
|
+
include DataMapper::Resource
|
533
|
+
def self.default_repository_name; :postgres end
|
534
|
+
|
535
|
+
property :id, Serial
|
536
|
+
property :name, String
|
537
|
+
end
|
538
|
+
|
539
|
+
class Yard
|
540
|
+
include DataMapper::Resource
|
541
|
+
def self.default_repository_name; :postgres end
|
542
|
+
|
543
|
+
property :id, Serial
|
544
|
+
property :name, String
|
545
|
+
property :engine_id, Integer
|
546
|
+
|
547
|
+
belongs_to :engine
|
548
|
+
end
|
549
|
+
end
|
550
|
+
|
551
|
+
before do
|
552
|
+
Engine.auto_migrate!(:postgres)
|
553
|
+
|
554
|
+
@adapter.execute('INSERT INTO "engines" ("id", "name") values (?, ?)', 1, 'engine1')
|
555
|
+
@adapter.execute('INSERT INTO "engines" ("id", "name") values (?, ?)', 2, 'engine2')
|
556
|
+
|
557
|
+
Yard.auto_migrate!(:postgres)
|
558
|
+
|
559
|
+
@adapter.execute('INSERT INTO "yards" ("id", "name", "engine_id") values (?, ?, ?)', 1, 'yard1', 1)
|
560
|
+
end
|
561
|
+
|
562
|
+
it "should load without the parent"
|
563
|
+
|
564
|
+
it 'should allow substituting the parent' do
|
565
|
+
repository(:postgres) do
|
566
|
+
y = Yard.first(:id => 1)
|
567
|
+
e = Engine.first(:id => 2)
|
568
|
+
y.engine = e
|
569
|
+
y.save
|
570
|
+
end
|
571
|
+
|
572
|
+
repository(:postgres) do
|
573
|
+
Yard.first(:id => 1).engine_id.should == 2
|
574
|
+
end
|
575
|
+
end
|
576
|
+
|
577
|
+
it "#belongs_to" do
|
578
|
+
yard = Yard.new
|
579
|
+
yard.should respond_to(:engine)
|
580
|
+
yard.should respond_to(:engine=)
|
581
|
+
end
|
582
|
+
|
583
|
+
it "should load the associated instance" do
|
584
|
+
y = repository(:postgres) do
|
585
|
+
Yard.first(:id => 1)
|
586
|
+
end
|
587
|
+
y.engine.should_not be_nil
|
588
|
+
y.engine.id.should == 1
|
589
|
+
y.engine.name.should == "engine1"
|
590
|
+
end
|
591
|
+
|
592
|
+
it 'should save the association key in the child' do
|
593
|
+
repository(:postgres) do
|
594
|
+
e = Engine.first(:id => 2)
|
595
|
+
Yard.create(:id => 2, :name => 'yard2', :engine => e)
|
596
|
+
end
|
597
|
+
|
598
|
+
repository(:postgres) do
|
599
|
+
Yard.first(:id => 2).engine_id.should == 2
|
600
|
+
end
|
601
|
+
end
|
602
|
+
|
603
|
+
it 'should save the parent upon saving of child' do
|
604
|
+
repository(:postgres) do
|
605
|
+
e = Engine.new(:id => 10, :name => "engine10")
|
606
|
+
y = Yard.new(:id => 10, :name => "Yard10", :engine => e)
|
607
|
+
y.save
|
608
|
+
|
609
|
+
y.engine_id.should == 10
|
610
|
+
end
|
611
|
+
|
612
|
+
repository(:postgres) do
|
613
|
+
Engine.first(:id => 10).should_not be_nil
|
614
|
+
end
|
615
|
+
end
|
616
|
+
end
|
617
|
+
|
618
|
+
describe "has n associations" do
|
619
|
+
before :all do
|
620
|
+
class Host
|
621
|
+
include DataMapper::Resource
|
622
|
+
def self.default_repository_name; :postgres end
|
623
|
+
|
624
|
+
property :id, Serial
|
625
|
+
property :name, String
|
626
|
+
|
627
|
+
has n, :slices
|
628
|
+
end
|
629
|
+
|
630
|
+
class Slice
|
631
|
+
include DataMapper::Resource
|
632
|
+
def self.default_repository_name; :postgres end
|
633
|
+
|
634
|
+
property :id, Serial
|
635
|
+
property :name, String
|
636
|
+
property :host_id, Integer
|
637
|
+
|
638
|
+
belongs_to :host
|
639
|
+
end
|
640
|
+
end
|
641
|
+
|
642
|
+
before do
|
643
|
+
Host.auto_migrate!(:postgres)
|
644
|
+
Slice.auto_migrate!(:postgres)
|
645
|
+
|
646
|
+
@adapter.execute('INSERT INTO "hosts" ("id", "name") values (?, ?)', 1, 'host1')
|
647
|
+
@adapter.execute('INSERT INTO "hosts" ("id", "name") values (?, ?)', 2, 'host2')
|
648
|
+
|
649
|
+
@adapter.execute('INSERT INTO "slices" ("id", "name", "host_id") values (?, ?, ?)', 1, 'slice1', 1)
|
650
|
+
@adapter.execute('INSERT INTO "slices" ("id", "name", "host_id") values (?, ?, ?)', 2, 'slice2', 1)
|
651
|
+
end
|
652
|
+
|
653
|
+
it "#has n" do
|
654
|
+
h = Host.new
|
655
|
+
h.should respond_to(:slices)
|
656
|
+
end
|
657
|
+
|
658
|
+
it "should allow removal of a child through a loaded association" do
|
659
|
+
h = repository(:postgres) do
|
660
|
+
Host.first(:id => 1)
|
661
|
+
end
|
662
|
+
|
663
|
+
s = h.slices.first
|
664
|
+
|
665
|
+
h.slices.delete(s)
|
666
|
+
h.slices.size.should == 1
|
667
|
+
h.save
|
668
|
+
|
669
|
+
s = repository(:postgres) do
|
670
|
+
Slice.first(:id => s.id)
|
671
|
+
end
|
672
|
+
|
673
|
+
s.host.should be_nil
|
674
|
+
s.host_id.should be_nil
|
675
|
+
end
|
676
|
+
|
677
|
+
it "should load the associated instances" do
|
678
|
+
h = repository(:postgres) do
|
679
|
+
Host.first(:id => 1)
|
680
|
+
end
|
681
|
+
|
682
|
+
h.slices.should_not be_nil
|
683
|
+
h.slices.size.should == 2
|
684
|
+
h.slices.first.id.should == 1
|
685
|
+
h.slices.last.id.should == 2
|
686
|
+
end
|
687
|
+
|
688
|
+
it "should add and save the associated instance" do
|
689
|
+
repository(:postgres) do
|
690
|
+
h = Host.first(:id => 1)
|
691
|
+
|
692
|
+
h.slices << Slice.new(:id => 3, :name => 'slice3')
|
693
|
+
h.save
|
694
|
+
|
695
|
+
s = repository(:postgres) do
|
696
|
+
Slice.first(:id => 3)
|
697
|
+
end
|
698
|
+
|
699
|
+
s.host.id.should == 1
|
700
|
+
end
|
701
|
+
end
|
702
|
+
|
703
|
+
it "should not save the associated instance if the parent is not saved" do
|
704
|
+
repository(:postgres) do
|
705
|
+
h = Host.new(:id => 10, :name => "host10")
|
706
|
+
h.slices << Slice.new(:id => 10, :name => 'slice10')
|
707
|
+
end
|
708
|
+
|
709
|
+
repository(:postgres) do
|
710
|
+
Slice.first(:id => 10).should be_nil
|
711
|
+
end
|
712
|
+
end
|
713
|
+
|
714
|
+
it "should save the associated instance upon saving of parent" do
|
715
|
+
repository(:postgres) do
|
716
|
+
h = Host.new(:id => 10, :name => "host10")
|
717
|
+
h.slices << Slice.new(:id => 10, :name => 'slice10')
|
718
|
+
h.save
|
719
|
+
end
|
720
|
+
|
721
|
+
s = repository(:postgres) do
|
722
|
+
Slice.first(:id => 10)
|
723
|
+
end
|
724
|
+
|
725
|
+
s.should_not be_nil
|
726
|
+
s.host.should_not be_nil
|
727
|
+
s.host.id.should == 10
|
728
|
+
end
|
729
|
+
end
|
730
|
+
end
|
731
|
+
end
|