Class: XMigra::ImpdeclMigrationAdder

Inherits:
NewMigrationAdder show all
Defined in:
lib/xmigra/impdecl_migration_adder.rb

Defined Under Namespace

Modules: SupportedDatabaseObject Classes: NoChangesError, SupportedObjectDeserializer

Constant Summary

Constants inherited from NewMigrationAdder

NewMigrationAdder::OBSOLETE_VERINC_FILE

Constants inherited from SchemaManipulator

SchemaManipulator::ACCESS_SUBDIR, SchemaManipulator::DBINFO_FILE, SchemaManipulator::INDEXES_SUBDIR, SchemaManipulator::PERMISSIONS_FILE, SchemaManipulator::PLUGIN_KEY, SchemaManipulator::STRUCTURE_SUBDIR, SchemaManipulator::VERINC_FILE

Instance Attribute Summary collapse

Attributes inherited from SchemaManipulator

#path, #plugin

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from NewMigrationAdder

#add_migration, #each_possible_production_chain_extension_handler, #production_chain_extended

Methods inherited from SchemaManipulator

#branch_upgrade_file, #load_plugin!

Constructor Details

#initialize(path) ⇒ ImpdeclMigrationAdder

Returns a new instance of ImpdeclMigrationAdder.



52
53
54
55
56
57
58
59
# 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

Instance Attribute Details

#strictObject

Returns the value of attribute strict.



61
62
63
# File 'lib/xmigra/impdecl_migration_adder.rb', line 61

def strict
  @strict
end

Class Method Details

.each_support_type(&blk) ⇒ Object



27
28
29
# File 'lib/xmigra/impdecl_migration_adder.rb', line 27

def self.each_support_type(&blk)
  @support_types.each_pair(&blk)
end

.register_support_type(tag, klass) ⇒ Object



9
10
11
12
13
14
15
16
17
18
19
20
21
# 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) ⇒ Object



23
24
25
# File 'lib/xmigra/impdecl_migration_adder.rb', line 23

def self.support_type(tag)
  @support_types[tag]
end

Instance Method Details

#add_migration_implementing_changes(file_path, options = {}) ⇒ Object



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# 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) ⇒ Object



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# 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) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# 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