Статьи

Рубиновый трансмогрификатор

Графика ввода-вывода Компьютер хорошо умеет перемещать данные. Когда вам нужно перенести данные из одного типа в другой, я обнаружил, что Ruby значительно облегчает мою работу. Некоторое время назад у меня была эта задача, которая включала перемещение данных. Мы получали десятки наборов данных, которые нужно было преобразовать из CSV в текстовый файл фиксированной длины. Это история о том, как мы это сделали.

Определение готовой продукции.

Нам дан, скажем, файл определения того, как данные должны быть отформатированы для импорта. Допустим, это выглядело так.

FIELD NAME FORMAT NAME A(50) ADDRESS1 A(50) ADDRESS2 A(50) CITY A(50) STATE A(2) ZIP A(10) CONTACT A(50) CONTACTPHONE A(10) ACCOUNTOPENED 9(8) 

Что это значит? Мы действительно сосредоточены на формате. «A» означает алфавитно-цифровую с пробелом, добавленным справа. «9» означает число с пробелом, добавленным слева. Число означает длину поля, поэтому «A (10)» представляет собой буквенно-цифровое поле из 10 символов.

Наши исходные данные находятся в файле CSV. Что-то вроде:

 name,address1,address2,city,state,zip,contact,contactPhone,accountOpened Wonder widgets,1600 Vassar Street,,dallas,dallas,tx,75220,Tim Smith,214-555-1212,12052001 Timmy's Bikes,2723 Auburn Street,Building 3,Erie,ERIE,PA,16508-1234,,814-555-4321,03232011 

Вывод просто должен быть текстовым файлом.

 Wonder widgets 1600 Vassar Street dallas tx75220 Tim Smith 214-555-121220011205 Timmy's Bikes 2723 Auburn Street Building 3 Erie PA16508-1234 814-555-432120110323 

Как бы Вы это сделали?

Время начать трансмогрификацию

Как бы вы преобразовали данные? Из определения цели мы знаем, что имя будет длиной 50 символов. Вы можете написать метод, чтобы взять столбец имени и сделать его длиной 50 символов. Хотите проложить этот путь? Мы можем повторно фактор позже. Помните, что вы должны передать текст.

 def nameFixing(name) name = name.ljust(50) end 

Старый добрый просто добавит лишние пробелы, которые нам нужны. Это кажется легким? Это Руби, но, возможно, нам стоит это проверить.

 require 'test/unit' def nameFixing(name) name = name.ljust(50) end class NameLengthTest < Test::Unit::TestCase def test_name_short_should_equal_50 name = nameFixing "boo" assert_equal(name.length, 50) end end 

Сохраните этот файл и запустите тест.

 $ ruby ljust_test.rb Loaded suite ljust_test Started . Finished in 0.000802 seconds. 1 tests, 1 assertions, 0 failures, 0 errors, 0 skips Test run options: --seed 27164 

Сладкая, это сработало.

Что произойдет, если его длина превышает 50 символов? Давай и попробуй.

 require 'test/unit' def nameFixing(name) name = name.ljust(50) end class NameLengthTest < Test::Unit::TestCase def test_name_short_should_equal_50 name = nameFixing "boo" assert_equal(name.length, 50) end def test_long_short_should_equal_50 name = nameFixing "dgrtefdgdfshrtyutyuykhjkmbnmvcbdfgrthjghjgghvdfgrstthg" assert_equal(name.length, 50) end end 

 $ ruby ljust_test.rb Loaded suite ljust_test Started F. Finished in 0.001179 seconds. 1) Failure: test_long_short_should_equal_50(NameLengthTest) [ljust_test.rb:15]: <54> expected but was <50>. 2 tests, 2 assertions, 1 failures, 0 errors, 0 skips Test run options: --seed 40377 

Вы ожидали этого? ljust добавляет пробел, но не ljust лишние символы. Как вы думаете, будут ли вырезать лишние символы? Вы получили это кусочек .

Если имя меньше 50, нам нужно добавить пробел слева. Если имя превышает 50, нам нужно вырезать лишние символы. Если имя содержит 50 символов, мы оставляем его в покое. Мы только что переписали наши тесты?

 require 'test/unit' def nameFixing(name) case when name.length 50 name = name.slice(0..49) when name.length < 50 name = name.ljust(50) else name end end class NameLengthTest < Test::Unit::TestCase def test_name_short_should_equal_50 name = nameFixing "boo" assert_equal(name.length, 50) end def test_name_long_should_equal_50 name = nameFixing "dgrtefdgdfshrtyutyuykhjkmbnmvcbdfgrthjghjgghvdfgrstthg" assert_equal(name.length, 50) end def test_name_50_should_equal_50 name = nameFixing "dgrtefdgdfshrtyutyuymbnmvcbdfgrthjghjgghvdfgrstthg" assert_equal(name.length, 50) end end 

Как вы думаете, это пройдет? Попытайся.

Теперь, когда у вас есть способ сделать имя подходящей длины, нам нужно перейти к другим столбцам. Вы заметили шаблон с определениями? Буквенно-цифровой или числовой и определенной длины.

Почему бы нам не пойти дальше и не взяться за длину колонны. Вы должны просто иметь возможность передать переменную, чтобы установить это. Попытайся.

 require 'test/unit' def nameFixing(name,len) case when name.length len name = name.slice(0..(len-1)) when name.length < len name = name.ljust(len) else name end end class NameLengthTest < Test::Unit::TestCase def test_name_short_should_equal_50 name = nameFixing "boo",50 assert_equal(name.length, 50) end def test_name_long_should_equal_50 name = nameFixing "dgrtefdgdfshrtyutyuykhjkmbnmvcbdfgrthjghjgghvdfgrstthg",50 assert_equal(name.length, 50) end def test_name_50_should_equal_50 name = nameFixing "dgrtefdgdfshrtyutyuymbnmvcbdfgrthjghjgghvdfgrstthg",50 assert_equal(name.length, 50) end end 

Теперь вы передаете длину, которую мы должны установить. Сохраните и повторите тест. Помните, Проверьте себя, прежде чем разрушить себя. Мне тоже нужно это практиковать.

 $ ruby ljust_test.rb Loaded suite ljust_test Started ... Finished in 0.000863 seconds. 3 tests, 3 assertions, 0 failures, 0 errors, 0 skips Test run options: --seed 30117 

Все еще зеленый. Мы можем установить длину на то, что мы хотим. Мы действительно работали только с буквенно-цифровой стороной. Для числовых мы должны добавить пробел с левой стороны. Мы не проверяли, добавляем ли мы пробел с правой стороны строки. Почему бы вам не пойти дальше и добавить это к тесту. Я буду ждать.

 def test_name_should_have_white_space_on_the_right name = nameFixing "should be 18", 18 assert_equal(name, "should be 18 ") end 

Ваши тесты проходят? Здорово. Давайте перейдем к числовым столбцам. Практически та же идея, но пустое пространство слева. Вот мой быстрый и грязный код

 require 'test/unit' def nameFixing(name,len) case when name.length len name = name.slice(0..(len-1)) when name.length < len name = name.ljust(len) else name end end def numbFixing(numb,len) case when numb.length len numb = numb.slice(0..(len-1)) when numb.length < len numb = numb.rjust(len) else numb end end class NameLengthTest < Test::Unit::TestCase def test_name_short_should_equal_50 name = nameFixing "boo",50 assert_equal(name.length, 50) end def test_name_long_should_equal_50 name = nameFixing "dgrtefdgdfshrtyutyuykhjkmbnmvcbdfgrthjghjgghvdfgrstthg",50 assert_equal(name.length, 50) end def test_name_50_should_equal_50 name = nameFixing "dgrtefdgdfshrtyutyuymbnmvcbdfgrthjghjgghvdfgrstthg",50 assert_equal(name.length, 50) end def test_name_should_have_white_space_on_the_right name = nameFixing "should be 18",18 assert_equal(name, "should be 18 ") end def test_numb_should_have_white_space_on_the_left numb = numbFixing "123",8 assert_equal(numb, " 123") end end 

Давай, попробуй. Это зеленый? Отлично, но мы не очень сухие.

Время рефакторинга.

Мы объединили методы и, когда нам нужно добавить пробел, мы могли бы поставить там буквенно-цифровой или числовой. Давайте попробуем это.

 require 'test/unit' def allThingsFixing(data,len,type) case when data.length len data = data.slice(0..(len-1)) when data.length < len if type == "A" data = data.ljust(len) else data = data.rjust(len) end else data end end class NameLengthTest < Test::Unit::TestCase def test_name_short_should_equal_50 data = allThingsFixing "boo",50,"A" assert_equal(data.length, 50) end def test_name_long_should_equal_50 data = allThingsFixing "dgrtefdgdfshrtyutyuykhjkmbnmvcbdfgrthjghjgghvdfgrstthg",50,"A" assert_equal(data.length, 50) end def test_name_50_should_equal_50 data = allThingsFixing "dgrtefdgdfshrtyutyuymbnmvcbdfgrthjghjgghvdfgrstthg",50,"A" assert_equal(data.length, 50) end def test_name_should_have_white_space_on_the_right data = allThingsFixing "should be 18",18,"A" assert_equal(data, "should be 18 ") end def test_numb_should_have_white_space_on_the_left data = allThingsFixing "123",8,"X" assert_equal(data, " 123") end end 

Вы повторили тест? Они все прошли? Brilliant.

Как обращаться с датой.

Согласно определению, дата должна быть yyyymmdd и, к счастью, мы получаем ее mmddyyyy . Нам просто нужно разрезать и переставить.

Сможете ли вы передать эти данные вслепую в наш метод? Мы знаем, что имя столбца — «accountOpened». Чтобы сохранить движение, мы можем передать имя столбца в метод. Время написать тест.

 def test_date_should_format_properly data = allThingsFixing "08152003",8,"9","accountOpened" assert_equal(data, "20030815") end 

Вы не забыли добавить новую переменную, которую вы передаете в метод?

Вот что я сделал с методом

 def allThingsFixing(data,len,type,column) case when data.length len data = data.slice(0..(len-1)) when data.length < len if type == "A" data = data.ljust(len) else data = data.rjust(len) end else data end end 

Вы можете пойти дальше и запустить тест. Это должно потерпеть неудачу.

 $ ruby ljust_test.rb Loaded suite ljust_test Started F..... Finished in 0.001405 seconds. 1) Failure: test_date_should_format_properly(NameLengthTest) [ljust_test.rb:52]: <"08152003"expected but was <"20030815">. 6 tests, 6 assertions, 1 failures, 0 errors, 0 skips Test run options: --seed 25697 

Теперь вам просто нужно переформатировать дату. Похоже, нам просто нужно перенести год назад. Теперь, где мы должны положить это? Поскольку в столбце восемь цифр, а в выводе он содержит восемь цифр, мы поместим его в секцию, где data.len == len

 def allThingsFixing(data,len,type,column) case when data.length len data = data.slice(0..(len-1)) when data.length < len if type == "A" data = data.ljust(len) else data = data.rjust(len) end else if column == "accountOpened" data = data.slice(4..7)+data.slice(0..3) else data end end end 

Там, кажется, много, если заявления. Это может быть пересмотрено позже. Пройдет ли тест?

 $ ruby ljust_test.rb Loaded suite ljust_test Started ...... Finished in 0.001030 seconds. 6 tests, 6 assertions, 0 failures, 0 errors, 0 skips Test run options: --seed 48958 

Трансмогрификация завершена!

Как бы вы получили данные в Transmogrifier? Будьте на связи!