class FixtureDependencies
Attributes
Public Class Methods
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 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 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
# 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 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
# 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
# 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
# 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
# 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 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
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
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
# File lib/fixture_dependencies/active_record.rb 12 def model_find_AR(model, pk) 13 model.find(pk) 14 end
# 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
# 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
# File lib/fixture_dependencies/sequel.rb 16 def model_find_by_pk_S(model, pk) 17 model[pk] 18 end
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
# File lib/fixture_dependencies/active_record.rb 20 def model_save_AR(object) 21 object.save || raise(ActiveRecord::ActiveRecordError) 22 end
# File lib/fixture_dependencies/sequel.rb 20 def model_save_S(object) 21 object.raise_on_save_failure = true 22 object.save 23 end
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
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 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
# File lib/fixture_dependencies/active_record.rb 24 def raise_model_error_AR(message) 25 raise ActiveRecord::RecordNotFound, message 26 end
# File lib/fixture_dependencies/sequel.rb 25 def raise_model_error_S(message) 26 raise Sequel::Error, message 27 end
# File lib/fixture_dependencies/active_record.rb 28 def reflection_AR(model, attr) 29 model.reflect_on_association(attr) 30 end
# File lib/fixture_dependencies/sequel.rb 29 def reflection_S(model, attr) 30 model.association_reflection(attr) 31 end
# File lib/fixture_dependencies/active_record.rb 32 def reflection_class_AR(reflection) 33 reflection.klass 34 end
# File lib/fixture_dependencies/sequel.rb 33 def reflection_class_S(reflection) 34 reflection.associated_class 35 end
# 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
# File lib/fixture_dependencies/sequel.rb 37 def reflection_key_S(reflection) 38 reflection[:key] 39 end
# File lib/fixture_dependencies/active_record.rb 40 def reflection_type_AR(reflection) 41 reflection.macro 42 end
# File lib/fixture_dependencies/sequel.rb 41 def reflection_type_S(reflection) 42 reflection[:type] 43 end
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
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