./bin/cli/am in lux-fw-0.1.35 vs ./bin/cli/am in lux-fw-0.2.1
- old
+ new
@@ -1,18 +1,33 @@
#!/usr/bin/env ruby
-ENV['DB_NAME'] = ARGV[0] if ARGV[0]
+require 'bundler/setup'
+require 'dotenv'
-require './config/application'
+Dotenv.load
+Bundler.require(:default)
+# load app config
+require './config/db'
+
+# Sequel extension and plugin test
+DB.run %[DROP TABLE IF EXISTS lux_tests;]
+DB.run %[CREATE TABLE lux_tests (int_array integer[] default '{}', text_array text[] default '{}');]
+class LuxTest < Sequel::Model; end;
+LuxTest.new.save
+die('"DB.extension :pg_array" not loaded') unless LuxTest.first.int_array.class == Sequel::Postgres::PGArray
+DB.run %[DROP TABLE IF EXISTS lux_tests;]
+
class AutoMigrate
attr_accessor :fields
class << self
- def table(table_name)
- die "table [#{table_name}] not in plural -> expected [#{table_name.to_s.pluralize}]" unless table_name.to_s.pluralize == table_name.to_s
+ def table table_name
+ die "Table [#{table_name}] not in plural -> expected [#{table_name.to_s.pluralize}]" unless table_name.to_s.pluralize == table_name.to_s
+ die 'Table name "%s" is not permited' % table_name if [:categories].include?(table_name)
+
unless DB.table_exists?(table_name.to_s)
# http://sequel.jeremyevans.net/rdoc/files/doc/schema_modification_rdoc.html
DB.create_table table_name do
primary_key :id, Integer
index :id, unique: true
@@ -47,129 +62,140 @@
###
def initialize table_name
@table_name = table_name
@fields = {}
+
+ klass = @table_name.to_s.classify
+
+ if Object.const_defined?(klass)
+ puts 'Table name "%s" is not allowed - class "%s" is allready defined'.red % [table_name, klass]
+ exit
+ end
+
+ eval %[class ::%s < Sequel::Model; end;] % klass
+ @object = klass.constantize.new
end
+ def transaction_do text
+ self.class.transaction_do text
+ end
+
def log_run what
puts ' %s' % what.green
self.class.transaction_do what
end
def fix_fields
for vals in @fields.values
type = vals[0]
opts = vals[1]
- opts[:limit] ||= 255 if type == :string
+ opts[:limit] ||= 255 if type == :string
opts[:default] ||= false if type == :boolean
-
- opts[:null] = true unless opts[:null].class.name == 'FalseClass'
- opts[:array] ||= false
- opts[:unique] ||= false
- opts[:default] = [] if opts[:array]
+ opts[:null] = true unless opts[:null].class.name == 'FalseClass'
+ opts[:array] ||= false
+ opts[:unique] ||= false
+ opts[:default] = [] if opts[:array]
end
end
def get_db_column_type field
type, opts = @fields[field]
db_type = type
db_type = :varchar if type == :string
db_type = Time if type == :datetime
+
if opts[:array]
db_type = '%s(%s)' % [db_type, opts[:limit]] if type == :string
db_type = '%s[]' % db_type
else
db_type = 'varchar(%s)' % opts[:limit] if opts[:limit]
end
+
db_type
end
def update
- begin
- obj = @table_name.to_s.classify.constantize
- o = obj.new
- rescue
- puts "Object #{@table_name.to_s.classify.red} does not exist, yet table #{@table_name.to_s.red} exists!"
- return
- end
+ puts "Table #{@table_name.to_s.yellow}, #{@fields.keys.length} fields"
# remove extra fields
- existing_fields = o.attributes.keys - ['id']
- for field in (existing_fields - @fields.keys.map(&:to_s))
+ for field in (@object.columns - @fields.keys - [:id])
print "Remove colum #{@table_name}.#{field} (y/N): ".light_blue
if STDIN.gets.chomp.downcase.index('y')
DB.drop_column @table_name, field
puts " drop_column #{field}".green
end
end
- puts "Table #{@table_name.to_s.yellow}, #{@fields.keys.length} fields"
-
# loop trough defined fileds in schema
for field, opts_in in @fields
type = opts_in[0]
opts = opts_in[1]
db_type = get_db_column_type(field)
# create missing columns
- unless obj.columns.index(field.to_sym)
+ unless @object.columns.index(field.to_sym)
DB.add_column @table_name, field, db_type, opts
if opts[:array]
default = type == :string ? "ARRAY[]::character varying[]" : "ARRAY[]::integer[]"
-
- self.class.transaction_do "ALTER TABLE #{@table_name} ALTER COLUMN #{field} SET DEFAULT #{default};"
+ transaction_do "ALTER TABLE #{@table_name} ALTER COLUMN #{field} SET DEFAULT #{default};"
end
puts " add_column #{field}, #{db_type}, #{opts.to_json}".green
next
end
- if current = obj.db_schema[field.to_sym]
+ if current = @object.db_schema[field]
# unhandled db schema changes will not happen
# ---
# field - field name
# current - current db_schema
# type - new proposed type in schema
# opts - new proposed types
# if we have type set as array and in db it is not array, fix that
- if opts[:array] && !current[:db_type].include?('[]')
+ if opts[:array]
+ # covert to array unless is array
+ if !current[:db_type].include?('[]')
+ transaction_do %[
+ alter table #{@table_name} alter #{field} drop default;
+ alter table #{@table_name} alter #{field} type #{current[:db_type]}[] using array[#{field}];
+ alter table #{@table_name} alter #{field} set default '{}';
+ ]
- self.class.transaction_do %[
- alter table #{@table_name} alter #{field} drop default;
- alter table #{@table_name} alter #{field} type #{current[:db_type]}[] using array[#{field}];
- alter table #{@table_name} alter #{field} set default '{}';
- ]
-
- puts " Coverted #{@table_name}.#{field} to array type".green
+ puts " Coverted #{@table_name}.#{field} to array type".green
+ elsif !current[:default]
+ # force default for array to be present
+ default = type == :string ? "ARRAY[]::character varying[]" : "ARRAY[]::integer[]"
+ transaction_do %[alter table #{@table_name} alter #{field} set default #{default};]
+ end
end
# if we have array but scema says no array
if !opts[:array] && current[:db_type].include?('[]')
m = current[:type] == :integer ? "#{field}[0]" : "array_to_string(#{field}, ',')"
- self.class.transaction_do %[
+ transaction_do %[
alter table #{@table_name} alter #{field} drop default;
alter table #{@table_name} alter #{field} type #{current[:db_type].sub('[]','')} using #{m};
]
puts " Coverted #{@table_name}.#{field}[] to non array type".red
end
# if varchar limit size has changed
- if type == :string && current[:max_length] != opts[:limit]
- self.class.transaction_do "ALTER TABLE #{@table_name} ALTER COLUMN #{field} TYPE varchar(#{opts[:limit]});"
+ if type == :string && !opts[:array] && current[:max_length] != opts[:limit]
+ transaction_do "ALTER TABLE #{@table_name} ALTER COLUMN #{field} TYPE varchar(#{opts[:limit]});"
puts " #{field} limit, #{current[:max_length]}-> #{opts[:limit]}".green
end
# covert from varchar to text
if type == :text && current[:max_length]
- self.class.transaction_do "ALTER TABLE #{@table_name} ALTER COLUMN #{field} SET DATA TYPE text"
+ transaction_do "ALTER TABLE #{@table_name} ALTER COLUMN #{field} SET DATA TYPE text"
puts " #{field} limit from #{current[:max_length]} to no limit (text type)".green
end
# null true or false
if current[:allow_null] != opts[:null]
@@ -219,11 +245,11 @@
end
rescue; end
end
def rename field_old, field_new
- existing_fields = @table_name.to_s.classify.constantize.new.attributes.keys.map(&:to_sym)
+ existing_fields = @table_name.to_s.classify.constantize.new.columns
if existing_fields.index(field_old.to_sym) && ! existing_fields.index(field_new.to_sym)
DB.rename_column(@table_name, field_old, field_new)
puts " * renamed #{@table_name}.#{field_old} to #{@table_name}.#{field_new}"
puts ' * please run auto migration again'
@@ -234,14 +260,14 @@
def method_missing type, *args
name = args[0]
opts = args[1] || {}
if [:string, :integer, :text, :boolean, :datetime, :date, :jsonb].index(type)
- @fields[name] = [type, opts]
+ @fields[name.to_sym] = [type, opts]
elsif [:decimal].index(type)
opts[:precision] ||= 8
opts[:scale] ||= 2
- @fields[name] = [:decimal, opts]
+ @fields[name.to_sym] = [:decimal, opts]
elsif type == :timestamps
opts[:null] ||= false
@fields[:created_at] = [:datetime, opts]
@fields[:created_by] = [:integer, opts]
@fields[:updated_at] = [:datetime, opts]