1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
|
require 'erb'
require 'yaml'
require 'active_record/support/class_inheritable_attributes'
require 'active_record/support/inflector'
# Fixtures are a way of organizing data that you want to test against. You normally have one YAML file with fixture
# definitions per model. They're just hashes of hashes with the first-level key being the name of fixture (try to keep
# that name unique across all fixtures in the system for reasons that will follow). The value to that key is a hash
# where the keys are column names and the values the fixture data you want to insert into it. Example for developers.yml:
#
# david:
# id: 1
# name: David Heinemeier Hansson
# birthday: 1979-10-15
# profession: Systems development
#
# steve:
# id: 2
# name: Steve Ross Kellock
# birthday: 1974-09-27
# profession: guy with keyboard
#
# So this YAML file includes two fixtures. T
#
# Now when we call <tt>@developers = Fixtures.create_fixtures(".", "developers")</tt> both developers will get inserted into
# the "developers" table through the active Active Record connection (that must be setup before-hand). And we can now query
# the fixture data through the <tt>@developers</tt> hash, so <tt>@developers["david"]["name"]</tt> will return
# <tt>"David Heinemeier Hansson"</tt> and <tt>@developers["david"]["birthday"]</tt> will return <tt>Date.new(1979, 10, 15)</tt>.
#
# In addition to getting the raw data, we can also get the Developer object by doing @developers["david"].find. This can then
# be used for comparison in a unit test. Something like:
#
# def test_find
# assert_equal @developers["david"]["name"], @developers["david"].find.name
# end
#
# Comparing that the data we have on the name is also what the object returns when we ask for it.
#
# == Automatic fixture setup and instance variable availability
#
# Fixtures can also be automatically instantiated in instance variables relating to their names using the following style:
#
# class FixturesTest < Test::Unit::TestCase
# fixtures :developers # you can add more with comma separation
#
# def test_developers
# assert_equal 3, @developers.size # the container for all the fixtures is automatically set
# assert_kind_of Developer, @david # works like @developers["david"].find
# assert_equal "David Heinemeier Hansson", @david.name
# end
# end
class Fixtures < Hash
def self.instantiate_fixtures(object, fixtures_directory, *table_names)
[ create_fixtures(fixtures_directory, *table_names) ].flatten.each_with_index do |fixtures, idx|
object.instance_variable_set "@#{table_names[idx]}", fixtures
fixtures.each { |name, fixture| object.instance_variable_set "@#{name}", fixture.find }
end
end
def self.create_fixtures(fixtures_directory, *table_names)
connection = block_given? ? yield : ActiveRecord::Base.connection
old_logger_level = ActiveRecord::Base.logger.level
begin
ActiveRecord::Base.logger.level = Logger::ERROR
fixtures = []
connection.transaction do
fixtures = table_names.flatten.map do |table_name|
Fixtures.new(connection, table_name.to_s, File.join(fixtures_directory, table_name.to_s))
end
fixtures.reverse.each{ |fixture| fixture.delete_existing_fixtures }
fixtures.each{ |fixture| fixture.insert_fixtures }
end
return fixtures.size > 1 ? fixtures : fixtures.first
ensure
ActiveRecord::Base.logger.level = old_logger_level
end
end
def initialize(connection, table_name, fixture_path, file_filter = /^\.|CVS|\.yml/)
@connection, @table_name, @fixture_path, @file_filter = connection, table_name, fixture_path, file_filter
@class_name = Inflector.classify(@table_name)
read_fixture_files
end
def delete_existing_fixtures
@connection.delete "DELETE FROM #{@table_name}"
end
def insert_fixtures
values.each do |fixture|
@connection.execute "INSERT INTO #{@table_name} (#{fixture.key_list}) VALUES(#{fixture.value_list})"
end
end
private
def read_fixture_files
if File.exists?(yaml_file_path)
YAML::load(erb_render(IO.read(yaml_file_path))).each do |name, data|
self[name] = Fixture.new(data, @class_name)
end
else
Dir.entries(@fixture_path).each do |file|
self[file] = Fixture.new(File.join(@fixture_path, file), @class_name) unless file =~ @file_filter
end
end
end
def yaml_file_path
@fixture_path + ".yml"
end
def yaml_fixtures_key(path)
File.basename(@fixture_path).split(".").first
end
def erb_render(fixture_content)
ERB.new(fixture_content).result
end
end
class Fixture #:nodoc:
include Enumerable
class FixtureError < StandardError; end
class FormatError < FixtureError; end
def initialize(fixture, class_name)
@fixture = fixture.is_a?(Hash) ? fixture : read_fixture_file(fixture)
@class_name = class_name
end
def each
@fixture.each { |item| yield item }
end
def [](key)
@fixture[key]
end
def to_hash
@fixture
end
def key_list
@fixture.keys.join(", ")
end
def value_list
@fixture.values.map { |v| ActiveRecord::Base.connection.quote(v).gsub('\\n', "\n").gsub('\\r', "\r") }.join(", ")
end
def find
Object.const_get(@class_name).find(self["id"])
end
private
def read_fixture_file(fixture_file_path)
IO.readlines(fixture_file_path).inject({}) do |fixture, line|
# Mercifully skip empty lines.
next if line.empty?
# Use the same regular expression for attributes as Active Record.
unless md = /^\s*([a-zA-Z][-_\w]*)\s*=>\s*(.+)\s*$/.match(line)
raise FormatError, "#{path}: fixture format error at '#{line}'. Expecting 'key => value'."
end
key, value = md.captures
# Disallow duplicate keys to catch typos.
raise FormatError, "#{path}: duplicate '#{key}' in fixture." if fixture[key]
fixture[key] = value.strip
fixture
end
end
end
class Test::Unit::TestCase #:nodoc:
include ClassInheritableAttributes
cattr_accessor :fixture_path
cattr_accessor :fixture_table_names
def self.fixtures(*table_names)
write_inheritable_attribute("fixture_table_names", table_names)
end
def setup
instantiate_fixtures(*fixture_table_names) if fixture_table_names
end
def self.method_added(method_symbol)
if method_symbol == :setup && !method_defined?(:setup_without_fixtures)
alias_method :setup_without_fixtures, :setup
define_method(:setup) do
instantiate_fixtures(*fixture_table_names) if fixture_table_names
setup_without_fixtures
end
end
end
private
def instantiate_fixtures(*table_names)
Fixtures.instantiate_fixtures(self, fixture_path, *table_names)
end
def fixture_table_names
self.class.read_inheritable_attribute("fixture_table_names")
end
end
|