module ConfigScripts module Scripts # This class is the base class for all of the config scripts that the # app will define. class Script # @!group Config # This method gets the directory in which the scripts will be stored. # # The scripts are stored in the +db/config_scripts+ directory under the app. # # @return [String] def self.script_directory Rails.root.join('db', 'config_scripts') end # @!group Pending Scripts # This method gets the scripts that we have not yet run. # # We will return the filenames, without the extensions. # # @return [Array] def self.pending_scripts paths = Dir.glob(File.join(self.script_directory, '*.rb')) paths.collect do |path| filename = File.basename(path, ".rb") timestamp = filename[0, 14] ScriptHistory.script_was_run?(timestamp) ? nil : filename end.compact end # This method runs all the scripts that have not yet been run. # # @return [True] def self.run_pending_scripts self.pending_scripts.each do |filename| self.run_file(filename) end true end # This method prints out the names of all of the scripts that have not # been run. def self.list_pending_scripts self.pending_scripts.each do |filename| puts filename end end # This method gets the script filename for a given class. # # This will convert the class name into the snake case format, and look # for a filename that has a timestamp followed by that. # # @param [String] name # The name of the config script class, with the +Config+ suffix. # # @return [String] filename # The filename. def self.filename_for_script(name) name_underscore = name.underscore paths = Dir.glob(File.join(self.script_directory, "*#{name_underscore}.rb")) path = paths.select do |path| path =~ Regexp.new("[\\d]+_#{name_underscore}.rb") end.first path = File.basename(path, ".rb") if path path end # This method runs the script with a given filename. # # The file must be in the +db/config_scripts+ directory, and should the # filename should not include the `+rb+ extension. # # @param [String] filename # The name of the file to run, without the extension. # # @param [Symbol] direction # Whether we should run the script +up+ or +down+. def self.run_file(filename, direction=:up) path = Rails.root.join('db', 'config_scripts', "#{filename}.rb") require path timestamp = filename[0,14] class_name = filename[15..-1].camelize + 'Config' klass = nil klass = class_name.constantize puts "Running #{filename} #{direction}" success = klass.new(timestamp).run(direction) end # @!group Creating # This method creates a new script. # # @param [String] timestamp # The timestamp that is used to uniquely indentify the script. # # @return [Script] def initialize(timestamp) @timestamp = timestamp end # @!group Running # @return [String] # The timestamp for this instance of the script. attr_accessor :timestamp # This method performs the changes for this config script. # # This implementation raises an exception. Subclasses must define this # method. # # If there are any issues running the script, this method should raise an # exception. def up raise "Not supported" end # This method rolls back the changes for this config script. # # This implementation raises an exception. Subclasses must define this # method if their scripts can be rolled back. # # If there are any issues rolling back the script, this method should # raise an exception. def down raise "Not supported" end # This method runs the script in a given direction. # # This will use either the +up+ or +down+ method. That method call will be # wrapped in a transaction, so if the script raises an exception, all of # its changes will be rolled back. # # If the method runs successfully, this will either record a timestamp # or remove a timestamp in the +config_scripts+ table. # # @param [Symbol] direction # Whether we should run +up+ or +down+. def run(direction) ActiveRecord::Base.transaction do begin self.send(direction) rescue => e puts "Error running script for #{self.class.name}: #{e.message}" puts e.backtrace.first raise e end case(direction) when :up ScriptHistory.record_timestamp(@timestamp) when :down ScriptHistory.remove_timestamp(@timestamp) end Rails.cache.clear end end end end end