]> projects.mako.cc - selectricity/blob - vendor/plugins/engines/lib/engines/testing_extensions.rb
6d9755d7a30c8439168228d0f34cd318b6273465
[selectricity] / vendor / plugins / engines / lib / engines / testing_extensions.rb
1 # The Engines testing extensions enable developers to load fixtures into specific
2 # tables irrespective of the name of the fixtures file. This work is heavily based on
3 # patches made by Duane Johnson (canadaduane), viewable at
4 # http://dev.rubyonrails.org/ticket/1911
5 #
6 # Engine developers should supply fixture files in the <engine>/test/fixtures directory
7 # as normal. Within their tests, they should load the fixtures using the 'fixture' command
8 # (rather than the normal 'fixtures' command). For example:
9 #
10 #   class UserTest < Test::Unit::TestCase
11 #     fixture :users, :table_name => LoginEngine.config(:user_table), :class_name => "User"
12 #    
13 #     ...
14 #
15 # This will ensure that the fixtures/users.yml file will get loaded into the correct
16 # table, and will use the correct model object class.
17
18
19
20 # A FixtureGroup is a set of fixtures identified by a name.  Normally, this is the name of the
21 # corresponding fixture filename.  For example, when you declare the use of fixtures in a
22 # TestUnit class, like so:
23 #   fixtures :users
24 # you are creating a FixtureGroup whose name is 'users', and whose defaults are set such that the
25 # +class_name+, +file_name+ and +table_name+ are guessed from the FixtureGroup's name.
26 class FixtureGroup
27   attr_accessor :table_name, :class_name, :connection
28   attr_reader :group_name, :file_name
29
30   def initialize(file_name, optional_names = {})
31     self.file_name = file_name
32     self.group_name = optional_names[:group_name] || file_name
33     if optional_names[:table_name]
34       self.table_name = optional_names[:table_name]
35       self.class_name = optional_names[:class_name] || Inflector.classify(@table_name.to_s.gsub('.','_'))
36     elsif optional_names[:class_name]
37       self.class_name = optional_names[:class_name]
38       if Object.const_defined?(@class_name)
39         model_class = Object.const_get(@class_name)
40         self.table_name = ActiveRecord::Base.table_name_prefix + model_class.table_name + ActiveRecord::Base.table_name_suffix
41       end
42     end
43
44     # In case either :table_name or :class_name was not set:
45     self.table_name ||= ActiveRecord::Base.table_name_prefix + @group_name.to_s + ActiveRecord::Base.table_name_suffix
46     self.class_name ||= Inflector.classify(@table_name.to_s.gsub('.','_'))
47   end
48
49   def file_name=(name)
50     @file_name = name.to_s
51   end
52   
53   def group_name=(name)
54     @group_name = name.to_sym
55   end
56
57   def class_file_name
58     Inflector.underscore(@class_name)
59   end
60   
61   # Instantiate an array of FixtureGroup objects from an array of strings (table_names)
62   def self.array_from_names(names)
63     names.collect { |n| FixtureGroup.new(n) }
64   end
65   
66   def hash
67     @group_name.hash
68   end
69   
70   def eql?(other)
71     @group_name.eql? other.group_name
72   end
73 end
74
75 class Fixtures < YAML::Omap
76
77   def self.instantiate_fixtures(object, fixture_group_name, fixtures, load_instances=true)
78     old_logger_level = ActiveRecord::Base.logger.level
79     ActiveRecord::Base.logger.level = Logger::ERROR
80
81     # table_name.to_s.gsub('.','_') replaced by 'fixture_group_name'
82     object.instance_variable_set "@#{fixture_group_name}", fixtures
83     if load_instances
84       ActiveRecord::Base.silence do
85         fixtures.each do |name, fixture|
86           begin
87             if model = fixture.find
88               object.instance_variable_set "@#{name}", model
89             end
90           rescue FixtureClassNotFound
91             # Let's hope the developer has included it himself
92           end
93         end
94       end
95     end
96
97     ActiveRecord::Base.logger.level = old_logger_level
98   end
99
100   # this doesn't really need to be overridden...
101   def self.instantiate_all_loaded_fixtures(object, load_instances=true)
102     all_loaded_fixtures.each do |fixture_group_name, fixtures|
103       Fixtures.instantiate_fixtures(object, fixture_group_name, fixtures, load_instances)
104     end
105   end
106
107   def self.create_fixtures(fixtures_directory, *fixture_groups)
108     connection = block_given? ? yield : ActiveRecord::Base.connection
109     fixture_groups.flatten!
110     
111     # Backwards compatibility: Allow an array of table names to be passed in, but just use them
112     # to create an array of FixtureGroup objects
113     if not fixture_groups.empty? and fixture_groups.first.is_a?(String)
114       fixture_groups = FixtureGroup.array_from_names(fixture_groups)
115     end
116
117     ActiveRecord::Base.silence do
118       fixtures_map = {}
119       fixtures = fixture_groups.map do |group|
120         fixtures_map[group.group_name] = Fixtures.new(connection, fixtures_directory, group)
121       end 
122       # Make sure all refs to all_loaded_fixtures use group_name as hash index, not table_name
123       all_loaded_fixtures.merge! fixtures_map 
124
125       connection.transaction do
126         fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures }
127         fixtures.each { |fixture| fixture.insert_fixtures }
128         
129         # Cap primary key sequences to max(pk).
130         if connection.respond_to?(:reset_pk_sequence!)
131           fixture_groups.each do |fg|
132             connection.reset_pk_sequence!(fg.table_name)
133           end
134         end
135       end
136
137       return fixtures.size > 1 ? fixtures : fixtures.first
138     end
139   end
140
141  
142   attr_accessor :connection, :fixtures_directory, :file_filter
143   attr_accessor :fixture_group
144  
145   def initialize(connection, fixtures_directory, fixture_group, file_filter = DEFAULT_FILTER_RE)
146     @connection, @fixtures_directory = connection, fixtures_directory
147     @fixture_group = fixture_group
148     @file_filter = file_filter
149     read_fixture_files
150   end
151  
152   def delete_existing_fixtures
153     @connection.delete "DELETE FROM #{@fixture_group.table_name}", 'Fixture Delete'
154   end
155  
156   def insert_fixtures
157     values.each do |fixture|
158       @connection.execute "INSERT INTO #{@fixture_group.table_name} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert'
159     end
160   end
161  
162   private
163     def read_fixture_files
164       if File.file?(yaml_file_path)
165         read_yaml_fixture_files
166       elsif File.file?(csv_file_path)
167         read_csv_fixture_files
168       elsif File.file?(deprecated_yaml_file_path)
169         raise Fixture::FormatError, ".yml extension required: rename #{deprecated_yaml_file_path} to #{yaml_file_path}"
170       elsif File.directory?(single_file_fixtures_path)
171         read_standard_fixture_files
172       else
173         raise Fixture::FixtureError, "Couldn't find a yaml, csv or standard file to load at #{@fixtures_directory} (#{@fixture_group.file_name})."
174       end
175     end
176
177     def read_yaml_fixture_files
178       # YAML fixtures
179       begin
180         if yaml = YAML::load(erb_render(IO.read(yaml_file_path)))
181           yaml = yaml.value if yaml.respond_to?(:type_id) and yaml.respond_to?(:value)
182           yaml.each do |name, data|
183             self[name] = Fixture.new(data, fixture_group.class_name)
184           end
185         end
186       rescue Exception=>boom
187         raise Fixture::FormatError, "a YAML error occured parsing #{yaml_file_path}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n  #{boom.class}: #{boom}"
188       end      
189     end
190
191     def read_csv_fixture_files
192       # CSV fixtures
193       reader = CSV::Reader.create(erb_render(IO.read(csv_file_path)))
194       header = reader.shift
195       i = 0
196       reader.each do |row|
197         data = {}
198         row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip }
199         self["#{@fixture_group.class_file_name}_#{i+=1}"]= Fixture.new(data, @fixture_group.class_name)
200       end
201     end
202
203     def read_standard_fixture_files
204       # Standard fixtures
205       path = File.join(@fixtures_directory, @fixture_group.file_name)
206       Dir.entries(path).each do |file|
207         path = File.join(@fixtures_directory, @fixture_group.file_name, file)
208         if File.file?(path) and file !~ @file_filter
209           self[file] = Fixture.new(path, @fixture_group.class_name)
210         end
211       end
212     end
213  
214     def yaml_file_path
215       fixture_path_with_extension ".yml"
216     end
217  
218     def deprecated_yaml_file_path
219       fixture_path_with_extension ".yaml"
220     end
221  
222     def csv_file_path
223       fixture_path_with_extension ".csv"
224     end
225     
226     def single_file_fixtures_path
227       fixture_path_with_extension ""
228     end
229  
230     def fixture_path_with_extension(ext)
231       File.join(@fixtures_directory, @fixture_group.file_name + ext)
232     end 
233
234     def erb_render(fixture_content)
235       ERB.new(fixture_content).result
236     end
237
238 end
239
240 module Test #:nodoc:
241   module Unit #:nodoc:
242     class TestCase #:nodoc:
243       cattr_accessor :fixtures_directory
244       class_inheritable_accessor :fixture_groups
245       class_inheritable_accessor :fixture_table_names
246       class_inheritable_accessor :use_transactional_fixtures
247       class_inheritable_accessor :use_instantiated_fixtures   # true, false, or :no_instances
248       class_inheritable_accessor :pre_loaded_fixtures
249
250       self.fixture_groups = []
251       self.use_transactional_fixtures = false
252       self.use_instantiated_fixtures = true
253       self.pre_loaded_fixtures = false
254
255       @@already_loaded_fixtures = {}
256
257       # Backwards compatibility
258       def self.fixture_path=(path); self.fixtures_directory = path; end
259       def self.fixture_path; self.fixtures_directory; end
260       def fixture_group_names; fixture_groups.collect { |g| g.group_name }; end
261       def fixture_table_names; fixture_group_names; end
262
263       def self.fixture(file_name, options = {})
264         self.fixture_groups |= [FixtureGroup.new(file_name, options)]
265         require_fixture_classes
266         setup_fixture_accessors
267       end
268
269       def self.fixtures(*file_names)
270         self.fixture_groups |= FixtureGroup.array_from_names(file_names.flatten)
271         require_fixture_classes
272         setup_fixture_accessors
273       end
274
275       def self.require_fixture_classes(fixture_groups_override = nil)
276         (fixture_groups_override || fixture_groups).each do |group| 
277           begin
278             require group.class_file_name
279           rescue LoadError
280             # Let's hope the developer has included it himself
281           end
282         end
283       end
284
285       def self.setup_fixture_accessors(fixture_groups_override=nil)
286         (fixture_groups_override || fixture_groups).each do |group|
287           define_method(group.group_name) do |fixture, *optionals|
288             force_reload = optionals.shift
289             @fixture_cache[group.group_name] ||= Hash.new
290             @fixture_cache[group.group_name][fixture] = nil if force_reload
291             @fixture_cache[group.group_name][fixture] ||= @loaded_fixtures[group.group_name][fixture.to_s].find
292           end
293         end
294       end
295
296       private
297         def load_fixtures
298           @loaded_fixtures = {}
299           fixtures = Fixtures.create_fixtures(fixtures_directory, fixture_groups)
300           unless fixtures.nil?
301             if fixtures.instance_of?(Fixtures)
302               @loaded_fixtures[fixtures.fixture_group.group_name] = fixtures
303             else
304               fixtures.each { |f| @loaded_fixtures[f.fixture_group.group_name] = f }
305             end
306           end
307         end
308
309         def instantiate_fixtures
310           if pre_loaded_fixtures
311             raise RuntimeError, 'Load fixtures before instantiating them.' if Fixtures.all_loaded_fixtures.empty?
312             unless @@required_fixture_classes
313               groups = Fixtures.all_loaded_fixtures.values.collect { |f| f.group_name }
314               self.class.require_fixture_classes groups
315               @@required_fixture_classes = true
316             end
317             Fixtures.instantiate_all_loaded_fixtures(self, load_instances?)
318           else
319             raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil?
320             @loaded_fixtures.each do |fixture_group_name, fixtures|
321               Fixtures.instantiate_fixtures(self, fixture_group_name, fixtures, load_instances?)
322             end
323           end
324         end
325     end
326   end
327 end

Benjamin Mako Hill || Want to submit a patch?