dm-core 0.9.2

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 (101) hide show
  1. data/CHANGELOG +144 -0
  2. data/FAQ +74 -0
  3. data/MIT-LICENSE +22 -0
  4. data/QUICKLINKS +12 -0
  5. data/README +143 -0
  6. data/lib/dm-core.rb +213 -0
  7. data/lib/dm-core/adapters.rb +4 -0
  8. data/lib/dm-core/adapters/abstract_adapter.rb +202 -0
  9. data/lib/dm-core/adapters/data_objects_adapter.rb +701 -0
  10. data/lib/dm-core/adapters/mysql_adapter.rb +132 -0
  11. data/lib/dm-core/adapters/postgres_adapter.rb +179 -0
  12. data/lib/dm-core/adapters/sqlite3_adapter.rb +105 -0
  13. data/lib/dm-core/associations.rb +172 -0
  14. data/lib/dm-core/associations/many_to_many.rb +138 -0
  15. data/lib/dm-core/associations/many_to_one.rb +101 -0
  16. data/lib/dm-core/associations/one_to_many.rb +275 -0
  17. data/lib/dm-core/associations/one_to_one.rb +61 -0
  18. data/lib/dm-core/associations/relationship.rb +116 -0
  19. data/lib/dm-core/associations/relationship_chain.rb +74 -0
  20. data/lib/dm-core/auto_migrations.rb +64 -0
  21. data/lib/dm-core/collection.rb +604 -0
  22. data/lib/dm-core/hook.rb +11 -0
  23. data/lib/dm-core/identity_map.rb +45 -0
  24. data/lib/dm-core/is.rb +16 -0
  25. data/lib/dm-core/logger.rb +233 -0
  26. data/lib/dm-core/migrations/destructive_migrations.rb +17 -0
  27. data/lib/dm-core/migrator.rb +29 -0
  28. data/lib/dm-core/model.rb +399 -0
  29. data/lib/dm-core/naming_conventions.rb +52 -0
  30. data/lib/dm-core/property.rb +611 -0
  31. data/lib/dm-core/property_set.rb +158 -0
  32. data/lib/dm-core/query.rb +590 -0
  33. data/lib/dm-core/repository.rb +159 -0
  34. data/lib/dm-core/resource.rb +618 -0
  35. data/lib/dm-core/scope.rb +35 -0
  36. data/lib/dm-core/support.rb +7 -0
  37. data/lib/dm-core/support/array.rb +13 -0
  38. data/lib/dm-core/support/assertions.rb +8 -0
  39. data/lib/dm-core/support/errors.rb +23 -0
  40. data/lib/dm-core/support/kernel.rb +7 -0
  41. data/lib/dm-core/support/symbol.rb +41 -0
  42. data/lib/dm-core/transaction.rb +267 -0
  43. data/lib/dm-core/type.rb +160 -0
  44. data/lib/dm-core/type_map.rb +80 -0
  45. data/lib/dm-core/types.rb +19 -0
  46. data/lib/dm-core/types/boolean.rb +7 -0
  47. data/lib/dm-core/types/discriminator.rb +32 -0
  48. data/lib/dm-core/types/object.rb +20 -0
  49. data/lib/dm-core/types/paranoid_boolean.rb +23 -0
  50. data/lib/dm-core/types/paranoid_datetime.rb +22 -0
  51. data/lib/dm-core/types/serial.rb +9 -0
  52. data/lib/dm-core/types/text.rb +10 -0
  53. data/spec/integration/association_spec.rb +1215 -0
  54. data/spec/integration/association_through_spec.rb +150 -0
  55. data/spec/integration/associations/many_to_many_spec.rb +171 -0
  56. data/spec/integration/associations/many_to_one_spec.rb +123 -0
  57. data/spec/integration/associations/one_to_many_spec.rb +66 -0
  58. data/spec/integration/auto_migrations_spec.rb +398 -0
  59. data/spec/integration/collection_spec.rb +1015 -0
  60. data/spec/integration/data_objects_adapter_spec.rb +32 -0
  61. data/spec/integration/model_spec.rb +68 -0
  62. data/spec/integration/mysql_adapter_spec.rb +85 -0
  63. data/spec/integration/postgres_adapter_spec.rb +732 -0
  64. data/spec/integration/property_spec.rb +224 -0
  65. data/spec/integration/query_spec.rb +376 -0
  66. data/spec/integration/repository_spec.rb +57 -0
  67. data/spec/integration/resource_spec.rb +324 -0
  68. data/spec/integration/sqlite3_adapter_spec.rb +352 -0
  69. data/spec/integration/sti_spec.rb +185 -0
  70. data/spec/integration/transaction_spec.rb +75 -0
  71. data/spec/integration/type_spec.rb +149 -0
  72. data/spec/lib/mock_adapter.rb +27 -0
  73. data/spec/spec_helper.rb +112 -0
  74. data/spec/unit/adapters/abstract_adapter_spec.rb +133 -0
  75. data/spec/unit/adapters/adapter_shared_spec.rb +15 -0
  76. data/spec/unit/adapters/data_objects_adapter_spec.rb +627 -0
  77. data/spec/unit/adapters/postgres_adapter_spec.rb +125 -0
  78. data/spec/unit/associations/many_to_many_spec.rb +14 -0
  79. data/spec/unit/associations/many_to_one_spec.rb +138 -0
  80. data/spec/unit/associations/one_to_many_spec.rb +385 -0
  81. data/spec/unit/associations/one_to_one_spec.rb +7 -0
  82. data/spec/unit/associations/relationship_spec.rb +67 -0
  83. data/spec/unit/associations_spec.rb +205 -0
  84. data/spec/unit/auto_migrations_spec.rb +110 -0
  85. data/spec/unit/collection_spec.rb +174 -0
  86. data/spec/unit/data_mapper_spec.rb +21 -0
  87. data/spec/unit/identity_map_spec.rb +126 -0
  88. data/spec/unit/is_spec.rb +80 -0
  89. data/spec/unit/migrator_spec.rb +33 -0
  90. data/spec/unit/model_spec.rb +339 -0
  91. data/spec/unit/naming_conventions_spec.rb +28 -0
  92. data/spec/unit/property_set_spec.rb +96 -0
  93. data/spec/unit/property_spec.rb +447 -0
  94. data/spec/unit/query_spec.rb +485 -0
  95. data/spec/unit/repository_spec.rb +93 -0
  96. data/spec/unit/resource_spec.rb +557 -0
  97. data/spec/unit/scope_spec.rb +131 -0
  98. data/spec/unit/transaction_spec.rb +493 -0
  99. data/spec/unit/type_map_spec.rb +114 -0
  100. data/spec/unit/type_spec.rb +119 -0
  101. metadata +187 -0
@@ -0,0 +1,185 @@
1
+ require 'pathname'
2
+ require Pathname(__FILE__).dirname.expand_path.parent + 'spec_helper'
3
+
4
+ if HAS_SQLITE3
5
+ describe DataMapper::AutoMigrations, '.auto_migrate! on STI models with sqlite3' do
6
+ before :all do
7
+ @adapter = repository(:sqlite3).adapter
8
+
9
+ @property_class = Struct.new(:name, :type, :nullable, :default, :serial)
10
+
11
+ class Book
12
+ include DataMapper::Resource
13
+
14
+ property :id, Serial
15
+ property :title, String, :nullable => false
16
+ property :isbn, Integer, :nullable => false
17
+ property :class_type, Discriminator
18
+ end
19
+
20
+ class Propaganda < Book
21
+ property :marxist, Boolean, :nullable => false, :default => false
22
+ end
23
+
24
+ class Fiction < Book
25
+ property :series, String
26
+ end
27
+
28
+ class ShortStory < Fiction
29
+ property :moral, String
30
+ end
31
+
32
+ class ScienceFiction < Fiction
33
+ property :aliens, Boolean
34
+ end
35
+
36
+ class SpaceWestern < ScienceFiction
37
+ property :cowboys, Boolean
38
+ end
39
+ end
40
+
41
+ describe "with a parent class" do
42
+ before :all do
43
+ Book.auto_migrate!(:sqlite3).should be_true
44
+
45
+ @table_set = @adapter.query('PRAGMA table_info("books")').inject({}) do |ts,column|
46
+ default = if 'NULL' == column.dflt_value || column.dflt_value.nil?
47
+ nil
48
+ else
49
+ /^(['"]?)(.*)\1$/.match(column.dflt_value)[2]
50
+ end
51
+
52
+ property = @property_class.new(
53
+ column.name,
54
+ column.type.upcase,
55
+ column.notnull == 0,
56
+ default,
57
+ column.pk == 1 # in SQLite3 the serial key is also primary
58
+ )
59
+
60
+ ts.update(property.name => property)
61
+ end
62
+
63
+ @index_list = @adapter.query('PRAGMA index_list("books")')
64
+ end
65
+
66
+ it "should create the child class property columns" do
67
+ @table_set.keys.should include("series", "marxist")
68
+ end
69
+
70
+ it "should create all property columns of the child classes in the inheritance tree" do
71
+ @table_set.keys.should include("moral")
72
+ end
73
+ end
74
+
75
+ describe "with a child class" do
76
+ before :all do
77
+ Propaganda.auto_migrate!(:sqlite3).should be_true
78
+
79
+ @table_set = @adapter.query('PRAGMA table_info("books")').inject({}) do |ts,column|
80
+ default = if 'NULL' == column.dflt_value || column.dflt_value.nil?
81
+ nil
82
+ else
83
+ /^(['"]?)(.*)\1$/.match(column.dflt_value)[2]
84
+ end
85
+
86
+ property = @property_class.new(
87
+ column.name,
88
+ column.type.upcase,
89
+ column.notnull == 0,
90
+ default,
91
+ column.pk == 1 # in SQLite3 the serial key is also primary
92
+ )
93
+
94
+ ts.update(property.name => property)
95
+ end
96
+
97
+ @index_list = @adapter.query('PRAGMA index_list("books")')
98
+ end
99
+
100
+ it "should create the parent class' property columns" do
101
+ @table_set.keys.should include("id", "title", "isbn")
102
+ end
103
+ end
104
+
105
+ describe "with a child class with it's own child class" do
106
+ before :all do
107
+ Fiction.auto_migrate!(:sqlite3).should be_true
108
+
109
+ @table_set = @adapter.query('PRAGMA table_info("books")').inject({}) do |ts,column|
110
+ default = if 'NULL' == column.dflt_value || column.dflt_value.nil?
111
+ nil
112
+ else
113
+ /^(['"]?)(.*)\1$/.match(column.dflt_value)[2]
114
+ end
115
+
116
+ property = @property_class.new(
117
+ column.name,
118
+ column.type.upcase,
119
+ column.notnull == 0,
120
+ default,
121
+ column.pk == 1 # in SQLite3 the serial key is also primary
122
+ )
123
+
124
+ ts.update(property.name => property)
125
+ end
126
+
127
+ @index_list = @adapter.query('PRAGMA index_list("books")')
128
+ end
129
+
130
+ it "should create the parent class' property columns" do
131
+ @table_set.keys.should include("id", "title", "isbn")
132
+ end
133
+
134
+ it "should create the child class' property columns" do
135
+ @table_set.keys.should include("moral")
136
+ end
137
+ end
138
+
139
+ describe "with a nephew class" do
140
+ before :all do
141
+ ShortStory.auto_migrate!(:sqlite3).should be_true
142
+
143
+ @table_set = @adapter.query('PRAGMA table_info("books")').inject({}) do |ts,column|
144
+ default = if 'NULL' == column.dflt_value || column.dflt_value.nil?
145
+ nil
146
+ else
147
+ /^(['"]?)(.*)\1$/.match(column.dflt_value)[2]
148
+ end
149
+
150
+ property = @property_class.new(
151
+ column.name,
152
+ column.type.upcase,
153
+ column.notnull == 0,
154
+ default,
155
+ column.pk == 1 # in SQLite3 the serial key is also primary
156
+ )
157
+
158
+ ts.update(property.name => property)
159
+ end
160
+ @index_list = @adapter.query('PRAGMA index_list("books")')
161
+ end
162
+
163
+
164
+ it "should create the grandparent class' property columns" do
165
+ @table_set.keys.should include("id", "title", "isbn")
166
+ end
167
+
168
+ it "should create the uncle class' property columns" do
169
+ @table_set.keys.should include("marxist")
170
+ end
171
+ end
172
+
173
+ describe "with a great-grandchild class" do
174
+ it "should inherit its parent's properties" do
175
+ SpaceWestern.properties[:aliens].should_not be_nil
176
+ end
177
+ it "should inherit its grandparent's properties" do
178
+ SpaceWestern.properties[:series].should_not be_nil
179
+ end
180
+ it "should inherit its great-granparent's properties" do
181
+ SpaceWestern.properties[:title].should_not be_nil
182
+ end
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,75 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+
3
+ # transaction capable adapters
4
+ ADAPTERS = []
5
+ ADAPTERS << :postgres if HAS_POSTGRES
6
+ ADAPTERS << :mysql if HAS_MYSQL
7
+ ADAPTERS << :sqlite3 if HAS_SQLITE3
8
+
9
+ if ADAPTERS.any?
10
+ class Sputnik
11
+ include DataMapper::Resource
12
+
13
+ property :id, Serial
14
+ property :name, DM::Text
15
+ end
16
+
17
+ describe DataMapper::Transaction do
18
+ before :all do
19
+ @repositories = []
20
+
21
+ ADAPTERS.each do |name|
22
+ @repositories << repository(name)
23
+ end
24
+ end
25
+
26
+ before :each do
27
+ ADAPTERS.each do |name|
28
+ Sputnik.auto_migrate!(name)
29
+ end
30
+ end
31
+
32
+ it "should commit changes to all involved adapters on a two phase commit" do
33
+ DataMapper::Transaction.new(*@repositories) do
34
+ ADAPTERS.each do |name|
35
+ repository(name) { Sputnik.create!(:name => 'hepp') }
36
+ end
37
+ end
38
+
39
+ ADAPTERS.each do |name|
40
+ repository(name) { Sputnik.all.size.should == 1 }
41
+ end
42
+ end
43
+
44
+ it "should not commit any changes if the block raises an exception" do
45
+ lambda do
46
+ DataMapper::Transaction.new(*@repositories) do
47
+ ADAPTERS.each do |name|
48
+ repository(name) { Sputnik.create!(:name => 'hepp') }
49
+ end
50
+ raise "plur"
51
+ end
52
+ end.should raise_error(Exception, /plur/)
53
+
54
+ ADAPTERS.each do |name|
55
+ repository(name) { Sputnik.all.size.should == 0 }
56
+ end
57
+ end
58
+
59
+ it "should not commit any changes if any of the adapters doesnt prepare properly" do
60
+ lambda do
61
+ DataMapper::Transaction.new(*@repositories) do |transaction|
62
+ ADAPTERS.each do |name|
63
+ repository(name) { Sputnik.create!(:name => 'hepp') }
64
+ end
65
+
66
+ transaction.primitive_for(@repositories.last.adapter).should_receive(:prepare).and_throw(Exception.new("I am the famous test exception"))
67
+ end
68
+ end.should raise_error(Exception, /I am the famous test exception/)
69
+
70
+ ADAPTERS.each do |name|
71
+ repository(name) { Sputnik.all.size.should == 0 }
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,149 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+
3
+ gem 'fastercsv', '>=1.2.3'
4
+ require 'fastercsv'
5
+
6
+ if ADAPTER
7
+ module TypeTests
8
+ class Impostor < DataMapper::Type
9
+ primitive String
10
+ end
11
+
12
+ class Coconut
13
+ include DataMapper::Resource
14
+
15
+ storage_names[ADAPTER] = 'coconuts'
16
+
17
+ def self.default_repository_name
18
+ ADAPTER
19
+ end
20
+
21
+ property :id, Serial
22
+ property :faked, Impostor
23
+ property :active, Boolean
24
+ property :note, Text
25
+ end
26
+ end
27
+
28
+ class Lemon
29
+ include DataMapper::Resource
30
+
31
+ def self.default_repository_name
32
+ ADAPTER
33
+ end
34
+
35
+ property :id, Serial
36
+ property :color, String
37
+ property :deleted_at, DataMapper::Types::ParanoidDateTime
38
+ end
39
+
40
+ class Lime
41
+ include DataMapper::Resource
42
+
43
+ def self.default_repository_name
44
+ ADAPTER
45
+ end
46
+
47
+ property :id, Serial
48
+ property :color, String
49
+ property :deleted_at, DataMapper::Types::ParanoidBoolean
50
+ end
51
+
52
+ describe DataMapper::Type, "with #{ADAPTER}" do
53
+ before do
54
+ TypeTests::Coconut.auto_migrate!(ADAPTER)
55
+
56
+ @document = <<-EOS.margin
57
+ NAME, RATING, CONVENIENCE
58
+ Freebird's, 3, 3
59
+ Whataburger, 1, 5
60
+ Jimmy John's, 3, 4
61
+ Mignon, 5, 2
62
+ Fuzi Yao's, 5, 1
63
+ Blue Goose, 5, 1
64
+ EOS
65
+
66
+ @stuff = YAML::dump({ 'Happy Cow!' => true, 'Sad Cow!' => false })
67
+
68
+ @active = true
69
+ @note = "This is a note on our ol' guy bob"
70
+ end
71
+
72
+ it "should instantiate an object with custom types" do
73
+ coconut = TypeTests::Coconut.new(:faked => 'bob', :active => @active, :note => @note)
74
+ coconut.faked.should == 'bob'
75
+ coconut.active.should be_a_kind_of(TrueClass)
76
+ coconut.note.should be_a_kind_of(String)
77
+ end
78
+
79
+ it "should CRUD an object with custom types" do
80
+ repository(ADAPTER) do
81
+ coconut = TypeTests::Coconut.new(:faked => 'bob', :active => @active, :note => @note)
82
+ coconut.save.should be_true
83
+ coconut.id.should_not be_nil
84
+
85
+ fred = TypeTests::Coconut.get!(coconut.id)
86
+ fred.faked.should == 'bob'
87
+ fred.active.should be_a_kind_of(TrueClass)
88
+ fred.note.should be_a_kind_of(String)
89
+
90
+ note = "Seems like bob is just mockin' around"
91
+ fred.note = note
92
+
93
+ fred.save.should be_true
94
+
95
+ active = false
96
+ fred.active = active
97
+
98
+ fred.save.should be_true
99
+
100
+ # Can't call coconut.reload since coconut.collection isn't setup.
101
+ mac = TypeTests::Coconut.get!(fred.id)
102
+ mac.active.should == active
103
+ mac.note.should == note
104
+ end
105
+ end
106
+
107
+ it "should respect paranoia with a datetime" do
108
+ Lemon.auto_migrate!(ADAPTER)
109
+
110
+ lemon = nil
111
+
112
+ repository(ADAPTER) do |repository|
113
+ lemon = Lemon.new
114
+ lemon.color = 'green'
115
+
116
+ lemon.save
117
+ lemon.destroy
118
+
119
+ lemon.deleted_at.should be_kind_of(DateTime)
120
+ end
121
+
122
+ repository(ADAPTER) do |repository|
123
+ Lemon.all.should be_empty
124
+ Lemon.get(lemon.id).should be_nil
125
+ end
126
+ end
127
+
128
+ it "should respect paranoia with a boolean" do
129
+ Lime.auto_migrate!(ADAPTER)
130
+
131
+ lime = nil
132
+
133
+ repository(ADAPTER) do |repository|
134
+ lime = Lime.new
135
+ lime.color = 'green'
136
+
137
+ lime.save
138
+ lime.destroy
139
+
140
+ lime.deleted_at.should be_kind_of(TrueClass)
141
+ end
142
+
143
+ repository(ADAPTER) do |repository|
144
+ Lime.all.should be_empty
145
+ Lime.get(lime.id).should be_nil
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,27 @@
1
+ module DataMapper
2
+ module Adapters
3
+ class MockAdapter < DataMapper::Adapters::DataObjectsAdapter
4
+
5
+ def create(resources)
6
+ 1
7
+ end
8
+
9
+ def exists?(storage_name)
10
+ true
11
+ end
12
+
13
+ end
14
+ end
15
+ end
16
+
17
+ module DataObjects
18
+ module Mock
19
+
20
+ def self.logger
21
+ end
22
+
23
+ def self.logger=(value)
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,112 @@
1
+ require 'rubygems'
2
+ gem 'rspec', '>=1.1.3'
3
+ require 'spec'
4
+ require 'pathname'
5
+
6
+ require Pathname(__FILE__).dirname.expand_path.parent + 'lib/dm-core'
7
+ require DataMapper.root / 'spec' / 'lib' / 'mock_adapter'
8
+
9
+ # setup mock adapters
10
+ [ :default, :mock, :legacy, :west_coast, :east_coast ].each do |repository_name|
11
+ DataMapper.setup(repository_name, "mock://localhost/#{repository_name}")
12
+ end
13
+
14
+ def setup_adapter(name, default_uri)
15
+ begin
16
+ DataMapper.setup(name, ENV["#{name.to_s.upcase}_SPEC_URI"] || default_uri)
17
+ Object.const_set('ADAPTER', ENV['ADAPTER'].to_sym) if name.to_s == ENV['ADAPTER']
18
+ true
19
+ rescue Exception => e
20
+ if name.to_s == ENV['ADAPTER']
21
+ Object.const_set('ADAPTER', nil)
22
+ warn "Could not load #{name} adapter: #{e}"
23
+ end
24
+ false
25
+ end
26
+ end
27
+
28
+ ENV['ADAPTER'] ||= 'sqlite3'
29
+
30
+ HAS_SQLITE3 = setup_adapter(:sqlite3, 'sqlite3::memory:')
31
+ HAS_MYSQL = setup_adapter(:mysql, 'mysql://localhost/dm_core_test')
32
+ HAS_POSTGRES = setup_adapter(:postgres, 'postgres://postgres@localhost/dm_core_test')
33
+
34
+ DataMapper::Logger.new(nil, :debug)
35
+
36
+ class Article
37
+ include DataMapper::Resource
38
+
39
+ property :id, Serial
40
+ property :blog_id, Integer
41
+ property :created_at, DateTime
42
+ property :author, String
43
+ property :title, String
44
+ end
45
+
46
+ class Comment
47
+ include DataMapper::Resource
48
+ end
49
+
50
+ class NormalClass
51
+ # should not include DataMapper::Resource
52
+ end
53
+
54
+ # ==========================
55
+ # Used for Association specs
56
+ class Vehicle
57
+ include DataMapper::Resource
58
+
59
+ property :id, Serial
60
+ property :name, String
61
+
62
+ class << self
63
+ attr_accessor :mock_relationship
64
+ end
65
+ end
66
+
67
+ class Manufacturer
68
+ include DataMapper::Resource
69
+
70
+ property :id, Serial
71
+ property :name, String
72
+
73
+ class << self
74
+ attr_accessor :mock_relationship
75
+ end
76
+ end
77
+
78
+ class Supplier
79
+ include DataMapper::Resource
80
+
81
+ property :id, Serial
82
+ property :name, String
83
+ end
84
+
85
+ class Class
86
+ def publicize_methods
87
+ klass = class << self; self; end
88
+
89
+ saved_private_class_methods = klass.private_instance_methods
90
+ saved_protected_class_methods = klass.protected_instance_methods
91
+ saved_private_instance_methods = self.private_instance_methods
92
+ saved_protected_instance_methods = self.protected_instance_methods
93
+
94
+ self.class_eval do
95
+ klass.send(:public, *saved_private_class_methods)
96
+ klass.send(:public, *saved_protected_class_methods)
97
+ public(*saved_private_instance_methods)
98
+ public(*saved_protected_instance_methods)
99
+ end
100
+
101
+ begin
102
+ yield
103
+ ensure
104
+ self.class_eval do
105
+ klass.send(:private, *saved_private_class_methods)
106
+ klass.send(:protected, *saved_protected_class_methods)
107
+ private(*saved_private_instance_methods)
108
+ protected(*saved_protected_instance_methods)
109
+ end
110
+ end
111
+ end
112
+ end