class FixtureDependencies

Attributes

class_map[RW]
fixture_path[RW]
fixtures[R]
loaded[R]
verbose[RW]

Public Class Methods

build(record, attributes = {}) click to toggle source

Loads the attribute for a single record, merging optional attributes.

   # File lib/fixture_dependencies.rb
45 def self.build(record, attributes = {})
46   obj = FixtureDependencies.load_attributes([record])
47 
48   attributes.each { |key, value| obj.send("#{key}=", value) }
49 
50   obj
51 end
load(*records) click to toggle source

Load all record arguments into the database. If a single argument is given and it corresponds to a single fixture, return the the model instance corresponding to that fixture. If a single argument if given and it corresponds to a model, return all model instances corresponding to that model. If multiple arguments are given, return a list of model instances (for single fixture arguments) or list of model instances (for model fixture arguments). If no arguments, return the empty list. If any of the arguments is a hash, assume the key specifies the model and the values specify the fixture, and treat it as though individual symbols specifying both model and fixture were given.

Examples:

  • load(:posts) # All post fixtures, not recommended

  • load(:posts, :comments) # All post and comment fixtures, again not recommended

  • load(:post__post1) # Just the post fixture named post1

  • load(:post__post1, :post__post2) # Post fixtures named post1 and post2

  • load(:posts=>[:post1, :post2]) # Post fixtures named post1 and post2

  • load(:post__post1, :comment__comment2) # Post fixture named post1 and comment fixture named comment2

  • load({:posts=>[:post1, :post2]}, :comment__comment2) # Post fixtures named post1 and post2 and comment fixture named comment2

This will load the data from the yaml files for each argument whose model is not already in the fixture hash.

   # File lib/fixture_dependencies.rb
33 def self.load(*records)
34   load_with_options(records)
35 end
load_attributes(*records) click to toggle source

Load the attributes for the record arguments. This method responds to the same interface as 'load', the difference being that has_many associations are not loaded.

   # File lib/fixture_dependencies.rb
40 def self.load_attributes(*records)
41   load_with_options(records, :attributes_only=>true)
42 end
load_with_options(records, opts = {}) click to toggle source
   # File lib/fixture_dependencies.rb
53 def self.load_with_options(records, opts = {})
54   ret = records.map do |record|
55     if record.is_a?(Hash)
56       record.map do |k, vals|
57         model = k.to_s.singularize
58         vals.map{|v| :"#{model}__#{v}"}
59       end
60     else
61       record
62     end
63   end.flatten.compact.map do |record| 
64     model_name, name = split_name(record)
65     if name
66       use(record.to_sym, opts)
67     else
68       model_name = model_name.singularize
69       unless loaded[model_name.to_sym]
70         puts "loading #{model_name}.yml" if verbose > 0
71         load_yaml(model_name) 
72       end
73       fixtures[model_name.to_sym].keys.map{|name| use(:"#{model_name}__#{name}", opts)}
74     end
75   end
76   ret.length == 1 ? ret[0] : ret
77 end

Private Class Methods

add(model_name, name, attributes) click to toggle source

Add a fixture to the fixture hash (does not add to the database, just makes it available to be add to the database via use).

   # File lib/fixture_dependencies.rb
92 def add(model_name, name, attributes)
93   (fixtures[model_name.to_sym]||={})[name.to_sym] = attributes
94 end
add_associated_object_AR(reflection, attr, object, assoc) click to toggle source
   # File lib/fixture_dependencies/active_record.rb
 4 def add_associated_object_AR(reflection, attr, object, assoc)
 5   if reflection.macro == :has_one
 6     object.send("#{attr}=", assoc)
 7   elsif !object.send(attr).include?(assoc)
 8     object.send(attr) << assoc
 9   end
10 end
add_associated_object_S(reflection, attr, object, assoc) click to toggle source
   # File lib/fixture_dependencies/sequel.rb
 4 def add_associated_object_S(reflection, attr, object, assoc)
 5   if reflection[:type] == :one_to_one
 6     object.send("#{reflection[:name]}=", assoc)
 7   else
 8     object.send("add_#{attr.to_s.singularize}", assoc) unless object.send(attr).include?(assoc)
 9   end
10 end
fixture_pk(model) click to toggle source
    # File lib/fixture_dependencies.rb
295 def fixture_pk(model)
296   case pk = model.primary_key
297   when Symbol, Array
298     pk
299   else
300     pk.to_sym
301   end
302 end
fixture_pkv(attributes, fpk) click to toggle source
    # File lib/fixture_dependencies.rb
304 def fixture_pkv(attributes, fpk)
305   case fpk
306   when Symbol
307     attributes[fpk]
308   else
309     fpk.map{|v| attributes[v]}
310   end
311 end
get(record) click to toggle source

Get the model instance that already exists in the database using the fixture name.

    # File lib/fixture_dependencies.rb
 98 def get(record)
 99   model_name, name = split_name(record)
100   model = model_class(model_name)
101   model_method(:model_find, model_type(model), model, fixtures[model_name.to_sym][name.to_sym][fixture_pk(model)])
102 end
load_yaml(model_name) click to toggle source

Adds all fixtures in the yaml fixture file for the model to the fixtures hash (does not add them to the database, see add).

    # File lib/fixture_dependencies.rb
106 def load_yaml(model_name)
107   raise(ArgumentError, "No fixture_path set. Use FixtureDependencies.fixture_path = ...") unless fixture_path
108 
109   klass = model_class(model_name)
110   filename = klass.send(klass.respond_to?(:fixture_filename) ? :fixture_filename : :table_name)
111   yaml_path = File.join(fixture_path, "#{filename}.yml")
112 
113   if File.exist?(yaml_path)
114     yaml = YAML.load(File.read(yaml_path))
115   elsif File.exist?("#{yaml_path}.erb")
116     yaml = YAML.load(ERB.new(File.read("#{yaml_path}.erb")).result)
117   else
118     raise(ArgumentError, "No valid fixture found at #{yaml_path}[.erb]")
119   end
120 
121   yaml.each do |name, attributes|
122     symbol_attrs = {}
123     attributes.each{|k,v| symbol_attrs[k.to_sym] = v}
124     add(model_name.to_sym, name, symbol_attrs)
125   end
126   loaded[model_name.to_sym] = true
127 end
model_class(model_name) click to toggle source

Return the class associated with the given model_name. By default, the class name is automagically derived from the model name, however this can be overridden by FixtureDependencies.class_map[:name] = Some::Class.

    # File lib/fixture_dependencies.rb
150 def model_class(model_name)
151   class_map[model_name.to_sym] || model_name.camelize.constantize
152 end
model_find_AR(model, pk) click to toggle source
   # File lib/fixture_dependencies/active_record.rb
12 def model_find_AR(model, pk)
13   model.find(pk)
14 end
model_find_S(model, pk) click to toggle source
   # File lib/fixture_dependencies/sequel.rb
12 def model_find_S(model, pk)
13   model[pk] || raise_model_error_S("No matching record for #{model.name}[#{pk.inspect}]")
14 end
model_find_by_pk_AR(model, pk) click to toggle source
   # File lib/fixture_dependencies/active_record.rb
16 def model_find_by_pk_AR(model, pk)
17   model.send("find_by_#{model.primary_key}", pk)
18 end
model_find_by_pk_S(model, pk) click to toggle source
   # File lib/fixture_dependencies/sequel.rb
16 def model_find_by_pk_S(model, pk)
17   model[pk]
18 end
model_method(meth, mtype, *args, &block) click to toggle source

Delegate to the correct method based on mtype

    # File lib/fixture_dependencies.rb
130 def model_method(meth, mtype, *args, &block)
131   send("#{meth}_#{mtype}", *args, &block)
132 end
model_save_AR(object) click to toggle source
   # File lib/fixture_dependencies/active_record.rb
20 def model_save_AR(object)
21   object.save || raise(ActiveRecord::ActiveRecordError)
22 end
model_save_S(object) click to toggle source
   # File lib/fixture_dependencies/sequel.rb
20 def model_save_S(object)
21   object.raise_on_save_failure = true
22   object.save
23 end
model_type(model) click to toggle source

A symbol representing the base class of the model, currently ActiveRecord::Base and Sequel::Model are supported.

    # File lib/fixture_dependencies.rb
136 def model_type(model)
137   if model.ancestors.map(&:to_s).include?('ActiveRecord::Base')
138     :AR
139   elsif model.ancestors.map(&:to_s).include?('Sequel::Model')
140     :S
141   else
142     raise TypeError, 'not ActiveRecord or Sequel model'
143   end
144 end
polymorphic_association(value) click to toggle source

Extract association id and association_class Example: addressable: john (Account)

=> ["john", "Account"]
    # File lib/fixture_dependencies.rb
323 def polymorphic_association(value)
324   value.to_s.scan(/(.*)\s\((.*)\)/).flatten
325 end
polymorphic_association?(value) click to toggle source

Polymorphic when value has the class indication Example: john (Account)

=> true
    # File lib/fixture_dependencies.rb
316 def polymorphic_association?(value)
317   polymorphic_association(value).size == 2
318 end
raise_model_error_AR(message) click to toggle source
   # File lib/fixture_dependencies/active_record.rb
24 def raise_model_error_AR(message)
25   raise ActiveRecord::RecordNotFound, message
26 end
raise_model_error_S(message) click to toggle source
   # File lib/fixture_dependencies/sequel.rb
25 def raise_model_error_S(message)
26   raise Sequel::Error, message
27 end
reflection_AR(model, attr) click to toggle source
   # File lib/fixture_dependencies/active_record.rb
28 def reflection_AR(model, attr)
29   model.reflect_on_association(attr)
30 end
reflection_S(model, attr) click to toggle source
   # File lib/fixture_dependencies/sequel.rb
29 def reflection_S(model, attr)
30   model.association_reflection(attr)
31 end
reflection_class_AR(reflection) click to toggle source
   # File lib/fixture_dependencies/active_record.rb
32 def reflection_class_AR(reflection)
33   reflection.klass
34 end
reflection_class_S(reflection) click to toggle source
   # File lib/fixture_dependencies/sequel.rb
33 def reflection_class_S(reflection)
34   reflection.associated_class
35 end
reflection_key_AR(reflection) click to toggle source
   # File lib/fixture_dependencies/active_record.rb
36 def reflection_key_AR(reflection)
37   reflection.options[:foreign_key] || reflection.klass.table_name.classify.foreign_key
38 end
reflection_key_S(reflection) click to toggle source
   # File lib/fixture_dependencies/sequel.rb
37 def reflection_key_S(reflection)
38   reflection[:key]
39 end
reflection_type_AR(reflection) click to toggle source
   # File lib/fixture_dependencies/active_record.rb
40 def reflection_type_AR(reflection)
41   reflection.macro
42 end
reflection_type_S(reflection) click to toggle source
   # File lib/fixture_dependencies/sequel.rb
41 def reflection_type_S(reflection)
42   reflection[:type]
43 end
split_name(name) click to toggle source

Split the fixture name into the name of the model and the name of the individual fixture.

    # File lib/fixture_dependencies.rb
156 def split_name(name)
157   name.to_s.split('__', 2)
158 end
use(record, opts = {}, loading = [], procs = {}) click to toggle source

Load the individual fixture into the database, by loading all necessary belongs_to dependencies before saving the model, and all has_* dependencies after saving the model. If the model already exists in the database, return it. Will check the yaml file for fixtures if no fixtures yet exist for the model. If the fixture isn't in the fixture hash, raise an error.

    # File lib/fixture_dependencies.rb
166 def use(record, opts = {}, loading = [], procs = {})
167   spaces = " " * loading.length
168   puts "#{spaces}using #{record}" if verbose > 0
169   puts "#{spaces}load stack:#{loading.inspect}" if verbose > 1
170   loading.push(record)
171   model_name, name = split_name(record)
172   model = model_class(model_name)
173   unless loaded[model_name.to_sym]
174     puts "#{spaces}loading #{model.table_name}.yml" if verbose > 0
175     load_yaml(model_name)
176   end
177   mtype = model_type(model)
178   model_method(:raise_model_error, mtype, "Couldn't use fixture #{record.inspect}") unless attributes = fixtures[model_name.to_sym][name.to_sym]
179   fpk = fixture_pk(model)
180   cpk = fpk.is_a?(Array)
181   # return if object has already been loaded into the database
182   if existing_obj = model_method(:model_find_by_pk, mtype, model, fixture_pkv(attributes,fpk))
183     puts "#{spaces}using #{record}: already in database (pk: #{fixture_pkv(attributes,fpk).inspect})" if verbose > 2
184     loading.pop
185     return existing_obj
186   end
187   if model.respond_to?(:sti_load)
188     obj = model.sti_load(model.sti_key => attributes[model.sti_key])
189     obj.send(:initialize)
190     model = obj.model
191   elsif model.respond_to?(:sti_key)
192     obj = attributes[model.sti_key].to_s.camelize.constantize.new
193   elsif model.respond_to?(:cti_key) # support for Sequel's pre-4.24.0 hybrid CTI support
194     mv = attributes[model.cti_key]
195     if (mm = model.cti_model_map)
196       model = mm[mv].to_s.constantize
197     elsif !mv.nil?
198       model = mv.constantize
199     end
200     obj = model.new
201   else
202     obj = model.new
203   end
204   puts "#{spaces}#{model} STI plugin detected, initializing instance of #{obj}" if (verbose > 1 && model.respond_to?(:sti_dataset))
205   many_associations = []
206   attributes.each do |attr, value|
207     next if attr.is_a?(Array)
208     if reflection = model_method(:reflection, mtype, model, attr.to_sym)
209       if [:belongs_to, :many_to_one].include?(model_method(:reflection_type, mtype, reflection))
210 
211         if polymorphic_association?(value)
212           value, polymorphic_class = polymorphic_association(value)
213           reflection[:class_name] = polymorphic_class
214           dep_name = "#{polymorphic_class.to_s.underscore}__#{value}".to_sym
215         else
216           dep_name = "#{model_method(:reflection_class, mtype, reflection).name.underscore}__#{value}".to_sym
217         end
218 
219         if dep_name == record
220           # Self referential record, use primary key
221           puts "#{spaces}#{record}.#{attr}: belongs_to self-referential" if verbose > 1
222           attr = model_method(:reflection_key, mtype, reflection)
223           value = fixture_pkv(attributes,fpk)
224           if cpk
225             attr.zip(value).each do |k, v|
226               puts "#{spaces}#{record}.#{k} = #{v.inspect}" if verbose > 2
227               obj.send("#{k}=", v)
228             end
229             next
230           end
231         elsif loading.include?(dep_name)
232           # Association cycle detected, set foreign key for this model afterward using procs
233           # This is will fail if the column is set to not null or validates_presence_of
234           puts "#{spaces}#{record}.#{attr}: belongs-to cycle detected:#{dep_name}" if verbose > 1
235           (procs[dep_name] ||= []) << Proc.new do |assoc|
236             m = model_method(:model_find, mtype, model, fixture_pkv(attributes,fpk))
237             m.send("#{attr}=", assoc)
238             model_method(:model_save, mtype, m)
239           end
240           value = nil
241         else
242           # Regular assocation, load it
243           puts "#{spaces}#{record}.#{attr}: belongs_to:#{dep_name}" if verbose > 1
244           use(dep_name, {}, loading, procs)
245           value = get(dep_name)
246         end
247       else
248         many_associations << [attr, reflection, value]
249         next
250       end
251     end
252     puts "#{spaces}#{record}.#{attr} = #{value.inspect}" if verbose > 2
253     obj.send("#{attr}=", value)
254   end
255 
256   return obj if opts[:attributes_only]
257 
258   puts "#{spaces}saving #{record}" if verbose > 1
259 
260   model_method(:model_save, mtype, obj)
261   # after saving the model, we set the primary key within the fixture hash, in case it was not explicitly specified in the fixture and was generated by an auto_increment / serial field
262   fixtures[model_name.to_sym][name.to_sym][fpk] ||= fixture_pkv(obj,fpk)
263 
264   loading.pop
265   # Update the circular references
266   if procs[record]
267     procs[record].each{|p| p.call(obj)} 
268     procs.delete(record)
269   end
270   # Update the has_many and habtm associations
271   many_associations.each do |attr, reflection, values|
272     Array(values).each do |value|
273       dep_name = "#{model_method(:reflection_class, mtype, reflection).name.underscore}__#{value}".to_sym
274       rtype = model_method(:reflection_type, mtype, reflection) if verbose > 1
275       if dep_name == record
276         # Self referential, add association
277         puts "#{spaces}#{record}.#{attr}: #{rtype} self-referential" if verbose > 1
278         model_method(:add_associated_object, mtype, reflection, attr, obj, obj)
279       elsif loading.include?(dep_name)
280         # Cycle Detected, add association to this object after saving other object
281         puts "#{spaces}#{record}.#{attr}: #{rtype} cycle detected:#{dep_name}" if verbose > 1
282         (procs[dep_name] ||= []) << Proc.new do |assoc|
283           model_method(:add_associated_object, mtype, reflection, attr, obj, assoc)
284         end
285       else
286         # Regular association, add it
287         puts "#{spaces}#{record}.#{attr}: #{rtype}:#{dep_name}" if verbose > 1
288         model_method(:add_associated_object, mtype, reflection, attr, obj, use(dep_name, {}, loading, procs))
289       end
290     end
291   end
292   obj
293 end