Skip to content

Instantly share code, notes, and snippets.

@ramonrails
Last active January 16, 2024 09:37
Show Gist options
  • Save ramonrails/e3ae6ac269ca7a28bb2ee21eed824e91 to your computer and use it in GitHub Desktop.
Save ramonrails/e3ae6ac269ca7a28bb2ee21eed824e91 to your computer and use it in GitHub Desktop.
Resilient, faster and maintainable seeding in ruby on rails
# frozen_string_literal: true
# a simple user factory
# factories/users.rb
#
FactoryBot.define do
factory :user do
name { Faker::Name.unique.name }
email { Faker::Internet.email(domain: "bharat.in") }
# some users will randomly be "admin"
role { "admin" } if [true, false].sample
end
end
# frozen_string_literal: true
# This file should ensure the existence of records required to run the application in every environment (production,
# development, test). The code here should be idempotent so that it can be executed at any point in every environment.
# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup).
#
# Example:
#
# ["Action", "Comedy", "Drama", "Horror"].each do |genre_name|
# MovieGenre.find_or_create_by!(name: genre_name)
# end
# fail-fast with guard-clause
#
return if Rails.env.production?
#
# start seeding when not in production
#
require "factory_bot"
require_relative "seeds/helper"
# seed constants
seeds = {
alarms: 10,
devices: 10,
groups: 5,
locations: 5,
messages: 10,
users: 5,
video_calls: 20
}
# start a block that will reset the logger on any error
#
begin
# seeds/helper.rb
# set the logger to what we need for the current environment
#
set_logger
logrun before: "Seeding the database..."
#
# Users, that will be Admins
#
logrun after: "Added #{User.count} users: #{User.random(2).pluck(:email).join(", ")}..." do
logrun(before: "Removing existing users...") { User.delete_all }
# creates a randomly sized array of hashes, filled with attributes from factory
users = Array.new(rand(1..seeds[:users])) { FactoryBot.attributes_for(:user).except(:password, :password_confirmation) }
User.insert_all(users) # bulk insert
# add at least one known admin
FactoryBot.create(:user, role: "admin", name: "Admin", email: "[email protected]")
end
# fetch data for further use
user_ids = User.ids
#
# Devices
#
logrun after: "Added #{Device.count} devices: #{Device.random(2).pluck(:name).join(", ")}..." do
logrun(before: "Removing existing devices...") { Device.delete_all }
devices = Array.new(rand(1..seeds[:devices])) { FactoryBot.attributes_for(:device) }
Device.insert_all(devices)
end
# fetch data for further use
device_ids = Device.ids
#
# Alarms
#
logrun after: "Added #{Alarm.count} alarms..." do
logrun(before: "Removing existing alarms...") { Alarm.delete_all }
alarms = Array.new(rand(1..seeds[:alarms])) { FactoryBot.attributes_for(:alarm, user_id: user_ids.sample) }
Alarm.insert_all(alarms)
end
#
# Messages
#
logrun after: "Added #{Message.count} messages... #{Message.random.content}..." do
logrun(before: "Removing existing messages...") { Message.delete_all }
messages = Array.new(rand(1..seeds[:messages])) { FactoryBot.attributes_for(:message) }
Message.insert_all(messages)
end
#
# Video Calls
#
logrun after: "Added #{VideoCall.count} video_calls..." do
logrun(before: "Removing existing video_calls...") { VideoCall.delete_all }
video_calls = Array.new(rand(1..seeds[:video_calls])) do
device_id_1, device_id_2 = device_ids.sample(2)
FactoryBot.attributes_for(:video_call, source_id: device_id_1, target_id: device_id_2)
end
VideoCall.insert_all(video_calls)
end
#
# Locations
#
logrun after: "Added #{Location.count} locations..." do
logrun(before: "Removing existing locations...") { Location.delete_all }
locations_array = Array.new(rand(1..seeds[:locations])) { FactoryBot.attributes_for(:location) }
Location.insert_all(locations_array)
end
# fetch ids for further use
location_ids = Location.ids
#
# Groups => Devices and Locations must be created before this
#
logrun after: "Added #{Group.count} groups..." do
logrun(before: "Removing existing groups...") { Group.delete_all }
rand(1..seeds[:groups]).times do
group_with_devices(devices_count: rand(2..5), location_ids: location_ids)
end
end
# rescue from any exception as StandardError
# we need this to reset the logger back to original
#
rescue => e
#
# switch the logger back to what was earlier
reset_logger
#
# raise the exception again to get rescued in the call chain
raise e
end
# frozen_string_literal: true
#
# helper methods for seeding
#
#
# Log and Run the block
#
# @param [String] message to log in the currently set logger
# [Block] block to yield
#
def logrun(**message)
# show this message --before-- executing the block
Rails.logger.info message[:before].to_s if message[:before]
# run the block if given
yield if block_given?
# show this message --after-- executing the block
Rails.logger.info message[:after].to_s if message[:after]
end
#
# switch the logger to $stdout and use `logger.info` for status updates
#
# @return [Object] Logger object that we have set
#
def set_logger
# remember the existing logger
@pre_seed_logger = Rails.logger
# output to STDOUT only for development environment
Rails.env.development? && Rails.logger = Logger.new($stdout)
# example: ELK LogStash in production
# Rails.env.production? && Rails.logger = LogStashLogger.new(type: :udp, host: 'localhost', port: 5044)
end
#
# Reset the logger back to what it was before seeding
#
def reset_logger
# reset back to pre-seed logger
Rails.logger = @pre_seed_logger
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment