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