I'm lazy and I since my production database has data I could use for testing through on going development, I was wondering if there were any easy methods of generating fixtures.
Examples of fixtures include loading a database with a specific known set of data, erasing a hard disk and installing a known clean operating system installation, copying a specific known set of files, or the preparation of input data as well as set-up and creation of mock objects.
The question is old, but as it still seems relevant: yes, there is an easy way to create fixtures from your development database:
class ActiveRecord::Base
def dump_fixture
fixture_file = "#{Rails.root}/test/fixtures/#{self.class.table_name}.yml"
File.open(fixture_file, "a+") do |f|
f.puts({ "#{self.class.table_name.singularize}_#{id}" => attributes }.
to_yaml.sub!(/---\s?/, "\n"))
end
end
end
Place this in a file in config/initializers
- now you can dump any ActiveRecord object in your Rails console and it will automatically be appended at the end of it's respective fixture file:
User.first.dump_fixture
appends fixture data to test/fixtures/users.yml
.
In case you're creating a script to run under rails runner
you can use the following approach:
File.open("#{Rails.root}/spec/fixtures/documents.yml", 'w') do |file|
file.write Document.all.to_a.map(&:attributes).to_yaml
end
you can create as much blocks as you want, or if you want to go to the full database you can try:
models = defined?(AppicationRecord) ? ApplicationRecord.decendants : ActiveRecord::Base.descendants
models.each do |model|
model_name = model.name.pluralize.underscore
File.open("#{Rails.root}/spec/fixtures/#{model_name}.yml", 'w') do |file|
file.write model.all.to_a.map(&:attributes).to_yaml
end
end
if you do not want the timestamps you can change the code to:
model.all.to_a.map { |m| m.attributes.except('created_at', 'updated_at')}.to_yaml
Building on @nikolasgd's answer I wrote a Rake Task, which may be useful for some:
# Use like "rake custom:create_test_fixtures[model]"
namespace :custom do
desc 'Re-Creates Fixtures for Testing for a Model'
task :create_test_fixtures, [:model] => [:environment] do |t, args|
class ActiveRecord::Base
def dump_fixture
fixture_file = "#{Rails.root}/test/fixtures/#{self.class.table_name}.yml"
File.open(fixture_file, "a+") do |f|
f.puts({"#{self.class.table_name.singularize}_#{id}" => attributes}.
to_yaml.sub!(/---\s?/, "\n"))
end
end
end
begin
model = args[:model].constantize
model.all.map(&:dump_fixture)
puts "OK: Created Fixture for Model \"#{args[:model]}\"."
rescue NameError
puts "ERROR: Model \"#{args[:model]}\" not found."
end
end
end
I expanded the solution by @nikolasgd to also support ActiveStorage attachment fields/blobs and support naming dumped objects:
# config/initializers/generate_fixture.rb
class ActiveRecord::Base
# Append this record to the fixture.yml for this record class
def dump_fixture(name: nil, include_attachments: false)
# puts "Dumping fixture for #{self.class.name} id=#{id} #{"with name #{name}" if name}"
attributes_to_exclude = [:updated_at, :created_at, *Rails.application.config.filter_parameters].map(&:to_s)
attributes_to_exclude << "id" if name != nil
# puts " Attributes excluded: #{attributes_to_exclude.inspect}"
attributes_to_dump = attributes
.except(*attributes_to_exclude)
.reject { |k,v| v.blank? }
name = "#{self.class.table_name.singularize}_#{id}" if name == nil
self.dump_raw_fixture({ name => attributes_to_dump }.to_yaml.sub(/---\s?/, "\n"))
if include_attachments != false
self.class.reflect_on_all_attachments
.each { |association|
a_name = association.name
Array(self.send(a_name.to_sym)).each_with_index { |attachment, index|
attachment_name = "#{name}_#{a_name.to_s.underscore}_#{index}"
blob_name = "#{attachment_name}_blob"
attachment.dump_raw_fixture({ name => {
"name" => a_name,
"record" => "#{name} (#{self.class.name})",
"blob" => blob_name
}}.to_yaml.sub(/---\s?/, "\n"))
blob = attachment.blob
blob.dump_raw_fixture("#{blob_name}: <%= ActiveStorage::Blob.fixture(filename: '#{blob.filename}') %>\n")
blob_path = "#{Rails.root}/test/fixtures/files/#{blob.filename}"
File.open(blob_path, "wb+") do |file|
blob.download { |chunk| file.write(chunk) }
end
}
}
end
end
def dump_raw_fixture(text)
fixture_file = "#{Rails.root}/test/fixtures/#{self.class.name.underscore.pluralize}.yml"
File.open(fixture_file, "a+") do |f|
f.puts(text)
end
end
end
It requires the following test_helper
(Rails 7 will obsolete this):
# test/test_helper.rb
class ActiveStorage::Blob
def self.fixture(filename:, **attributes)
blob = new(
filename: filename,
key: generate_unique_secure_token
)
io = Rails.root.join("test/fixtures/files/#{filename}").open
blob.unfurl(io)
blob.assign_attributes(attributes)
blob.upload_without_unfurling(io)
blob.attributes.transform_values { |values| values.is_a?(Hash) ? values.to_json : values }.compact.to_json
end
end
You can run this from rails console
as before:
User.find(1).dump_fixture name: bob, include_attachments: true
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With