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.
Files changed (126) hide show
  1. data/.autotest +26 -0
  2. data/CONTRIBUTING +51 -0
  3. data/FAQ +92 -0
  4. data/History.txt +145 -0
  5. data/MIT-LICENSE +22 -0
  6. data/Manifest.txt +125 -0
  7. data/QUICKLINKS +12 -0
  8. data/README.txt +143 -0
  9. data/Rakefile +30 -0
  10. data/SPECS +63 -0
  11. data/TODO +1 -0
  12. data/lib/dm-core.rb +224 -0
  13. data/lib/dm-core/adapters.rb +4 -0
  14. data/lib/dm-core/adapters/abstract_adapter.rb +202 -0
  15. data/lib/dm-core/adapters/data_objects_adapter.rb +707 -0
  16. data/lib/dm-core/adapters/mysql_adapter.rb +136 -0
  17. data/lib/dm-core/adapters/postgres_adapter.rb +188 -0
  18. data/lib/dm-core/adapters/sqlite3_adapter.rb +105 -0
  19. data/lib/dm-core/associations.rb +199 -0
  20. data/lib/dm-core/associations/many_to_many.rb +147 -0
  21. data/lib/dm-core/associations/many_to_one.rb +107 -0
  22. data/lib/dm-core/associations/one_to_many.rb +309 -0
  23. data/lib/dm-core/associations/one_to_one.rb +61 -0
  24. data/lib/dm-core/associations/relationship.rb +218 -0
  25. data/lib/dm-core/associations/relationship_chain.rb +81 -0
  26. data/lib/dm-core/auto_migrations.rb +113 -0
  27. data/lib/dm-core/collection.rb +638 -0
  28. data/lib/dm-core/dependency_queue.rb +31 -0
  29. data/lib/dm-core/hook.rb +11 -0
  30. data/lib/dm-core/identity_map.rb +45 -0
  31. data/lib/dm-core/is.rb +16 -0
  32. data/lib/dm-core/logger.rb +232 -0
  33. data/lib/dm-core/migrations/destructive_migrations.rb +17 -0
  34. data/lib/dm-core/migrator.rb +29 -0
  35. data/lib/dm-core/model.rb +471 -0
  36. data/lib/dm-core/naming_conventions.rb +84 -0
  37. data/lib/dm-core/property.rb +673 -0
  38. data/lib/dm-core/property_set.rb +162 -0
  39. data/lib/dm-core/query.rb +625 -0
  40. data/lib/dm-core/repository.rb +159 -0
  41. data/lib/dm-core/resource.rb +637 -0
  42. data/lib/dm-core/scope.rb +58 -0
  43. data/lib/dm-core/support.rb +7 -0
  44. data/lib/dm-core/support/array.rb +13 -0
  45. data/lib/dm-core/support/assertions.rb +8 -0
  46. data/lib/dm-core/support/errors.rb +23 -0
  47. data/lib/dm-core/support/kernel.rb +7 -0
  48. data/lib/dm-core/support/symbol.rb +41 -0
  49. data/lib/dm-core/transaction.rb +267 -0
  50. data/lib/dm-core/type.rb +160 -0
  51. data/lib/dm-core/type_map.rb +80 -0
  52. data/lib/dm-core/types.rb +19 -0
  53. data/lib/dm-core/types/boolean.rb +7 -0
  54. data/lib/dm-core/types/discriminator.rb +34 -0
  55. data/lib/dm-core/types/object.rb +24 -0
  56. data/lib/dm-core/types/paranoid_boolean.rb +34 -0
  57. data/lib/dm-core/types/paranoid_datetime.rb +33 -0
  58. data/lib/dm-core/types/serial.rb +9 -0
  59. data/lib/dm-core/types/text.rb +10 -0
  60. data/lib/dm-core/version.rb +3 -0
  61. data/script/all +5 -0
  62. data/script/performance.rb +203 -0
  63. data/script/profile.rb +87 -0
  64. data/spec/integration/association_spec.rb +1371 -0
  65. data/spec/integration/association_through_spec.rb +203 -0
  66. data/spec/integration/associations/many_to_many_spec.rb +449 -0
  67. data/spec/integration/associations/many_to_one_spec.rb +163 -0
  68. data/spec/integration/associations/one_to_many_spec.rb +151 -0
  69. data/spec/integration/auto_migrations_spec.rb +398 -0
  70. data/spec/integration/collection_spec.rb +1069 -0
  71. data/spec/integration/data_objects_adapter_spec.rb +32 -0
  72. data/spec/integration/dependency_queue_spec.rb +58 -0
  73. data/spec/integration/model_spec.rb +127 -0
  74. data/spec/integration/mysql_adapter_spec.rb +85 -0
  75. data/spec/integration/postgres_adapter_spec.rb +731 -0
  76. data/spec/integration/property_spec.rb +233 -0
  77. data/spec/integration/query_spec.rb +506 -0
  78. data/spec/integration/repository_spec.rb +57 -0
  79. data/spec/integration/resource_spec.rb +475 -0
  80. data/spec/integration/sqlite3_adapter_spec.rb +352 -0
  81. data/spec/integration/sti_spec.rb +208 -0
  82. data/spec/integration/strategic_eager_loading_spec.rb +138 -0
  83. data/spec/integration/transaction_spec.rb +75 -0
  84. data/spec/integration/type_spec.rb +271 -0
  85. data/spec/lib/logging_helper.rb +18 -0
  86. data/spec/lib/mock_adapter.rb +27 -0
  87. data/spec/lib/model_loader.rb +91 -0
  88. data/spec/lib/publicize_methods.rb +28 -0
  89. data/spec/models/vehicles.rb +34 -0
  90. data/spec/models/zoo.rb +47 -0
  91. data/spec/spec.opts +3 -0
  92. data/spec/spec_helper.rb +86 -0
  93. data/spec/unit/adapters/abstract_adapter_spec.rb +133 -0
  94. data/spec/unit/adapters/adapter_shared_spec.rb +15 -0
  95. data/spec/unit/adapters/data_objects_adapter_spec.rb +628 -0
  96. data/spec/unit/adapters/postgres_adapter_spec.rb +133 -0
  97. data/spec/unit/associations/many_to_many_spec.rb +17 -0
  98. data/spec/unit/associations/many_to_one_spec.rb +152 -0
  99. data/spec/unit/associations/one_to_many_spec.rb +393 -0
  100. data/spec/unit/associations/one_to_one_spec.rb +7 -0
  101. data/spec/unit/associations/relationship_spec.rb +71 -0
  102. data/spec/unit/associations_spec.rb +242 -0
  103. data/spec/unit/auto_migrations_spec.rb +111 -0
  104. data/spec/unit/collection_spec.rb +182 -0
  105. data/spec/unit/data_mapper_spec.rb +35 -0
  106. data/spec/unit/identity_map_spec.rb +126 -0
  107. data/spec/unit/is_spec.rb +80 -0
  108. data/spec/unit/migrator_spec.rb +33 -0
  109. data/spec/unit/model_spec.rb +339 -0
  110. data/spec/unit/naming_conventions_spec.rb +36 -0
  111. data/spec/unit/property_set_spec.rb +83 -0
  112. data/spec/unit/property_spec.rb +753 -0
  113. data/spec/unit/query_spec.rb +530 -0
  114. data/spec/unit/repository_spec.rb +93 -0
  115. data/spec/unit/resource_spec.rb +626 -0
  116. data/spec/unit/scope_spec.rb +142 -0
  117. data/spec/unit/transaction_spec.rb +493 -0
  118. data/spec/unit/type_map_spec.rb +114 -0
  119. data/spec/unit/type_spec.rb +119 -0
  120. data/tasks/ci.rb +68 -0
  121. data/tasks/dm.rb +63 -0
  122. data/tasks/doc.rb +20 -0
  123. data/tasks/gemspec.rb +23 -0
  124. data/tasks/hoe.rb +46 -0
  125. data/tasks/install.rb +20 -0
  126. metadata +216 -0
@@ -0,0 +1,163 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'spec_helper'))
2
+
3
+ if ADAPTER
4
+ module ManyToOneSpec
5
+ class Parent
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
+ end
15
+
16
+ class Child
17
+ include DataMapper::Resource
18
+
19
+ def self.default_repository_name
20
+ ADAPTER
21
+ end
22
+
23
+ property :id, Serial
24
+ property :name, String
25
+ property :type, Discriminator
26
+
27
+ belongs_to :parent
28
+ end
29
+
30
+ class StepChild < Child
31
+ end
32
+ end
33
+
34
+ describe DataMapper::Associations::ManyToOne::Proxy do
35
+ before do
36
+ [ ManyToOneSpec::Parent, ManyToOneSpec::Child ].each { |model| model.auto_migrate! }
37
+
38
+ repository(ADAPTER) do
39
+ @parent = ManyToOneSpec::Parent.create(:name => 'parent')
40
+ @child = ManyToOneSpec::Child.create(:name => 'child', :parent => @parent)
41
+ @other = ManyToOneSpec::Parent.create(:name => 'other parent')
42
+ @step_child = ManyToOneSpec::StepChild.create(:name => 'step child', :parent => @other)
43
+ @association = @child.parent
44
+ end
45
+ end
46
+
47
+ describe "#association_accessor (STI)" do
48
+ include LoggingHelper
49
+
50
+ it "should set parent" do
51
+ ManyToOneSpec::StepChild.first(:id => @step_child.id).parent.should == @other
52
+ end
53
+
54
+ it "should use the identity map for STI" do
55
+ repository(ADAPTER) do |r|
56
+ parent = ManyToOneSpec::Parent.first(:id => @parent.id)
57
+ child = ManyToOneSpec::Child.first(:id => @child.id)
58
+ step_child = ManyToOneSpec::StepChild.first(:id => @step_child.id)
59
+ logger do |log|
60
+ # should retrieve from the IdentityMap
61
+ child.parent.object_id.should == parent.object_id
62
+
63
+ # should retrieve from the datasource
64
+ other = step_child.parent
65
+
66
+ # should retrieve from the IdentityMap
67
+ step_child.parent.should == @other
68
+ step_child.parent.object_id.should == other.object_id
69
+
70
+ log.readlines.size.should == 1
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ describe '#replace' do
77
+ it 'should remove the resource from the collection' do
78
+ @association.should == @parent
79
+ @association.replace(@other)
80
+ @association.should == @other
81
+ end
82
+
83
+ it 'should not automatically save that the resource was removed from the association' do
84
+ @association.replace(@other)
85
+ @child.reload.parent.should == @parent
86
+ end
87
+
88
+ it 'should return the association' do
89
+ @association.replace(@other).object_id.should == @association.object_id
90
+ end
91
+ end
92
+
93
+ describe '#save' do
94
+ describe 'when the parent is nil' do
95
+ before do
96
+ @association.replace(nil)
97
+ end
98
+
99
+ it 'should not save the parent' do
100
+ @association.save
101
+ end
102
+
103
+ it 'should return false' do
104
+ @association.save.should == false
105
+ end
106
+ end
107
+
108
+ describe 'when the parent is not a new record' do
109
+ before do
110
+ @parent.should_not be_new_record
111
+ @child.should_not be_new_record
112
+ end
113
+
114
+ it 'should not save the parent' do
115
+ @parent.should_not_receive(:save)
116
+ @association.save
117
+ end
118
+
119
+ it 'should return true' do
120
+ @association.save.should == true
121
+ end
122
+
123
+ it "should return true to the child" do
124
+ @child.save.should == true
125
+ end
126
+ end
127
+
128
+ describe 'when the parent is a new record' do
129
+ before do
130
+ @parent = ManyToOneSpec::Parent.new(:name => 'unsaved parent')
131
+ @parent.should be_new_record
132
+ @association.replace(@parent)
133
+ end
134
+
135
+ it 'should save the parent' do
136
+ @association.save
137
+ @parent.should_not be_new_record
138
+ end
139
+
140
+ it 'should return the result of the save' do
141
+ @association.save.should == true
142
+ end
143
+ end
144
+ end
145
+
146
+ describe '#reload' do
147
+ before do
148
+ @child.parent_id.should == @parent.id
149
+ @association.replace(@other)
150
+ end
151
+
152
+ it 'should not change the foreign key in the child' do
153
+ @child.parent_id.should == @other.id
154
+ @association.reload
155
+ @child.parent_id.should == @other.id
156
+ end
157
+
158
+ it 'should return self' do
159
+ @association.reload.object_id.should == @association.object_id
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,151 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'spec_helper'))
2
+ require 'pp'
3
+ describe "OneToMany" do
4
+ before(:all) do
5
+ class Team
6
+ include DataMapper::Resource
7
+
8
+ def self.default_repository_name; ADAPTER end
9
+
10
+ property :id, Serial
11
+ property :name, String
12
+ property :class_type, Discriminator
13
+
14
+ has n, :players
15
+ end
16
+
17
+ class BaseballTeam < Team
18
+ end
19
+
20
+ class Player
21
+ include DataMapper::Resource
22
+
23
+ def self.default_repository_name; ADAPTER end
24
+
25
+ property :id, Serial
26
+ property :name, String
27
+
28
+ belongs_to :team
29
+ end
30
+
31
+ [Team, Player].each { |k| k.auto_migrate!(ADAPTER) }
32
+
33
+ Team.create(:name => "Cowboys")
34
+ BaseballTeam.create(:name => "Giants")
35
+ end
36
+
37
+ describe "(saved parent, saved child)" do
38
+ before(:each) do
39
+ @dc_united = Team.create
40
+ @emilio = Player.create(:team => @dc_united)
41
+ end
42
+
43
+ it "child association should return parent" do
44
+ @emilio.team.should == @dc_united
45
+ end
46
+
47
+ it "parent association should return children" do
48
+ @dc_united.players.should == [@emilio]
49
+ end
50
+ end
51
+
52
+ describe "(saved parent, unsaved child)" do
53
+ before(:each) do
54
+ @dc_united = Team.create
55
+ @emilio = Player.new(:team => @dc_united)
56
+ end
57
+
58
+ it "child association should return parent" do
59
+ @emilio.team.should == @dc_united
60
+ end
61
+
62
+ it "parent association should return children" do
63
+ pending("DataMapper does not yet support in-memory associations") do
64
+ @dc_united.players.should == [@emilio]
65
+ end
66
+ end
67
+ end
68
+
69
+ describe "(unsaved parent, saved child)" do
70
+ before(:each) do
71
+ @dc_united = Team.new
72
+ @emilio = Player.create(:team => @dc_united)
73
+ end
74
+
75
+ it "child association should return parent" do
76
+ @emilio.team.should == @dc_united
77
+ end
78
+
79
+ it "parent association should return children" do
80
+ @dc_united.players.should == [@emilio]
81
+ end
82
+
83
+ it "should return true to child.save" do
84
+ @emilio.should_not be_a_new_record
85
+ @emilio.save.should be_true
86
+ end
87
+ end
88
+
89
+ describe "(unsaved parent, unsaved child)" do
90
+ before(:each) do
91
+ @dc_united = Team.new
92
+ @emilio = Player.new(:team => @dc_united)
93
+ end
94
+
95
+ it "child association should return parent" do
96
+ @emilio.team.should == @dc_united
97
+ end
98
+
99
+ it "parent association should return children" do
100
+ pending("DataMapper does not yet support in-memory associations") do
101
+ @dc_united.players.should == [@emilio]
102
+ end
103
+ end
104
+ end
105
+
106
+ it "unsaved parent model should accept array of hashes for association" do
107
+ players = [{ :name => "Brett Favre" }, { :name => "Reggie White" }]
108
+
109
+ team = Team.new(:name => "Packers", :players => players)
110
+ team.players.zip(players) do |player, values|
111
+ player.should be_an_instance_of(Player)
112
+ values.each { |k, v| player.send(k).should == v }
113
+ end
114
+
115
+ players = team.players
116
+ team.save
117
+
118
+ repository(ADAPTER) do
119
+ Team.get(3).players.should == players
120
+ end
121
+ end
122
+
123
+ it "saved parent model should accept array of hashes for association" do
124
+ players = [{ :name => "Troy Aikman" }, { :name => "Chad Hennings" }]
125
+
126
+ team = Team.get(1)
127
+ team.players = players
128
+ team.players.zip(players) do |player, values|
129
+ player.should be_an_instance_of(Player)
130
+ values.each { |k, v| player.send(k).should == v }
131
+ end
132
+
133
+ players = team.players
134
+ team.save
135
+
136
+ repository(ADAPTER) do
137
+ Team.get(1).players.should == players
138
+ end
139
+ end
140
+
141
+ describe "STI" do
142
+ it "should work" do
143
+ repository(ADAPTER) do
144
+ Player.create(:name => "Barry Bonds", :team => BaseballTeam.first)
145
+ end
146
+ repository(ADAPTER) do
147
+ Player.first.team.should == BaseballTeam.first
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,398 @@
1
+ require 'pathname'
2
+ require Pathname(__FILE__).dirname.expand_path.parent + 'spec_helper'
3
+ require 'ostruct'
4
+
5
+ TODAY = Date.today
6
+ NOW = DateTime.now
7
+
8
+ TIME_STRING_1 = '2007-04-21 04:14:12'
9
+ TIME_STRING_2 = '2007-04-21 04:14:12.1'
10
+ TIME_STRING_3 = '2007-04-21 04:14:12.01'
11
+ TIME_STRING_4 = '2007-04-21 04:14:12.123456'
12
+
13
+ TIME_1 = Time.parse(TIME_STRING_1)
14
+ TIME_2 = Time.parse(TIME_STRING_2)
15
+ TIME_3 = Time.parse(TIME_STRING_3)
16
+ TIME_4 = Time.parse(TIME_STRING_4)
17
+
18
+ class EveryType
19
+ include DataMapper::Resource
20
+
21
+ property :serial, Serial
22
+ property :fixnum, Integer, :nullable => false, :default => 1
23
+ property :string, String, :nullable => false, :default => 'default'
24
+ property :empty, String, :nullable => false, :default => ''
25
+ property :date, Date, :nullable => false, :default => TODAY, :index => :date_date_time, :unique_index => :date_float
26
+ property :true_class, TrueClass, :nullable => false, :default => true
27
+ property :false_class, TrueClass, :nullable => false, :default => false
28
+ property :text, DM::Text, :nullable => false, :default => 'text'
29
+ # property :class, Class, :nullable => false, :default => Class # FIXME: Class types cause infinite recursions in Resource
30
+ property :big_decimal, BigDecimal, :nullable => false, :default => BigDecimal('1.1'), :precision => 2, :scale => 1
31
+ property :float, Float, :nullable => false, :default => 1.1, :precision => 2, :scale => 1, :unique_index => :date_float
32
+ property :date_time, DateTime, :nullable => false, :default => NOW, :index => [:date_date_time, true]
33
+ property :time_1, Time, :nullable => false, :default => TIME_1, :unique_index => true
34
+ property :time_2, Time, :nullable => false, :default => TIME_2
35
+ property :time_3, Time, :nullable => false, :default => TIME_3
36
+ property :time_4, Time, :nullable => false, :default => TIME_4
37
+ property :object, Object, :nullable => true # FIXME: cannot supply a default for Object
38
+ property :discriminator, DM::Discriminator
39
+ end
40
+
41
+ module Publications
42
+ class ShortStoryCollection
43
+ include DataMapper::Resource
44
+ property :serial, Serial
45
+ property :date, Date, :nullable => false, :default => TODAY, :index => :date_date_time
46
+ end
47
+ end
48
+
49
+ if HAS_SQLITE3
50
+ describe DataMapper::AutoMigrations, '.auto_migrate! with sqlite3' do
51
+ before :all do
52
+ @adapter = repository(:sqlite3).adapter
53
+
54
+ DataMapper::Resource.descendants.clear
55
+
56
+ @property_class = Struct.new(:name, :type, :nullable, :default, :serial)
57
+ end
58
+
59
+ after :all do
60
+ DataMapper::Resource.descendants.clear
61
+ end
62
+
63
+ describe 'with sqlite3' do
64
+ before :all do
65
+ EveryType.auto_migrate!(:sqlite3).should be_true
66
+
67
+ @table_set = @adapter.query('PRAGMA table_info(?)', 'every_types').inject({}) do |ts,column|
68
+ default = if 'NULL' == column.dflt_value || column.dflt_value.nil?
69
+ nil
70
+ else
71
+ /^(['"]?)(.*)\1$/.match(column.dflt_value)[2]
72
+ end
73
+
74
+ property = @property_class.new(
75
+ column.name,
76
+ column.type.upcase,
77
+ column.notnull == 0,
78
+ default,
79
+ column.pk == 1 # in SQLite3 the serial key is also primary
80
+ )
81
+
82
+ ts.update(property.name => property)
83
+ end
84
+
85
+ @index_list = @adapter.query('PRAGMA index_list(?)', 'every_types')
86
+
87
+ # bypass DM to create the record using only the column default values
88
+ @adapter.execute('INSERT INTO "every_types" ("serial", "discriminator") VALUES (?, ?)', 1, EveryType)
89
+
90
+ @book = repository(:sqlite3) { EveryType.first }
91
+ end
92
+
93
+ types = {
94
+ :serial => [ Integer, 'INTEGER', false, nil, 1, true ],
95
+ :fixnum => [ Integer, 'INTEGER', false, '1', 1, false ],
96
+ :string => [ String, 'VARCHAR(50)', false, 'default', 'default', false ],
97
+ :empty => [ String, 'VARCHAR(50)', false, '', '' , false ],
98
+ :date => [ Date, 'DATE', false, TODAY.strftime('%Y-%m-%d'), TODAY, false ],
99
+ :true_class => [ TrueClass, 'BOOLEAN', false, 't', true, false ],
100
+ :false_class => [ TrueClass, 'BOOLEAN', false, 'f', false, false ],
101
+ :text => [ DM::Text, 'TEXT', false, 'text', 'text', false ],
102
+ # :class => [ Class, 'VARCHAR(50)', false, 'Class', 'Class', false ],
103
+ :big_decimal => [ BigDecimal, 'DECIMAL(2,1)', false, '1.1', BigDecimal('1.1'), false ],
104
+ :float => [ Float, 'FLOAT(2,1)', false, '1.1', 1.1, false ],
105
+ :date_time => [ DateTime, 'DATETIME', false, NOW.strftime('%Y-%m-%d %H:%M:%S'), NOW, false ],
106
+ :time_1 => [ Time, 'TIMESTAMP', false, TIME_STRING_1, TIME_1, false ],
107
+ #SQLite pads out the microseconds to the full 6 digits no matter what the value is - we simply pad up the zeros needed
108
+ :time_2 => [ Time, 'TIMESTAMP', false, TIME_STRING_2.dup << '00000', TIME_2, false ],
109
+ :time_3 => [ Time, 'TIMESTAMP', false, TIME_STRING_3.dup << '0000', TIME_3, false ],
110
+ :time_4 => [ Time, 'TIMESTAMP', false, TIME_STRING_4, TIME_4, false ],
111
+ :object => [ Object, 'TEXT', true, nil, nil, false ],
112
+ :discriminator => [ DM::Discriminator, 'VARCHAR(50)', false, nil, EveryType, false ],
113
+ }
114
+
115
+ types.each do |name,(klass,type,nullable,default,key)|
116
+ describe "a #{klass} property" do
117
+ it "should be created as a #{type}" do
118
+ @table_set[name.to_s].type.should == type
119
+ end
120
+
121
+ it "should #{!nullable && 'not'} be nullable".squeeze(' ') do
122
+ @table_set[name.to_s].nullable.should == nullable
123
+ end
124
+
125
+ it "should have a default value #{default.inspect}" do
126
+ @table_set[name.to_s].default.should == default
127
+ end
128
+
129
+ expected_value = types[name][4]
130
+ it 'should properly typecast value' do
131
+ if DateTime == klass
132
+ @book.send(name).to_s.should == expected_value.to_s
133
+ else
134
+ @book.send(name).should == expected_value
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ it 'should have 4 indexes: 2 non-unique index, 2 unique index' do
141
+ @index_list.size.should == 4
142
+
143
+ expected_indices = {
144
+ "unique_index_every_types_date_float" => 1,
145
+ "unique_index_every_types_time_1" => 1,
146
+ "index_every_types_date_date_time" => 0,
147
+ "index_every_types_date_time" => 0
148
+ }
149
+
150
+ @index_list.each do |index|
151
+ expected_indices.should have_key(index.name)
152
+ expected_indices[index.name].should == index.unique
153
+ end
154
+ end
155
+
156
+ it 'should escape a namespaced model' do
157
+ Publications::ShortStoryCollection.auto_migrate!(:sqlite3).should be_true
158
+ @adapter.query('SELECT "name" FROM "sqlite_master" WHERE type = ?', 'table').should include('publications_short_story_collections')
159
+ end
160
+ end
161
+ end
162
+ end
163
+
164
+ if HAS_MYSQL
165
+ describe DataMapper::AutoMigrations, '.auto_migrate! with mysql' do
166
+ before :all do
167
+ @adapter = repository(:mysql).adapter
168
+
169
+ DataMapper::Resource.descendants.clear
170
+
171
+ @property_class = Struct.new(:name, :type, :nullable, :default, :serial)
172
+ end
173
+
174
+ after :all do
175
+ DataMapper::Resource.descendants.clear
176
+ end
177
+
178
+ describe 'with mysql' do#
179
+ before :all do
180
+ EveryType.auto_migrate!(:mysql).should be_true
181
+
182
+ @table_set = @adapter.query('DESCRIBE `every_types`').inject({}) do |ts,column|
183
+ property = @property_class.new(
184
+ column.field,
185
+ column.type.upcase,
186
+ column.null == 'YES',
187
+ column.type.upcase == 'TEXT' ? nil : column.default,
188
+ column.extra.split.include?('auto_increment')
189
+ )
190
+
191
+ ts.update(property.name => property)
192
+ end
193
+
194
+ @index_list = @adapter.query('SHOW INDEX FROM `every_types`')
195
+
196
+ # bypass DM to create the record using only the column default values
197
+ @adapter.execute('INSERT INTO `every_types` (`serial`, `text`, `discriminator`) VALUES (?, ?, ?)', 1, 'text', EveryType)
198
+
199
+ @book = repository(:mysql) { EveryType.first }
200
+ end
201
+
202
+ types = {
203
+ :serial => [ Integer, 'INT(11)', false, nil, 1, true ],
204
+ :fixnum => [ Integer, 'INT(11)', false, '1', 1, false ],
205
+ :string => [ String, 'VARCHAR(50)', false, 'default', 'default', false ],
206
+ :empty => [ String, 'VARCHAR(50)', false, '', '', false ],
207
+ :date => [ Date, 'DATE', false, TODAY.strftime('%Y-%m-%d'), TODAY, false ],
208
+ :true_class => [ TrueClass, 'TINYINT(1)', false, '1', true, false ],
209
+ :false_class => [ TrueClass, 'TINYINT(1)', false, '0', false, false ],
210
+ :text => [ DM::Text, 'TEXT', false, nil, 'text', false ],
211
+ # :class => [ Class, 'VARCHAR(50)', false, 'Class', 'Class', false ],
212
+ :big_decimal => [ BigDecimal, 'DECIMAL(2,1)', false, '1.1', BigDecimal('1.1'), false ],
213
+ :float => [ Float, 'FLOAT(2,1)', false, '1.1', 1.1, false ],
214
+ :date_time => [ DateTime, 'DATETIME', false, NOW.strftime('%Y-%m-%d %H:%M:%S'), NOW, false ],
215
+ :time_1 => [ Time, 'TIMESTAMP', false, TIME_1.strftime('%Y-%m-%d %H:%M:%S'), TIME_1, false ],
216
+ :time_2 => [ Time, 'TIMESTAMP', false, TIME_2.strftime('%Y-%m-%d %H:%M:%S'), TIME_2, false ],
217
+ :time_3 => [ Time, 'TIMESTAMP', false, TIME_3.strftime('%Y-%m-%d %H:%M:%S'), TIME_3 , false ],
218
+ :time_4 => [ Time, 'TIMESTAMP', false, TIME_4.strftime('%Y-%m-%d %H:%M:%S'), TIME_4 , false ],
219
+ :object => [ Object, 'TEXT', true, nil, nil, false ],
220
+ :discriminator => [ DM::Discriminator, 'VARCHAR(50)', false, nil, EveryType, false ],
221
+ }
222
+
223
+ types.each do |name,(klass,type,nullable,default,key)|
224
+ describe "a #{klass} property" do
225
+ it "should be created as a #{type}" do
226
+ @table_set[name.to_s].type.should == type
227
+ end
228
+
229
+ it "should #{!nullable && 'not'} be nullable".squeeze(' ') do
230
+ @table_set[name.to_s].nullable.should == nullable
231
+ end
232
+
233
+ it "should have a default value #{default.inspect}" do
234
+ @table_set[name.to_s].default.should == default
235
+ end
236
+
237
+ expected_value = types[name][4]
238
+ it 'should properly typecast value' do
239
+ if DateTime == klass || Time == klass # mysql doesn't support microsecond
240
+ @book.send(name).to_s.should == expected_value.to_s
241
+ else
242
+ @book.send(name).should == expected_value
243
+ end
244
+ end
245
+ end
246
+ end
247
+
248
+ it 'should have 4 indexes: 2 non-unique index, 2 unique index' do
249
+ pending do
250
+ # TODO
251
+ @index_list[0].Key_name.should == 'unique_index_every_types_date_float'
252
+ @index_list[0].Non_unique.should == 0
253
+ @index_list[1].Key_name.should == 'unique_index_every_types_time_1'
254
+ @index_list[1].Non_unique.should == 0
255
+ @index_list[2].Key_name.should == 'index_every_types_date_date_time'
256
+ @index_list[2].Non_unique.should == 1
257
+ @index_list[3].Key_name.should == 'index_every_types_date_time'
258
+ @index_list[3].Non_unique.should == 1
259
+ end
260
+ end
261
+
262
+ it 'should escape a namespaced model' do
263
+ Publications::ShortStoryCollection.auto_migrate!(:mysql).should be_true
264
+ @adapter.query('SHOW TABLES').should include('publications_short_story_collections')
265
+ end
266
+ end
267
+ end
268
+ end
269
+
270
+ if HAS_POSTGRES
271
+ describe DataMapper::AutoMigrations, '.auto_migrate! with postgres' do
272
+ before :all do
273
+ @adapter = repository(:postgres).adapter
274
+
275
+ DataMapper::Resource.descendants.clear
276
+
277
+ @property_class = Struct.new(:name, :type, :nullable, :default, :serial)
278
+ end
279
+
280
+ after :all do
281
+ DataMapper::Resource.descendants.clear
282
+ end
283
+
284
+ describe 'with postgres' do
285
+ before :all do
286
+ EveryType.auto_migrate!(:postgres).should be_true
287
+
288
+ query = <<-EOS
289
+ SELECT
290
+ -- Field
291
+ "pg_attribute"."attname" AS "Field",
292
+ -- Type
293
+ CASE "pg_type"."typname"
294
+ WHEN 'varchar' THEN 'varchar'
295
+ ELSE "pg_type"."typname"
296
+ END AS "Type",
297
+ -- Null
298
+ CASE WHEN "pg_attribute"."attnotnull" THEN ''
299
+ ELSE 'YES'
300
+ END AS "Null",
301
+ -- Default
302
+ "pg_attrdef"."adsrc" AS "Default"
303
+ FROM "pg_class"
304
+ INNER JOIN "pg_attribute"
305
+ ON ("pg_class"."oid" = "pg_attribute"."attrelid")
306
+ INNER JOIN pg_type
307
+ ON ("pg_attribute"."atttypid" = "pg_type"."oid")
308
+ LEFT JOIN "pg_attrdef"
309
+ ON ("pg_class"."oid" = "pg_attrdef"."adrelid" AND "pg_attribute"."attnum" = "pg_attrdef"."adnum")
310
+ WHERE "pg_class"."relname" = ? AND "pg_attribute"."attnum" >= ? AND NOT "pg_attribute"."attisdropped"
311
+ ORDER BY "pg_attribute"."attnum"
312
+ EOS
313
+
314
+ @table_set = @adapter.query(query, 'every_types', 1).inject({}) do |ts,column|
315
+ default = column.default
316
+ serial = false
317
+
318
+ if column.default == "nextval('every_types_serial_seq'::regclass)"
319
+ default = nil
320
+ serial = true
321
+ end
322
+
323
+ property = @property_class.new(
324
+ column.field,
325
+ column.type.upcase,
326
+ column.null == 'YES',
327
+ default,
328
+ serial
329
+ )
330
+
331
+ ts.update(property.name => property)
332
+ end
333
+
334
+ # bypass DM to create the record using only the column default values
335
+ @adapter.execute('INSERT INTO "every_types" ("serial", "discriminator") VALUES (?, ?)', 1, EveryType)
336
+
337
+ @book = repository(:postgres) { EveryType.first }
338
+ end
339
+
340
+ types = {
341
+ :serial => [ Integer, 'INT4', false, nil, 1, true ],
342
+ :fixnum => [ Integer, 'INT4', false, '1', 1, false ],
343
+ :string => [ String, 'VARCHAR', false, "'default'::character varying", 'default', false ],
344
+ :empty => [ String, 'VARCHAR', false, "''::character varying", '', false ],
345
+ :date => [ Date, 'DATE', false, "'#{TODAY.strftime('%Y-%m-%d')}'::date", TODAY, false ],
346
+ :true_class => [ TrueClass, 'BOOL', false, 'true', true, false ],
347
+ :false_class => [ TrueClass, 'BOOL', false, 'false', false, false ],
348
+ :text => [ DM::Text, 'TEXT', false, "'text'::text", 'text', false ],
349
+ # :class => [ Class, 'VARCHAR(50)', false, 'Class', 'Class', false ],
350
+ :big_decimal => [ BigDecimal, 'NUMERIC', false, '1.1', BigDecimal('1.1'), false ],
351
+ :float => [ Float, 'FLOAT8', false, '1.1', 1.1, false ],
352
+ :date_time => [ DateTime, 'TIMESTAMP', false, "'#{NOW.strftime('%Y-%m-%d %H:%M:%S')}'::timestamp without time zone", NOW, false ],
353
+ :time_1 => [ Time, 'TIMESTAMP', false, "'" << TIME_STRING_1.dup << "'::timestamp without time zone", TIME_1, false ],
354
+ #The weird zero here is simply because postgresql seems to want to store .10 instead of .1 for this one
355
+ #affects anything with an exact tenth of a second (i.e. .1, .2, .3, ...)
356
+ :time_2 => [ Time, 'TIMESTAMP', false, "'" << TIME_STRING_2.dup << "0'::timestamp without time zone", TIME_2, false ],
357
+ :time_3 => [ Time, 'TIMESTAMP', false, "'" << TIME_STRING_3.dup << "'::timestamp without time zone", TIME_3, false ],
358
+ :time_4 => [ Time, 'TIMESTAMP', false, "'" << TIME_STRING_4.dup << "'::timestamp without time zone", TIME_4, false ],
359
+ :object => [ Object, 'TEXT', true, nil, nil, false ],
360
+ :discriminator => [ DM::Discriminator, 'VARCHAR', false, nil, EveryType, false ],
361
+ }
362
+
363
+ types.each do |name,(klass,type,nullable,default,key)|
364
+ describe "a #{Extlib::Inflection.classify(name.to_s)} property" do
365
+ it "should be created as a #{type}" do
366
+ @table_set[name.to_s].type.should == type
367
+ end
368
+
369
+ it "should #{!nullable && 'not'} be nullable".squeeze(' ') do
370
+ @table_set[name.to_s].nullable.should == nullable
371
+ end
372
+
373
+ it "should have a default value #{default.inspect}" do
374
+ @table_set[name.to_s].default.should == default
375
+ end
376
+
377
+ expected_value = types[name][4]
378
+ it 'should properly typecast value' do
379
+ if DateTime == klass
380
+ @book.send(name).to_s.should == expected_value.to_s
381
+ else
382
+ @book.send(name).should == expected_value
383
+ end
384
+ end
385
+ end
386
+ end
387
+
388
+ it 'should have 4 indexes: 2 non-unique index, 2 unique index' do
389
+ pending 'TODO'
390
+ end
391
+
392
+ it 'should escape a namespaced model' do
393
+ Publications::ShortStoryCollection.auto_migrate!(:postgres).should be_true
394
+ @adapter.query('SELECT "tablename" FROM "pg_tables" WHERE "tablename" NOT LIKE ?', 'pg_%').should include('publications_short_story_collections')
395
+ end
396
+ end
397
+ end
398
+ end