class XMigra::ImpdeclMigrationAdder

Attributes

strict[RW]

Public Class Methods

each_support_type(&blk) click to toggle source
# File lib/xmigra/impdecl_migration_adder.rb, line 27
def self.each_support_type(&blk)
  @support_types.each_pair(&blk)
end
new(path) click to toggle source
Calls superclass method
# File lib/xmigra/impdecl_migration_adder.rb, line 52
def initialize(path)
  super(path)
  @migrations = MigrationChain.new(
    self.path.join(STRUCTURE_SUBDIR),
    :db_specifics=>@db_specifics,
    :vcs_specifics=>@vcs_specifics,
  )
end
register_support_type(tag, klass) { || ... } click to toggle source
# File lib/xmigra/impdecl_migration_adder.rb, line 9
def self.register_support_type(tag, klass)
  if @support_types.has_key? tag
    raise Error, "#{@support_types[tag]} already registered to handle #{tag}"
  end
  @support_types[tag] = klass
  if block_given?
    begin
      yield
    ensure
      @support_types.delete(tag)
    end
  end
end
support_type(tag) click to toggle source
# File lib/xmigra/impdecl_migration_adder.rb, line 23
def self.support_type(tag)
  @support_types[tag]
end

Public Instance Methods

add_migration_implementing_changes(file_path, options={}) click to toggle source
# File lib/xmigra/impdecl_migration_adder.rb, line 63
def add_migration_implementing_changes(file_path, options={})
  file_path = Pathname(file_path)
  prev_impl = @migrations.latest_declarative_implementations[file_path]
  decl_stat = prev_impl.declarative_status
  
  # Declarative doesn't make any sense without version control
  unless VersionControlSupportModules.find {|m| self.kind_of? m}
    raise Error, "#{self.path} is not under version control (required for declarative)"
  end
  
  # Check if an implementation is needed/allowed
  if bad_rel = {
      :equal=>"the same revision as",
      :older=>"an older revision than",
  }[decl_stat]
    raise NoChangesError, "#{file_path} changed in #{bad_rel} the latest implementing migration #{prev_impl.file_path}"
  end
  
  # This should require the same user to generate a migration on the same
  # day starting from the same committed version working on the same
  # branch to cause a collision of migration file names:
  file_hash = begin
    file_base = begin
      [
        SchemaUpdater.new(path).branch_identifier,
        vcs_latest_revision(file_path),
      ].join("\x00")
    rescue VersionControlError
      ''
    end
    XMigra.secure_digest(
      [(ENV['USER'] || ENV['USERNAME']).to_s, file_base.to_s].join("\x00"),
      :encoding=>:base32
    )[0,12]
  end
  summary = "#{file_path.basename('.yaml')}-#{file_hash}.decl"
  
  add_migration_options = {
    :file_path=>file_path,
  }
  
  # Figure out the goal of the change to the declarative
  fail_options = []
  case decl_stat
  when :unimplemented
    fail_options << :renounce
    add_migration_options[:goal] = options[:adopt] ? 'adoption' : 'creation'
  when :newer
    fail_options.concat [:adopt, :renounce]
    add_migration_options[:goal] = 'revision'
  when :missing
    fail_options << :adopt
    add_migration_options[:goal] = options[:renounce] ? 'renunciation' : 'destruction'
  end
  
  if opt = fail_options.find {|o| options[o]}
    raise Program::ArgumentError, "--#{opt} flag is invalid when declarative file is #{decl_stat}"
  end
  
  # gsub gets rid of trailing whitespace on a line (which would force double-quote syntax)
  add_migration_options[:delta] = prev_impl.delta(file_path).gsub(/\s+$/, '').extend(LiteralYamlStyle)
  unless options[:adopt] || options[:renounce]
    begin
      if suggested_sql = build_suggested_sql(decl_stat, file_path, prev_impl)
        add_migration_options[:sql] = suggested_sql
        add_migration_options[:sql_suggested] = true
      end
    rescue DeclarativeSupport::SpecificationError
      add_migration_options[:spec_error] = $!.to_s
    end
  end
  
  add_migration(summary, add_migration_options)
end
build_suggested_sql(decl_stat, file_path, prev_impl) click to toggle source
# File lib/xmigra/impdecl_migration_adder.rb, line 180
def build_suggested_sql(decl_stat, file_path, prev_impl)
  d = SupportedObjectDeserializer.new(
    file_path.basename('.yaml').to_s,
    @db_specifics
  )
  case decl_stat
  when :unimplemented
    initial_state = YAML.parse_file(file_path)
    initial_state = d.deserialize(initial_state.children[0])
    
    if initial_state.kind_of?(SupportedDatabaseObject)
      initial_state.creation_sql
    end
  when :newer
    old_state = YAML.parse(
      vcs_contents(file_path, :revision=>prev_impl.vcs_latest_revision),
      file_path
    )
    old_state = d.deserialize(old_state.children[0])
    new_state = YAML.parse_file(file_path)
    new_state = d.deserialize(new_state.children[0])
    
    if new_state.kind_of?(SupportedDatabaseObject) && old_state.class == new_state.class
      new_state.sql_to_effect_from old_state
    end
  when :missing
    penultimate_state = YAML.parse(
      vcs_contents(file_path, :revision=>prev_impl.vcs_latest_revision),
      file_path
    )
    penultimate_state = d.deserialize(penultimate_state.children[0])
    
    if penultimate_state.kind_of?(SupportedDatabaseObject)
      penultimate_state.destruction_sql
    end
  end
rescue DeclarativeSupport::SpecificationError
  raise
rescue StandardError => e
  XMigra.log_error(e)
  raise if strict
  nil
end
migration_data(head_info, options) click to toggle source
Calls superclass method
# File lib/xmigra/impdecl_migration_adder.rb, line 138
def migration_data(head_info, options)
  target_object = options[:file_path].basename('.yaml')
  goal = options[:goal].to_sym
  super(head_info, options).tap do |data|
    # The "changes" key is not used by declarative implementation
    #migrations -- the "of object" (TARGET_KEY) is used instead
    data.delete(Migration::CHANGES)
    
    data[DeclarativeMigration::GOAL_KEY] = options[:goal].to_s
    data[DeclarativeMigration::TARGET_KEY] = target_object.to_s
    data[DeclarativeMigration::DECLARATION_VERSION_KEY] = begin
      if [:renunciation, :destruction].include?(goal)
        'DELETED'
      else
        XMigra.secure_digest(options[:file_path].read)
      end
    end
    data['delta'] = options[:delta]
    options[:spec_error].tap do |message|
      data['specification error'] = message if message
    end
    
    # Reorder "sql" key to here (unless adopting or renouncing, then
    # remove "sql" completely)
    provided_sql = data.delete('sql')
    unless [:adoption, :renunciation].include? goal
      data['sql'] = provided_sql
      data[DeclarativeMigration::QUALIFICATION_KEY] = begin
        if options[:sql_suggested]
          'suggested command sequence'
        else
          'unimplemented'
        end
      end 
    end
    
    # Reorder "description" key to here with
    data.delete('description')
    data['description'] = "Declarative #{goal} of #{target_object}"
  end
end