activerecord 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- data/CHANGELOG +102 -1
- data/dev-utils/eval_debugger.rb +12 -7
- data/lib/active_record.rb +2 -0
- data/lib/active_record/aggregations.rb +1 -1
- data/lib/active_record/associations.rb +74 -53
- data/lib/active_record/associations.rb.orig +555 -0
- data/lib/active_record/associations/association_collection.rb +74 -15
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +86 -25
- data/lib/active_record/associations/has_many_association.rb +48 -50
- data/lib/active_record/base.rb +56 -24
- data/lib/active_record/connection_adapters/abstract_adapter.rb +46 -3
- data/lib/active_record/connection_adapters/mysql_adapter.rb +15 -15
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +128 -135
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +76 -78
- data/lib/active_record/deprecated_associations.rb +1 -1
- data/lib/active_record/fixtures.rb +137 -54
- data/lib/active_record/observer.rb +1 -1
- data/lib/active_record/support/inflector.rb +8 -0
- data/lib/active_record/transactions.rb +31 -14
- data/rakefile +13 -5
- data/test/abstract_unit.rb +7 -1
- data/test/associations_test.rb +99 -27
- data/test/base_test.rb +15 -1
- data/test/connections/native_sqlite/connection.rb +24 -14
- data/test/deprecated_associations_test.rb +3 -4
- data/test/deprecated_associations_test.rb.orig +334 -0
- data/test/fixtures/bad_fixtures/attr_with_numeric_first_char +1 -0
- data/test/fixtures/bad_fixtures/attr_with_spaces +1 -0
- data/test/fixtures/bad_fixtures/blank_line +3 -0
- data/test/fixtures/bad_fixtures/duplicate_attributes +3 -0
- data/test/fixtures/bad_fixtures/missing_value +1 -0
- data/test/fixtures/company_in_module.rb +15 -1
- data/test/fixtures/db_definitions/mysql.sql +2 -1
- data/test/fixtures/db_definitions/postgresql.sql +2 -1
- data/test/fixtures/db_definitions/sqlite.sql +2 -1
- data/test/fixtures/developers_projects/david_action_controller +2 -1
- data/test/fixtures/developers_projects/david_active_record +2 -1
- data/test/fixtures/fixture_database.sqlite +0 -0
- data/test/fixtures/fixture_database_2.sqlite +0 -0
- data/test/fixtures/project.rb +2 -1
- data/test/fixtures/projects/action_controller +1 -1
- data/test/fixtures/topics/second +1 -1
- data/test/fixtures_test.rb +63 -4
- data/test/inflector_test.rb +17 -0
- data/test/modules_test.rb +8 -0
- data/test/transactions_test.rb +16 -4
- metadata +10 -2
@@ -20,7 +20,7 @@ module ActiveRecord
|
|
20
20
|
|
21
21
|
def deprecated_remove_association_relation(association_name)# :nodoc:
|
22
22
|
module_eval <<-"end_eval", __FILE__, __LINE__
|
23
|
-
def remove_#{association_name}(items)
|
23
|
+
def remove_#{association_name}(*items)
|
24
24
|
#{association_name}.delete(items)
|
25
25
|
end
|
26
26
|
end_eval
|
@@ -1,4 +1,6 @@
|
|
1
1
|
require 'yaml'
|
2
|
+
require 'active_record/support/class_inheritable_attributes'
|
3
|
+
require 'active_record/support/inflector'
|
2
4
|
|
3
5
|
# Fixtures are a way of organizing data that you want to test against. Each fixture file is created as a row
|
4
6
|
# in the database and created as a hash with column names as keys and data as values. All of these fixture hashes
|
@@ -31,6 +33,20 @@ require 'yaml'
|
|
31
33
|
# assert_equal @developers["david"]["name"], Developer.find(@developers["david"]["id"]).name
|
32
34
|
# end
|
33
35
|
#
|
36
|
+
# == Automatic fixture setup and instance variable availability
|
37
|
+
#
|
38
|
+
# Fixtures can also be automatically instantiated in instance variables relating to their names using the following style:
|
39
|
+
#
|
40
|
+
# class FixturesTest < Test::Unit::TestCase
|
41
|
+
# fixtures :developers # you can add more with comma separation
|
42
|
+
#
|
43
|
+
# def test_developers
|
44
|
+
# assert_equal 3, @developers.size # the container for all the fixtures is automatically set
|
45
|
+
# assert_kind_of Developer, @david # works like @developers["david"].find
|
46
|
+
# assert_equal "David Heinemeier Hansson", @david.name
|
47
|
+
# end
|
48
|
+
# end
|
49
|
+
#
|
34
50
|
# == YAML fixtures
|
35
51
|
#
|
36
52
|
# Additionally, fixtures supports yaml files. Like fixture files, these yaml files have a pre-defined format. The document
|
@@ -53,50 +69,56 @@ require 'yaml'
|
|
53
69
|
# In that file, there's two records. Each record must have two parts: 'name' and 'data'. The data that you add
|
54
70
|
# must be indented like you see above.
|
55
71
|
#
|
56
|
-
# Yaml fixtures file names must end with .
|
72
|
+
# Yaml fixtures file names must end with .yml as in people.yml or camel.yml. The yaml fixtures are placed in the same
|
57
73
|
# directory as the normal fixtures and can happy co-exist. :)
|
58
|
-
class Fixtures
|
74
|
+
class Fixtures < Hash
|
75
|
+
def self.instantiate_fixtures(object, fixtures_directory, *table_names)
|
76
|
+
[ create_fixtures(fixtures_directory, *table_names) ].flatten.each_with_index do |fixtures, idx|
|
77
|
+
object.instance_variable_set "@#{table_names[idx]}", fixtures
|
78
|
+
fixtures.each { |name, fixture| object.instance_variable_set "@#{name}", fixture.find }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
59
82
|
def self.create_fixtures(fixtures_directory, *table_names)
|
60
83
|
connection = block_given? ? yield : ActiveRecord::Base.connection
|
61
|
-
ActiveRecord::Base.logger.level
|
84
|
+
old_logger_level = ActiveRecord::Base.logger.level
|
62
85
|
|
63
|
-
|
64
|
-
|
86
|
+
begin
|
87
|
+
ActiveRecord::Base.logger.level = Logger::ERROR
|
88
|
+
fixtures = connection.transaction do
|
89
|
+
table_names.flatten.map do |table_name|
|
90
|
+
Fixtures.new(connection, table_name.to_s, File.join(fixtures_directory, table_name.to_s))
|
91
|
+
end
|
92
|
+
end
|
93
|
+
return fixtures.size > 1 ? fixtures : fixtures.first
|
94
|
+
ensure
|
95
|
+
ActiveRecord::Base.logger.level = old_logger_level
|
65
96
|
end
|
66
|
-
|
67
|
-
ActiveRecord::Base.logger.level = Logger::DEBUG
|
68
|
-
|
69
|
-
return fixtures.size > 1 ? fixtures : fixtures.first
|
70
97
|
end
|
71
98
|
|
72
99
|
def initialize(connection, table_name, fixture_path, file_filter = /^\.|CVS|\.yaml/)
|
73
100
|
@connection, @table_name, @fixture_path, @file_filter = connection, table_name, fixture_path, file_filter
|
74
|
-
@
|
101
|
+
@class_name = Inflector.classify(@table_name)
|
75
102
|
|
103
|
+
read_fixture_files
|
76
104
|
delete_existing_fixtures
|
77
105
|
insert_fixtures
|
78
106
|
end
|
79
107
|
|
80
|
-
# Access a fixture hash by using its file name as the key
|
81
|
-
def [](key)
|
82
|
-
@fixtures[key]
|
83
|
-
end
|
84
|
-
|
85
|
-
# Get the number of fixtures kept in this container
|
86
|
-
def length
|
87
|
-
@fixtures.length
|
88
|
-
end
|
89
|
-
|
90
108
|
private
|
91
|
-
def
|
92
|
-
Dir.entries(@fixture_path).
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
109
|
+
def read_fixture_files
|
110
|
+
Dir.entries(@fixture_path).each do |file|
|
111
|
+
case file
|
112
|
+
when /\.ya?ml$/
|
113
|
+
path = File.join(@fixture_path, file)
|
114
|
+
YamlFixture.produce(path).each { |fixture|
|
115
|
+
self[fixture.yaml_name] = fixture
|
116
|
+
}
|
117
|
+
when @file_filter
|
118
|
+
# skip
|
119
|
+
else
|
120
|
+
self[file] = Fixture.new(@fixture_path, file, @class_name)
|
98
121
|
end
|
99
|
-
fixtures
|
100
122
|
end
|
101
123
|
end
|
102
124
|
|
@@ -105,20 +127,25 @@ class Fixtures
|
|
105
127
|
end
|
106
128
|
|
107
129
|
def insert_fixtures
|
108
|
-
|
130
|
+
values.each do |fixture|
|
109
131
|
@connection.execute "INSERT INTO #{@table_name} (#{fixture.key_list}) VALUES(#{fixture.value_list})"
|
110
132
|
end
|
111
133
|
end
|
112
|
-
|
113
|
-
def []=(key, value)
|
114
|
-
@fixtures[key] = value
|
115
|
-
end
|
116
134
|
end
|
117
135
|
|
118
136
|
class Fixture #:nodoc:
|
119
|
-
|
120
|
-
|
121
|
-
|
137
|
+
include Enumerable
|
138
|
+
class FixtureError < StandardError; end
|
139
|
+
class FormatError < FixtureError; end
|
140
|
+
|
141
|
+
def initialize(fixture_path, file, class_name)
|
142
|
+
@fixture_path, @file, @class_name = fixture_path, file, class_name
|
143
|
+
@fixture = read_fixture_file
|
144
|
+
@class_name
|
145
|
+
end
|
146
|
+
|
147
|
+
def each
|
148
|
+
@fixture.each { |item| yield item }
|
122
149
|
end
|
123
150
|
|
124
151
|
def [](key)
|
@@ -134,14 +161,29 @@ class Fixture #:nodoc:
|
|
134
161
|
end
|
135
162
|
|
136
163
|
def value_list
|
137
|
-
@fixture.values.map { |v| "'
|
164
|
+
@fixture.values.map { |v| ActiveRecord::Base.connection.quote(v).gsub('\\n', "\n").gsub('\\r', "\r") }.join(", ")
|
138
165
|
end
|
139
|
-
|
166
|
+
|
167
|
+
def find
|
168
|
+
Object.const_get(@class_name).find(self["id"])
|
169
|
+
end
|
170
|
+
|
140
171
|
private
|
141
|
-
def
|
142
|
-
|
143
|
-
|
144
|
-
|
172
|
+
def read_fixture_file
|
173
|
+
path = File.join(@fixture_path, @file)
|
174
|
+
IO.readlines(path).inject({}) do |fixture, line|
|
175
|
+
# Mercifully skip empty lines.
|
176
|
+
next if line.empty?
|
177
|
+
|
178
|
+
# Use the same regular expression for attributes as Active Record.
|
179
|
+
unless md = /^\s*([a-zA-Z][-_\w]*)\s*=>\s*(.+)\s*$/.match(line)
|
180
|
+
raise FormatError, "#{path}: fixture format error at '#{line}'. Expecting 'key => value'."
|
181
|
+
end
|
182
|
+
key, value = md.captures
|
183
|
+
|
184
|
+
# Disallow duplicate keys to catch typos.
|
185
|
+
raise FormatError, "#{path}: duplicate '#{key}' in fixture." if fixture[key]
|
186
|
+
fixture[key] = value.strip
|
145
187
|
fixture
|
146
188
|
end
|
147
189
|
end
|
@@ -150,23 +192,64 @@ end
|
|
150
192
|
# A YamlFixture is like a fixture, but instead of a name to use as
|
151
193
|
# a key, it uses a yaml_name.
|
152
194
|
class YamlFixture < Fixture #:nodoc:
|
153
|
-
|
154
|
-
|
195
|
+
class YamlFormatError < FormatError; end
|
196
|
+
|
197
|
+
# yaml_name is analogous to a normal fixture's filename
|
198
|
+
attr_reader :yaml_name
|
155
199
|
|
156
|
-
#
|
200
|
+
# Instantiate with fixture name and data.
|
157
201
|
def initialize(yaml_name, fixture)
|
158
202
|
@yaml_name, @fixture = yaml_name, fixture
|
159
203
|
end
|
160
204
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
205
|
+
def produce(yaml_file_name)
|
206
|
+
YamlFixture.produce(yaml_file_name)
|
207
|
+
end
|
208
|
+
|
209
|
+
# Extract an array of YamlFixtures from a yaml file.
|
210
|
+
def self.produce(yaml_file_name)
|
211
|
+
fixtures = []
|
212
|
+
File.open(yaml_file_name) do |yaml_file|
|
213
|
+
YAML::load_documents(yaml_file) do |doc|
|
214
|
+
missing = %w(name data).reject { |key| doc[key] }.join(' and ')
|
215
|
+
raise YamlFormatError, "#{path}: yaml fixture missing #{missing}: #{doc.to_yaml}" unless missing.empty?
|
216
|
+
fixtures << YamlFixture.new(doc['name'], doc['data'])
|
217
|
+
end
|
168
218
|
end
|
169
|
-
|
170
|
-
results
|
219
|
+
fixtures
|
171
220
|
end
|
172
221
|
end
|
222
|
+
|
223
|
+
class Test::Unit::TestCase #:nodoc:
|
224
|
+
include ClassInheritableAttributes
|
225
|
+
|
226
|
+
cattr_accessor :fixture_path
|
227
|
+
cattr_accessor :fixture_table_names
|
228
|
+
|
229
|
+
def self.fixtures(*table_names)
|
230
|
+
write_inheritable_attribute("fixture_table_names", table_names)
|
231
|
+
end
|
232
|
+
|
233
|
+
def setup
|
234
|
+
instantiate_fixtures(*fixture_table_names) if fixture_table_names
|
235
|
+
end
|
236
|
+
|
237
|
+
def self.method_added(method_symbol)
|
238
|
+
if method_symbol == :setup && !method_defined?(:setup_without_fixtures)
|
239
|
+
alias_method :setup_without_fixtures, :setup
|
240
|
+
define_method(:setup) do
|
241
|
+
instantiate_fixtures(*fixture_table_names) if fixture_table_names
|
242
|
+
setup_without_fixtures
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
private
|
248
|
+
def instantiate_fixtures(*table_names)
|
249
|
+
Fixtures.instantiate_fixtures(self, fixture_path, *table_names)
|
250
|
+
end
|
251
|
+
|
252
|
+
def fixture_table_names
|
253
|
+
self.class.read_inheritable_attribute("fixture_table_names")
|
254
|
+
end
|
255
|
+
end
|
@@ -8,7 +8,7 @@ module ActiveRecord
|
|
8
8
|
#
|
9
9
|
# class CommentObserver < ActiveRecord::Observer
|
10
10
|
# def after_save(comment)
|
11
|
-
#
|
11
|
+
# Notifications.deliver_comment("admin@do.com", "New comment was posted", comment)
|
12
12
|
# end
|
13
13
|
# end
|
14
14
|
#
|
@@ -31,6 +31,14 @@ module Inflector
|
|
31
31
|
class_name_in_module.gsub(/^.*::/, '')
|
32
32
|
end
|
33
33
|
|
34
|
+
def tableize(class_name)
|
35
|
+
pluralize(underscore(class_name))
|
36
|
+
end
|
37
|
+
|
38
|
+
def classify(table_name)
|
39
|
+
camelize(singularize(table_name))
|
40
|
+
end
|
41
|
+
|
34
42
|
def foreign_key(class_name, separate_class_name_and_id_with_underscore = true)
|
35
43
|
Inflector.underscore(Inflector.demodulize(class_name)) +
|
36
44
|
(separate_class_name_and_id_with_underscore ? "_id" : "id")
|
@@ -18,13 +18,13 @@ module ActiveRecord
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
# Transactions are protective blocks where SQL statements are only permanent if they can all
|
21
|
+
# Transactions are protective blocks where SQL statements are only permanent if they can all succeed as one atomic action.
|
22
22
|
# The classic example is a transfer between two accounts where you can only have a deposit if the withdrawal succedded and
|
23
23
|
# vice versa. Transaction enforce the integrity of the database and guards the data against program errors or database break-downs.
|
24
24
|
# So basically you should use transaction blocks whenever you have a number of statements that must be executed together or
|
25
25
|
# not at all. Example:
|
26
26
|
#
|
27
|
-
#
|
27
|
+
# transaction do
|
28
28
|
# david.withdrawal(100)
|
29
29
|
# mary.deposit(100)
|
30
30
|
# end
|
@@ -33,6 +33,23 @@ module ActiveRecord
|
|
33
33
|
# Exceptions will force a ROLLBACK that returns the database to the state before the transaction was begun. Be aware, though,
|
34
34
|
# that the objects by default will _not_ have their instance data returned to their pre-transactional state.
|
35
35
|
#
|
36
|
+
# == Transactions are not distributed across database connections
|
37
|
+
#
|
38
|
+
# A transaction acts on a single database connection. If you have
|
39
|
+
# multiple class-specific databases, the transaction will not protect
|
40
|
+
# interaction among them. One workaround is to begin a transaction
|
41
|
+
# on each class whose models you alter:
|
42
|
+
#
|
43
|
+
# Student.transaction do
|
44
|
+
# Course.transaction do
|
45
|
+
# course.enroll(student)
|
46
|
+
# student.units += course.units
|
47
|
+
# end
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# This is a poor solution, but full distributed transactions are beyond
|
51
|
+
# the scope of Active Record.
|
52
|
+
#
|
36
53
|
# == Save and destroy are automatically wrapped in a transaction
|
37
54
|
#
|
38
55
|
# Both Base#save and Base#destroy come wrapped in a transaction that ensures that whatever you do in validations or callbacks
|
@@ -65,38 +82,38 @@ module ActiveRecord
|
|
65
82
|
begin
|
66
83
|
objects.each { |o| o.extend(Transaction::Simple) }
|
67
84
|
objects.each { |o| o.start_transaction }
|
68
|
-
connection.begin_db_transaction
|
69
85
|
|
70
|
-
block
|
71
|
-
|
72
|
-
connection.commit_db_transaction
|
86
|
+
result = connection.transaction(&block)
|
87
|
+
|
73
88
|
objects.each { |o| o.commit_transaction }
|
74
|
-
|
75
|
-
|
89
|
+
return result
|
90
|
+
rescue Exception => object_transaction_rollback
|
76
91
|
objects.each { |o| o.abort_transaction }
|
77
|
-
raise
|
92
|
+
raise
|
78
93
|
ensure
|
79
94
|
TRANSACTION_MUTEX.unlock
|
80
95
|
end
|
81
96
|
end
|
82
97
|
end
|
83
98
|
|
99
|
+
def transaction(*objects, &block)
|
100
|
+
self.class.transaction(*objects, &block)
|
101
|
+
end
|
102
|
+
|
84
103
|
def destroy_with_transactions #:nodoc:
|
85
104
|
if TRANSACTION_MUTEX.locked?
|
86
105
|
destroy_without_transactions
|
87
106
|
else
|
88
|
-
|
107
|
+
transaction { destroy_without_transactions }
|
89
108
|
end
|
90
109
|
end
|
91
110
|
|
92
111
|
def save_with_transactions(perform_validation = true) #:nodoc:
|
93
|
-
result = nil
|
94
112
|
if TRANSACTION_MUTEX.locked?
|
95
|
-
|
113
|
+
save_without_transactions(perform_validation)
|
96
114
|
else
|
97
|
-
|
115
|
+
transaction { save_without_transactions(perform_validation) }
|
98
116
|
end
|
99
|
-
return result
|
100
117
|
end
|
101
118
|
end
|
102
119
|
end
|
data/rakefile
CHANGED
@@ -6,7 +6,10 @@ require 'rake/packagetask'
|
|
6
6
|
require 'rake/gempackagetask'
|
7
7
|
require 'rake/contrib/rubyforgepublisher'
|
8
8
|
|
9
|
-
|
9
|
+
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
|
10
|
+
PKG_NAME = 'activerecord'
|
11
|
+
PKG_VERSION = '1.1.0' + PKG_BUILD
|
12
|
+
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
|
10
13
|
|
11
14
|
PKG_FILES = FileList[
|
12
15
|
"lib/**/*", "test/**/*", "examples/**/*", "doc/**/*", "[A-Z]*", "install.rb", "rakefile"
|
@@ -20,7 +23,6 @@ task :default => [ :test_ruby_mysql, :test_mysql_ruby, :test_sqlite, :test_postg
|
|
20
23
|
|
21
24
|
Rake::TestTask.new("test_ruby_mysql") { |t|
|
22
25
|
t.libs << "test" << "test/connections/native_mysql"
|
23
|
-
t.test_files = "lib/active_record/vendor/mysql.rb"
|
24
26
|
t.pattern = 'test/*_test.rb'
|
25
27
|
t.verbose = true
|
26
28
|
}
|
@@ -43,7 +45,7 @@ Rake::TestTask.new("test_sqlite") { |t|
|
|
43
45
|
t.verbose = true
|
44
46
|
}
|
45
47
|
|
46
|
-
#
|
48
|
+
# Generate the RDoc documentation
|
47
49
|
|
48
50
|
Rake::RDocTask.new { |rdoc|
|
49
51
|
rdoc.rdoc_dir = 'doc'
|
@@ -56,6 +58,12 @@ Rake::RDocTask.new { |rdoc|
|
|
56
58
|
}
|
57
59
|
|
58
60
|
|
61
|
+
# Publish beta gem
|
62
|
+
desc "Publish the beta gem"
|
63
|
+
task :pgem => [:package] do
|
64
|
+
Rake::SshFilePublisher.new("davidhh@one.textdrive.com", "domains/rubyonrails.org/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
|
65
|
+
end
|
66
|
+
|
59
67
|
# Publish documentation
|
60
68
|
desc "Publish the API documentation"
|
61
69
|
task :pdoc => [:rdoc] do
|
@@ -73,7 +81,7 @@ end
|
|
73
81
|
dist_dirs = [ "lib", "test", "examples", "dev-utils" ]
|
74
82
|
|
75
83
|
spec = Gem::Specification.new do |s|
|
76
|
-
s.name =
|
84
|
+
s.name = PKG_NAME
|
77
85
|
s.version = PKG_VERSION
|
78
86
|
s.summary = "Implements the ActiveRecord pattern for ORM."
|
79
87
|
s.description = %q{Implements the ActiveRecord pattern (Fowler, PoEAA) for ORM. It ties database tables and classes together for business objects, like Customer or Subscription, that can find, save, and destroy themselves without resorting to manual SQL.}
|
@@ -119,4 +127,4 @@ task :lines do
|
|
119
127
|
end
|
120
128
|
}
|
121
129
|
puts "Lines #{lines}, LOC #{codelines}"
|
122
|
-
end
|
130
|
+
end
|
data/test/abstract_unit.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
2
2
|
|
3
|
+
# Make rubygems available for testing if possible
|
4
|
+
begin require('rubygems'); rescue LoadError; end
|
5
|
+
begin require('dev-utils/debug'); rescue LoadError; end
|
6
|
+
|
3
7
|
require 'test/unit'
|
4
8
|
require 'active_record'
|
5
9
|
require 'active_record/fixtures'
|
@@ -13,4 +17,6 @@ class Test::Unit::TestCase #:nodoc:
|
|
13
17
|
Fixtures.create_fixtures(File.dirname(__FILE__) + "/fixtures/", table_names)
|
14
18
|
end
|
15
19
|
end
|
16
|
-
end
|
20
|
+
end
|
21
|
+
|
22
|
+
Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures/"
|