dm-core 0.9.2 → 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. data/.autotest +26 -0
  2. data/{CHANGELOG → History.txt} +78 -77
  3. data/Manifest.txt +123 -0
  4. data/{README → README.txt} +0 -0
  5. data/Rakefile +29 -0
  6. data/SPECS +63 -0
  7. data/TODO +1 -0
  8. data/lib/dm-core.rb +6 -1
  9. data/lib/dm-core/adapters/data_objects_adapter.rb +29 -32
  10. data/lib/dm-core/adapters/mysql_adapter.rb +1 -1
  11. data/lib/dm-core/adapters/postgres_adapter.rb +1 -1
  12. data/lib/dm-core/adapters/sqlite3_adapter.rb +2 -2
  13. data/lib/dm-core/associations.rb +26 -0
  14. data/lib/dm-core/associations/many_to_many.rb +34 -25
  15. data/lib/dm-core/associations/many_to_one.rb +4 -4
  16. data/lib/dm-core/associations/one_to_many.rb +48 -13
  17. data/lib/dm-core/associations/one_to_one.rb +4 -4
  18. data/lib/dm-core/associations/relationship.rb +144 -42
  19. data/lib/dm-core/associations/relationship_chain.rb +31 -24
  20. data/lib/dm-core/auto_migrations.rb +0 -4
  21. data/lib/dm-core/collection.rb +40 -7
  22. data/lib/dm-core/dependency_queue.rb +31 -0
  23. data/lib/dm-core/hook.rb +2 -2
  24. data/lib/dm-core/is.rb +2 -2
  25. data/lib/dm-core/logger.rb +10 -10
  26. data/lib/dm-core/model.rb +94 -41
  27. data/lib/dm-core/property.rb +72 -41
  28. data/lib/dm-core/property_set.rb +8 -14
  29. data/lib/dm-core/query.rb +34 -9
  30. data/lib/dm-core/repository.rb +0 -0
  31. data/lib/dm-core/resource.rb +13 -13
  32. data/lib/dm-core/scope.rb +25 -2
  33. data/lib/dm-core/type.rb +3 -3
  34. data/lib/dm-core/types/discriminator.rb +10 -8
  35. data/lib/dm-core/types/object.rb +4 -0
  36. data/lib/dm-core/types/paranoid_boolean.rb +15 -4
  37. data/lib/dm-core/types/paranoid_datetime.rb +15 -4
  38. data/lib/dm-core/version.rb +3 -0
  39. data/script/all +5 -0
  40. data/script/performance.rb +191 -0
  41. data/script/profile.rb +86 -0
  42. data/spec/integration/association_spec.rb +288 -204
  43. data/spec/integration/association_through_spec.rb +9 -3
  44. data/spec/integration/associations/many_to_many_spec.rb +97 -31
  45. data/spec/integration/associations/many_to_one_spec.rb +41 -6
  46. data/spec/integration/associations/one_to_many_spec.rb +18 -2
  47. data/spec/integration/auto_migrations_spec.rb +0 -0
  48. data/spec/integration/collection_spec.rb +89 -42
  49. data/spec/integration/dependency_queue_spec.rb +58 -0
  50. data/spec/integration/model_spec.rb +67 -8
  51. data/spec/integration/postgres_adapter_spec.rb +19 -20
  52. data/spec/integration/property_spec.rb +17 -8
  53. data/spec/integration/query_spec.rb +273 -191
  54. data/spec/integration/resource_spec.rb +108 -10
  55. data/spec/integration/strategic_eager_loading_spec.rb +138 -0
  56. data/spec/integration/transaction_spec.rb +3 -3
  57. data/spec/integration/type_spec.rb +121 -0
  58. data/spec/lib/logging_helper.rb +18 -0
  59. data/spec/lib/model_loader.rb +91 -0
  60. data/spec/lib/publicize_methods.rb +28 -0
  61. data/spec/models/vehicles.rb +34 -0
  62. data/spec/models/zoo.rb +48 -0
  63. data/spec/spec.opts +3 -0
  64. data/spec/spec_helper.rb +25 -62
  65. data/spec/unit/adapters/data_objects_adapter_spec.rb +1 -0
  66. data/spec/unit/associations/many_to_many_spec.rb +3 -0
  67. data/spec/unit/associations/many_to_one_spec.rb +9 -1
  68. data/spec/unit/associations/one_to_many_spec.rb +12 -4
  69. data/spec/unit/associations/relationship_spec.rb +19 -15
  70. data/spec/unit/associations_spec.rb +37 -0
  71. data/spec/unit/collection_spec.rb +8 -0
  72. data/spec/unit/data_mapper_spec.rb +14 -0
  73. data/spec/unit/model_spec.rb +2 -2
  74. data/spec/unit/property_set_spec.rb +0 -13
  75. data/spec/unit/property_spec.rb +92 -21
  76. data/spec/unit/query_spec.rb +49 -4
  77. data/spec/unit/resource_spec.rb +122 -60
  78. data/spec/unit/scope_spec.rb +11 -0
  79. data/tasks/ci.rb +68 -0
  80. data/tasks/dm.rb +63 -0
  81. data/tasks/doc.rb +20 -0
  82. data/tasks/hoe.rb +38 -0
  83. data/tasks/install.rb +20 -0
  84. metadata +63 -22
@@ -18,7 +18,7 @@ if ADAPTER
18
18
  has n, :taggings
19
19
 
20
20
  has n, :relationships
21
- has n, :related_posts, :through => :relationships, :class_name => 'Post'
21
+ has n, :related_posts, :through => :relationships, :class_name => 'Post', :child_key => [:post_id]
22
22
 
23
23
  has n, :posts, :through => :taggings
24
24
  end
@@ -68,7 +68,7 @@ if ADAPTER
68
68
 
69
69
  property :id, Serial
70
70
  belongs_to :post
71
- belongs_to :related_post, :class_name => "Post"
71
+ belongs_to :related_post, :class_name => "Post", :child_key => [:related_post_id]
72
72
  end
73
73
 
74
74
  [Post, Tag, Tagging, Relationship].each do |descendant|
@@ -102,7 +102,7 @@ if ADAPTER
102
102
  good.taggings << goody
103
103
  good.save
104
104
 
105
- relation = Relationship.new(:related_post => another_post)
105
+ relation = Relationship.new(:related_post_id => another_post.id)
106
106
  post.relationships << relation
107
107
  post.save
108
108
  end
@@ -135,6 +135,12 @@ if ADAPTER
135
135
  related_posts.first(10, :id => 2).map { |r| r.id }.should == [ post.id ]
136
136
  end
137
137
 
138
+ it 'should handle get()' do
139
+ post = Post.get!(2)
140
+ related_posts = Post.first.related_posts
141
+ related_posts.get(2).should == post
142
+ end
143
+
138
144
  it 'should proxy object should be frozen' do
139
145
  Post.first.related_posts.should be_frozen
140
146
  end
@@ -13,6 +13,8 @@ describe DataMapper::Associations::ManyToMany::Proxy do
13
13
  has n, :books, :through => Resource
14
14
  end
15
15
 
16
+ Object.send(:remove_const, :Book) if defined?(Book)
17
+
16
18
  class Book
17
19
  include DataMapper::Resource
18
20
 
@@ -23,7 +25,9 @@ describe DataMapper::Associations::ManyToMany::Proxy do
23
25
 
24
26
  has n, :editors, :through => Resource
25
27
  end
28
+ end
26
29
 
30
+ before do
27
31
  [ Book, Editor, BookEditor ].each { |k| k.auto_migrate! }
28
32
 
29
33
  repository(ADAPTER) do
@@ -37,90 +41,150 @@ describe DataMapper::Associations::ManyToMany::Proxy do
37
41
  BookEditor.create(:book => book_1, :editor => editor_1)
38
42
  BookEditor.create(:book => book_2, :editor => editor_1)
39
43
  BookEditor.create(:book => book_1, :editor => editor_2)
44
+
45
+ @parent = book_3
46
+ @association = @parent.editors
47
+ @other = [ editor_1 ]
48
+ end
49
+ end
50
+
51
+ it 'should provide #replace' do
52
+ @association.should respond_to(:replace)
53
+ end
54
+
55
+ describe '#replace' do
56
+ it 'should remove the resource from the collection' do
57
+ @association.should have(0).entries
58
+ @association.replace(@other)
59
+ @association.should == @other
60
+ end
61
+
62
+ it 'should not automatically save that the resource was removed from the association' do
63
+ @association.replace(@other)
64
+ @parent.reload.should have(0).editors
65
+ end
66
+
67
+ it 'should return the association' do
68
+ @association.replace(@other).object_id.should == @association.object_id
69
+ end
70
+
71
+ it 'should add the new resources so they will be saved when saving the parent' do
72
+ @association.replace(@other)
73
+ @association.should == @other
74
+ @parent.save
75
+ @association.reload.should == @other
76
+ end
77
+
78
+ it 'should instantiate the remote model if passed an array of hashes' do
79
+ @association.replace([ { :name => 'Jim Smith' } ])
80
+ other = [ Editor.first(:name => 'Jim Smith') ]
81
+ other.first.should_not be_nil
82
+ @association.should == other
83
+ @parent.save
84
+ @association.reload.should == other
40
85
  end
41
86
  end
42
87
 
43
88
  it "should correctly link records" do
44
- Editor.get(1).books.size.should == 2
45
- Editor.get(2).books.size.should == 1
46
- Book.get(1).editors.size.should == 2
47
- Book.get(2).editors.size.should == 1
89
+ Book.get(1).should have(2).editors
90
+ Book.get(2).should have(1).editors
91
+ Book.get(3).should have(0).editors
92
+ Editor.get(1).should have(2).books
93
+ Editor.get(2).should have(1).books
48
94
  end
49
95
 
50
96
  it "should be able to have associated objects manually added" do
51
97
  book = Book.get(3)
52
- # book.editors.size.should == 0
98
+ book.should have(0).editors
53
99
 
54
100
  be = BookEditor.new(:book_id => book.id, :editor_id => 2)
55
101
  book.book_editors << be
56
102
  book.save
57
103
 
58
- book.reload
59
- book.editors.size.should == 1
104
+ book.reload.should have(1).editors
60
105
  end
61
106
 
62
107
  it "should automatically added necessary through class" do
63
108
  book = Book.get(3)
109
+ book.should have(0).editors
110
+
64
111
  book.editors << Editor.get(1)
65
112
  book.editors << Editor.new(:name => "Jimmy John")
66
113
  book.save
67
- book.editors.size.should == 3
114
+
115
+ book.reload.should have(2).editors
68
116
  end
69
117
 
70
118
  it "should react correctly to a new record" do
71
119
  book = Book.new(:title => "Finnegan's Wake")
72
- book.editors << Editor.get(2)
120
+ editor = Editor.get(2)
121
+ book.should have(0).editors
122
+ editor.should have(1).books
123
+
124
+ book.editors << editor
73
125
  book.save
74
- book.editors.size.should == 1
75
- Editor.get(2).books.size.should == 3
126
+
127
+ book.reload.should have(1).editors
128
+ editor.reload.should have(2).books
76
129
  end
77
130
 
78
131
  it "should be able to delete intermediate model" do
79
- book = Book.get(3)
80
- be = BookEditor.get(3,1)
132
+ book = Book.get(1)
133
+ book.should have(2).book_editors
134
+ book.should have(2).editors
135
+
136
+ be = BookEditor.get(1,1)
81
137
  book.book_editors.delete(be)
82
138
  book.save
139
+
83
140
  book.reload
84
- book = Book.get(3)
85
- book.book_editors.size.should == 2
86
- book.editors.size.should == 2
141
+ book.should have(1).book_editors
142
+ book.should have(1).editors
87
143
  end
88
144
 
89
145
  it "should be clearable" do
90
146
  repository(ADAPTER) do
91
147
  book = Book.get(2)
92
- book.editors.size.should == 1
148
+ book.should have(1).book_editors
149
+ book.should have(1).editors
150
+
93
151
  book.editors.clear
94
152
  book.save
153
+
95
154
  book.reload
96
- book.book_editors.size.should == 0
97
- book.editors.size.should == 0
155
+ book.should have(0).book_editors
156
+ book.should have(0).editors
98
157
  end
99
158
  repository(ADAPTER) do
100
- Book.get(2).editors.size.should == 0
159
+ Book.get(2).should have(0).editors
101
160
  end
102
161
  end
103
162
 
104
163
  it "should be able to delete one object" do
105
164
  book = Book.get(1)
106
- editor = book.editors.first
165
+ book.should have(2).book_editors
166
+ book.should have(2).editors
107
167
 
108
- book.editors.size.should == 2
168
+ editor = book.editors.first
109
169
  book.editors.delete(editor)
110
- book.book_editors.size.should == 1
111
- book.editors.size.should == 1
112
170
  book.save
171
+
113
172
  book.reload
114
- Editor.get(1).books.should_not include(book)
173
+ book.should have(1).book_editors
174
+ book.should have(1).editors
175
+ editor.reload.books.should_not include(book)
115
176
  end
116
177
 
117
178
  it "should be destroyable" do
118
179
  pending 'cannot destroy a collection yet' do
119
- book = Book.get(3)
180
+ book = Book.get(2)
181
+ book.should have(1).editors
182
+
120
183
  book.editors.destroy
121
184
  book.save
185
+
122
186
  book.reload
123
- book.editors.size.should == 0
187
+ book.should have(0).editors
124
188
  end
125
189
  end
126
190
 
@@ -139,7 +203,9 @@ describe DataMapper::Associations::ManyToMany::Proxy do
139
203
  class Book
140
204
  has n, :authors, :through => Resource
141
205
  end
206
+ end
142
207
 
208
+ before do
143
209
  [ Author, AuthorBook ].each { |k| k.auto_migrate! }
144
210
 
145
211
  @author = Author.create(:name => 'James Joyce')
@@ -162,10 +228,10 @@ describe DataMapper::Associations::ManyToMany::Proxy do
162
228
  end
163
229
 
164
230
  it 'should correctly link records' do
165
- @author.books.should have(3).entries
166
- @book_1.authors.should have(1).entries
167
- @book_2.authors.should have(1).entries
168
- @book_3.authors.should have(1).entries
231
+ @author.should have(3).books
232
+ @book_1.should have(1).authors
233
+ @book_2.should have(1).authors
234
+ @book_3.should have(1).authors
169
235
  end
170
236
  end
171
237
  end
@@ -22,20 +22,55 @@ if ADAPTER
22
22
 
23
23
  property :id, Serial
24
24
  property :name, String
25
+ property :type, Discriminator
25
26
 
26
27
  belongs_to :parent, :class_name => 'ManyToOneSpec::Parent'
27
28
  end
29
+
30
+ class StepChild < Child
31
+ end
28
32
  end
29
33
 
30
34
  describe DataMapper::Associations::ManyToOne::Proxy do
31
35
  before do
32
- ManyToOneSpec::Parent.auto_migrate!
33
- ManyToOneSpec::Child.auto_migrate!
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
34
65
 
35
- @parent = ManyToOneSpec::Parent.create(:name => 'parent')
36
- @child = ManyToOneSpec::Child.create(:name => 'child', :parent => @parent)
37
- @other = ManyToOneSpec::Parent.create(:name => 'other parent')
38
- @association = @child.parent
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
39
74
  end
40
75
 
41
76
  describe '#replace' do
@@ -9,10 +9,14 @@ describe "OneToMany" do
9
9
 
10
10
  property :id, Serial
11
11
  property :name, String
12
+ property :class_type, Discriminator
12
13
 
13
14
  has n, :players
14
15
  end
15
16
 
17
+ class BaseballTeam < Team
18
+ end
19
+
16
20
  class Player
17
21
  include DataMapper::Resource
18
22
 
@@ -26,7 +30,8 @@ describe "OneToMany" do
26
30
 
27
31
  [Team, Player].each { |k| k.auto_migrate!(ADAPTER) }
28
32
 
29
- Team.create!(:name => "Cowboys")
33
+ Team.create(:name => "Cowboys")
34
+ BaseballTeam.create(:name => "Giants")
30
35
  end
31
36
 
32
37
  it "unsaved parent model should accept array of hashes for association" do
@@ -42,7 +47,7 @@ describe "OneToMany" do
42
47
  team.save
43
48
 
44
49
  repository(ADAPTER) do
45
- Team.get(2).players.should == players
50
+ Team.get(3).players.should == players
46
51
  end
47
52
  end
48
53
 
@@ -63,4 +68,15 @@ describe "OneToMany" do
63
68
  Team.get(1).players.should == players
64
69
  end
65
70
  end
71
+
72
+ describe "STI" do
73
+ it "should work" do
74
+ repository(ADAPTER) do
75
+ Player.create(:name => "Barry Bonds", :team => BaseballTeam.first)
76
+ end
77
+ repository(ADAPTER) do
78
+ Player.first.team.should == BaseballTeam.first
79
+ end
80
+ end
81
+ end
66
82
  end
File without changes
@@ -29,6 +29,10 @@ if ADAPTER
29
29
  property :zebra_id, Integer
30
30
 
31
31
  belongs_to :zebra
32
+
33
+ def self.sort_by_name
34
+ all(:order => [ :name ])
35
+ end
32
36
  end
33
37
 
34
38
  class CollectionSpecParty
@@ -71,46 +75,6 @@ if ADAPTER
71
75
  end
72
76
  end
73
77
 
74
- describe 'association proxying' do
75
- include CollectionSpecHelper
76
-
77
- before do
78
- setup
79
- end
80
-
81
- it "should provide a Query" do
82
- repository(ADAPTER) do
83
- zebras = Zebra.all(:order => [ :name ])
84
- zebras.query.order.should == [DataMapper::Query::Direction.new(Zebra.properties(ADAPTER)[:name])]
85
- end
86
- end
87
-
88
- it "should proxy the relationships of the model" do
89
- repository(ADAPTER) do
90
- zebras = Zebra.all
91
- zebras.should have(3).entries
92
- zebras.find { |zebra| zebra.name == 'Nancy' }.stripes.should have(2).entries
93
- zebras.stripes.should == [@babe, @snowball]
94
- end
95
- end
96
-
97
- it "should preserve it's order on reload" do
98
- repository(ADAPTER) do |r|
99
- zebras = Zebra.all(:order => [ :name ])
100
-
101
- order = %w{ Bessie Nancy Steve }
102
-
103
- zebras.map { |z| z.name }.should == order
104
-
105
- # Force a lazy-load call:
106
- zebras.first.notes
107
-
108
- # The order should be unaffected.
109
- zebras.map { |z| z.name }.should == order
110
- end
111
- end
112
- end
113
-
114
78
  describe DataMapper::Collection do
115
79
  include CollectionSpecHelper
116
80
 
@@ -163,6 +127,49 @@ if ADAPTER
163
127
  results.first.should == bob
164
128
  end
165
129
 
130
+ describe 'model proxying' do
131
+ it 'should delegate to a model method' do
132
+ stripes = @model.first.stripes
133
+ stripes.should respond_to(:sort_by_name)
134
+ stripes.sort_by_name.should == [ @babe, @snowball ]
135
+ end
136
+ end
137
+
138
+ describe 'association proxying' do
139
+ it "should provide a Query" do
140
+ repository(ADAPTER) do
141
+ zebras = Zebra.all(:order => [ :name ])
142
+ zebras.query.order.should == [DataMapper::Query::Direction.new(Zebra.properties(ADAPTER)[:name])]
143
+ end
144
+ end
145
+
146
+ it "should proxy the relationships of the model" do
147
+ repository(ADAPTER) do
148
+ zebras = Zebra.all
149
+ zebras.should have(3).entries
150
+ zebras.find { |zebra| zebra.name == 'Nancy' }.stripes.should have(2).entries
151
+ zebras.should respond_to(:stripes)
152
+ zebras.stripes.should == [@babe, @snowball]
153
+ end
154
+ end
155
+
156
+ it "should preserve it's order on reload" do
157
+ repository(ADAPTER) do |r|
158
+ zebras = Zebra.all(:order => [ :name ])
159
+
160
+ order = %w{ Bessie Nancy Steve }
161
+
162
+ zebras.map { |z| z.name }.should == order
163
+
164
+ # Force a lazy-load call:
165
+ zebras.first.notes
166
+
167
+ # The order should be unaffected.
168
+ zebras.map { |z| z.name }.should == order
169
+ end
170
+ end
171
+ end
172
+
166
173
  describe '.new' do
167
174
  describe 'with non-index keys' do
168
175
  it 'should instantiate read-only resources' do
@@ -295,6 +302,32 @@ if ADAPTER
295
302
  end
296
303
  end
297
304
 
305
+ describe '#build' do
306
+ it 'should build a new resource' do
307
+ resource = @collection.build(:name => 'John')
308
+ resource.should be_kind_of(@model)
309
+ resource.should be_new_record
310
+ end
311
+
312
+ it 'should append the new resource to the collection' do
313
+ resource = @collection.build(:name => 'John')
314
+ resource.should be_new_record
315
+ resource.collection.object_id.should == @collection.object_id
316
+ @collection.should include(resource)
317
+ end
318
+
319
+ it 'should use the query conditions to set default values' do
320
+ resource = @collection.build
321
+ resource.should be_new_record
322
+ resource.name.should be_nil
323
+
324
+ @collection.query.update(:name => 'John')
325
+
326
+ resource = @collection.build
327
+ resource.name.should == 'John'
328
+ end
329
+ end
330
+
298
331
  describe '#clear' do
299
332
  it 'should orphan the resource from the collection' do
300
333
  entries = @collection.entries
@@ -508,6 +541,14 @@ if ADAPTER
508
541
  end
509
542
  end
510
543
 
544
+ describe '#freeze' do
545
+ it 'should freeze the underlying array' do
546
+ @collection.should_not be_frozen
547
+ @collection.freeze
548
+ @collection.should be_frozen
549
+ end
550
+ end
551
+
511
552
  describe '#get' do
512
553
  it 'should find a resource in a collection by key' do
513
554
  resource = @collection.get(*@nancy.key)
@@ -515,9 +556,15 @@ if ADAPTER
515
556
  resource.id.should == @nancy.id
516
557
  end
517
558
 
559
+ it "should find a resource in a collection by typecasting the key" do
560
+ resource = @collection.get(@nancy.key.to_s)
561
+ resource.should be_kind_of(DataMapper::Resource)
562
+ resource.id.should == @nancy.id
563
+ end
564
+
518
565
  it 'should not find a resource not in the collection' do
519
566
  @query.update(:offset => 0, :limit => 3)
520
- @david = Zebra.create!(:name => 'David', :age => 15, :notes => 'Albino')
567
+ @david = Zebra.create(:name => 'David', :age => 15, :notes => 'Albino')
521
568
  @collection.get(@david.key).should be_nil
522
569
  end
523
570
  end
@@ -531,7 +578,7 @@ if ADAPTER
531
578
 
532
579
  it 'should raise an exception if the resource is not found' do
533
580
  @query.update(:offset => 0, :limit => 3)
534
- @david = Zebra.create!(:name => 'David', :age => 15, :notes => 'Albino')
581
+ @david = Zebra.create(:name => 'David', :age => 15, :notes => 'Albino')
535
582
  lambda {
536
583
  @collection.get!(@david.key)
537
584
  }.should raise_error(DataMapper::ObjectNotFoundError)