Class: Enginery::Migrator

Inherits:
Object
  • Object
show all
Includes:
Helpers
Defined in:
lib/enginery/migrator.rb,
app/migrations/tracking_table/Sequel.rb,
app/migrations/tracking_table/DataMapper.rb,
app/migrations/tracking_table/ActiveRecord.rb

Defined Under Namespace

Classes: TracksMigrator, TracksModel

Constant Summary collapse

TIME_FORMAT =
'%Y-%m-%d_%H-%M-%S'.freeze
NAME_REGEXP =
/\A(\d+)\.(\d+\-\d+\-\d+_\d+\-\d+\-\d+)\.(.*)#{Regexp.escape MIGRATION_SUFFIX}\Z/.freeze

Instance Method Summary collapse

Methods included from Helpers

#activerecord_associations, #app_config, #app_controllers, #app_models, #boot_app, #controller_exists?, #datamapper_associations, #dst_path, extract_setup, fail, #fail_unless_in_app_folder!, fail_verbosely, #in_app_folder?, #load_boot_rb, #migrations_by_model, #model_exists?, o, parse_input, #routes_by_controller, #sequel_associations, #src_path, #unrootify, #valid_controller?, valid_db_type?, valid_engine?, valid_orm?, #valid_route?, valid_server?, validate_constant_name, #validate_route_name, #view_setups_for

Constructor Details

#initialize(dst_root, setups = {}) ⇒ Migrator

Returns a new instance of Migrator.



8
9
10
11
12
13
14
15
# File 'lib/enginery/migrator.rb', line 8

def initialize dst_root, setups = {}
  @dst_root, @setups = dst_root, setups
  @migrations = Dir[dst_path(:migrations, '**/*%s' % MIGRATION_SUFFIX)].inject([]) do |map,f|
    step, time, name = File.basename(f).scan(NAME_REGEXP).flatten
    step && time && name && map << [step.to_i, time, name, f.sub(dst_path.migrations, '')]
    map
  end.sort {|a,b| a.first <=> b.first}.freeze
end

Instance Method Details

#last_run(file) ⇒ Object



128
129
130
131
132
# File 'lib/enginery/migrator.rb', line 128

def last_run file
  create_tracking_table_if_needed
  return unless track = track_exists?(file)
  [track.vector, track.performed_at]
end

#listObject

list available migrations with date of last run, if any



107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/enginery/migrator.rb', line 107

def list
  create_tracking_table_if_needed
  o indent('--'), '-=---'
  @migrations.each do |(step,time,name,file)|
    track = track_exists?(File.basename(file))
    last_perform = track ? '%s on %s' % [track.vector, track.performed_at] : 'none'
    o indent(step), ' : ', name
    o indent('created at'), ' : ', DateTime.strptime(time, TIME_FORMAT).rfc2822
    o indent('last performed'), ' : ', last_perform
    o indent('--'), '-=---'
  end
end

#new(name) ⇒ Object

generate new migration. it will create a [n]..[name].rb migration file in base/migrations/ and column_transitions.yml file in base/migrations/track/ migration file will contain “up” and “down” sections. column_transitions file will keep track of column type changes.



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/enginery/migrator.rb', line 23

def new name
  (name.nil? || name.empty?) && fail("Please provide migration name via second argument")
  (name =~ /[^\w|\d|\-|\.|\:]/) && fail("Migration name can contain only alphanumerics, dashes, semicolons and dots")
  @migrations.any? {|m| m[2] == name} && fail('"%s" migration already exists' % name)
  
  max = (@migrations.max {|m| m.first}||[0]).first
  model = @setups[:create_table] || @setups[:update_table]
  context = {model: model, name: name, step: max + 1}

  [:create_table, :update_table].each do |o|
    context[o] = (m = constant_defined?(@setups[o])) ? model_to_table(m) : nil
  end
  table = context[:create_table] || context[:update_table] ||
    fail('No model provided or provided one does not exists!')

  [:create_columns, :update_columns].each do |o|
    context[o] = transitions(table, (@setups[o]||[]).map {|(n,t)| [n, opted_column_type(t)]})
  end
  context[:rename_columns] = @setups[:rename_columns]||[]

  engine = Tenjin::Engine.new(path: [src_path.migrations], cache: false)
  source_code = engine.render('%s.erb' % guess_orm, context.merge(context: context))

  o
  o '--- %s model - generating "%s" migration ---' % [model, name]
  o
  o '  Serial Number: %s' % context[:step]
  o
  time = Time.now.strftime(TIME_FORMAT)
  path = dst_path(:migrations, class_to_route(model))
  FileUtils.mkdir_p(path)
  file = File.join(path, [context[:step], time, name, 'rb']*'.')
  write_file file, source_code
  output_source_code source_code.split("\n")
  name
end

#outstanding_migrations(vector) ⇒ Object



120
121
122
123
124
125
126
# File 'lib/enginery/migrator.rb', line 120

def outstanding_migrations vector
  create_tracking_table_if_needed
  serials = @migrations.inject([]) do |l,(step,time,name,file)|
    track_exists?(File.basename(file), vector) ? l : l.push(step)
  end
  serials_to_files(vector, *serials)
end

#run(vector, file, force_run = nil) ⇒ Object

  • validate migration file name

  • apply migration in given direction if migration was not previously performed in given direction or :force option given

  • create a track in TRACKING_TABLE so on consequent requests we may know whether migration was already performed



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/enginery/migrator.rb', line 86

def run vector, file, force_run = nil
  vector = validate_vector(vector)
  
  (migration = @migrations.find {|m| m.last == file}) ||
    fail('"%s" is not a valid migration file' % file)
  
  create_tracking_table_if_needed

  track = track_exists?(file, vector)
  if track && !force_run
    o
    o '*** Skipping "%s: %s" migration ***' % [migration[0], migration[2]]
    o '  It was already performed %s on %s' % [track.vector.upcase, track.performed_at]
    o '  Use :force option to run it anyway - enginery m:%s:force ...' % vector
    o
    return
  end
  apply!(migration, vector) && persist_track(file, vector)
end

#serials_to_files(vector, *serials) ⇒ Object

convert given range or a single migration into files to be run ex: 1-5 will run migrations from one to 5 inclusive

1 2 4 will run 1st, 2nd, and 4th migrations
2 will run only 2nd migration


64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/enginery/migrator.rb', line 64

def serials_to_files vector, *serials
  vector = validate_vector(vector)
  serials.map do |serial|
    if serial =~ /\-/
      a, z = serial.split('-')
      (a..z).to_a
    else
      serial
    end
  end.flatten.map do |e|
    @migrations.find {|m| m.first == e.to_i} ||
      fail('Wrong range provided. "%s" is not a recognized migration step' % e)
  end.sort do |a,b|
    vector == :up ? a.first <=> b.first : b.first <=> a.first
  end.map(&:last)
end