|
Название: Killich: Переносим БД с PHP на Rails при помощи Rake Отправлено: killich от 16 Август, 2009, 01:31:57 Приветствую, дорогой товариСч! =)
Расскажу я тебе не о красивых обнаженных женских телах, прекрасный вид которых давно утонул в твоем сознании под давлением воспаленного виртуальностью разума, и даже не о том, как прекрасно в теплый летний вечер лежать на берегу небольшого озера и наслаждаться теплом лучей медленно заходящего за горизонт раскаленного шара.. ..зачем.. ведь этого все равно нет в твоей жизни =) ..а расскажу я тебе о том, как потратил несколько часов своей замечательной молодой жизни на написание rake задачи, организующей перенос данных из одной БД на в другую. И так: Постановка задачи: Имеем: Локальная машина с двумя MySQL БД БД1 => взята из 2х летнего php проекта, который переносится на рельсы БД2 => Новая БД на рельсах с совершенно другой структурой данных БД1 - вмещает в себе несколько десятков таблиц. БД1 использовалась несколькими экземплярами одного движка. Таблицы каждого экземпляра имеют уникальный префикс перед именем, который можно считать Логином администратора сайта. Основное содержимое БД1 - контентные страницы и файлы связанные со страницами. В БД1 организована кривая древовидная структура - Страницы хранятся в отдельной таблице - Информация о дереве в другой. БД2 - БД под многопользовательскую Rails систему. Обладает таблицей страниц, с привязкой к конкретному пользователю и полями, обеспечивающими функционал дерева (На основе awesome_nested_set) Основная цель - перенести деревья страниц в новый движок и обеспечить, что информация о файлах, прикрепленных к страницам не терялась (ранее была программная отрисовка прикрепленных файлов - мы заменим ее на простой html поскольку новый движок не предполагает автоматической отрисоки прикрепленных файлов) Начинаем: 1. Создаем файл для rake и его основу: Ruby lib\tasks\import.rakeRuby namespace :db donamespace :import do # rake db:import:start desc 'import data form OldSite' task :start => :environment do # # # end# db:import:start end# db:import end#:db У нас должно быть два соединения - одно с нашей текущей базой, другое со старой базой. Соединение с новой базой у нас будет создаваться по умолчанию - тут вопросов нет. Соединение с другой базой мы организуем через промежуточный класс порожденный от ActiveRecord::Base Ruby class OldSiteConnect < ActiveRecord::Baseestablish_connection( :adapter => "mysql", :host => "localhost", :username => "root", :password => "", :database => "OldSite", :encoding => "utf8" ) end Все порожденные классы от OldSiteConnect будут с одной стороны ActiveRecord::Base а, с другой стороны будут иметь отличное от стандартного соединение. Обратите внимение старая база у меня в cp1251 опция :encoding => "utf8" позволила мне не думать о переконвертации текста в utf8. У меня в старой базе три таблицы с которыми мне придетсф работать: Ruby class OldSiteSection < OldSiteConnectset_table_name "#{login}_sections" end class OldSitePage < OldSiteConnect set_table_name "#{login}_pages" end class OldSiteLinkedFiles < OldSiteConnect set_table_name "#{login}_linked_files" end Ruby set_table_name "#{login}_sections"Функция set_table_name позволила мне изменить имя таблицы связанной с Моделью, поскольку у меня ИМЯ МОДЕЛИ и ИМЯ ТАБЛИЦЫ БД не совпадали. Пришлось немного обмануть ожидания ActiveRecord Внутри класса моя внешняя переменная login интерпретироваться не захотела и я не став долго думать запихнул код в eval =) ну да да.. бросайте в меня камни =) подумаешь один eval =) а шуму то, шуму =) Итого получаем так: Ruby namespace :db donamespace :import do # rake db:import:start desc 'import data form OldSite' task :start => :environment do class OldSiteConnect < ActiveRecord::Base establish_connection( :adapter => "mysql", :host => "localhost", :username => "root", :password => "", :database => "OldSite", :encoding => "utf8" ) end logins= %w{ town1 town2 town3 town4 town5 } logins.each do |login| user= User.find_by_login(login) eval(" class OldSiteSection < OldSiteConnect set_table_name '#{login}_sections' end class OldSitePage < OldSiteConnect set_table_name '#{login}_pages' end class OldSiteLinkedFiles < OldSiteConnect set_table_name '#{login}_linked_files' end ") sections= OldSiteSection.find(:all, :order=>"Prev_Id ASC") end# logins.each do |login| end# db:import:start end# db:import end#:db И так - мы видим список логинов пользователей. Для каждого пользователя будут переопределяться классы Модели для доступа к нужным таблицам старой БД. Видите?! Видите?! Ruby sections= OldSiteSection.find(:all, :order=>"Prev_Id ASC")Я уже обращаюсь к старой базе и получаю от туда структуру дерева страниц (там оно было названо sections) По этой структуре sections надо выбрать страницы. Страницы надо перенести в новую БД и построить из них дерево. Дерево в новой БД будет строится по новым ID страниц, и не будет опираться на старые ID. Точнее старые ID будут определять дерево, а по новым ID нужно это дерево уже строить. Что бы привести все это в какое то соответствие - я делаю ассоциативный массив. Ключ ассоциативной пары - это старый ID страницы (в старой БД он назывался Prev_Id) Значение ассоциативной пары - новый ID страницы в новой БД. При создании каждой страницы я буду сохранять во внешнем хеше пары - старый ID => Новый ID И при необходимости буду вызывать функцию перемещения страницы к предку, если в ассоциативной паре есть нужная информация. На самом деле объяснить на словах довольно трудно - была бы у вас такая задачка - сами бы поняли что к чему. Ruby namespace :db donamespace :import do # rake db:import:start desc 'import data form OldSite' task :start => :environment do class OldSiteConnect < ActiveRecord::Base establish_connection( :adapter => "mysql", :host => "localhost", :username => "root", :password => "", :database => "OldSite", :encoding => "utf8" ) end logins= %w{ town1 town2 town3 town4 town5 } logins.each do |login| user= User.find_by_login(login) eval(" class OldSiteSection < OldSiteConnect set_table_name '#{login}_sections' end class OldSitePage < OldSiteConnect set_table_name '#{login}_pages' end class OldSiteLinkedFiles < OldSiteConnect set_table_name '#{login}_linked_files' end ") # перебираем все разделы (фактически это дерево) sections= OldSiteSection.find(:all, :order=>"Prev_Id ASC") # Хеш для соответствия старого и нового id ids_set= Hash.new sections.each do |s| # Старый id страницы old_id= s.Page_Id # Старая страница basic_page= OldSitePage.find(old_id) title= basic_page.Description content= basic_page.Content page= Page.new( :user_id=>user.id, :title=>title, :content=>content ) page.save new_id= page.id # Если в спискt родителей имеется такой id, то страницу нужно переместить к родителю page.move_to_child_of(Page.find(ids_set[s.Prev_Id])) if ids_set[s.Prev_Id] # Добавить в список соответствий id ids_set[old_id] = new_id end# sections.each do |s| end# logins.each do |login| end# db:import:start end# db:import end#:db Немного пришлось прикрепить gsub поскольку в новой базе нужно было хранить разметку, а при переносе базы скобки превратились в html эквиваленты. Кроме того, нужно было заменить все пути у ссылок, ведущих на локальные файлы, в новом движке все файлы хранятся централизовано =) Ruby title= basic_page.Description.gsub(">", '>').gsub("<", '<').gsub(""", "'")
content= basic_page.Content.gsub(">", '>').gsub("<", '<').gsub(""", "'") content= content.gsub("./files/common/", "/uploads/files/#{login}/") content= content.gsub("./files/pages/", "/uploads/files/#{login}/") content= content.gsub("./files/#{login}/common/", "/uploads/files/#{login}/") content= content.gsub("./files/#{login}/pages/", "/uploads/files/#{login}/") А еще я вспомнил, что в старом движке к странице программно прикреплялись файлы. Я не буду заниматься написанием аналогичного программного функционала - а просто найду все прикрепленные к странице файлы и в конце страницы сделаю список со ссылками. Для каждой страницы я буду вызывать Ruby # Найти файлы если они прикреплены к страницеfiles= OldSiteLinkedFiles.find(:all, :conditions => ['Page_Id = ? and Linked = ?', old_id, 1]) Page_Id в старом движке означал ID страницы, а Linked - это флаг того, что файл отображается, при просмотре страницы. Ruby content= basic_page.Content.gsub(">", '>').gsub("<", '<').gsub(""", "'")
# если массив найденных файлов не пуст то к содержимому страницы присоединяю # HTML список со ссылками на файлы (content = content + file_div(files) ) unless files.empty? Функция file_div(files) должна генерировать HTML Мне не хотелось использовать чистый HTML код и я хотел использовать хелперы из ACTION VIEW Ruby def file_div(files)res= "" files.each do |f| res<< content_tag(:li, link_to(f.Description, f.Path) ) end res= content_tag(:ul, res, :class=>:linked_files) end Однако в Rake content_tag и link_to работать не захотело. Я сделал так: Ruby require 'action_view/helpers/tag_helper'require 'action_view/helpers/url_helper' class Helpers include ActionView::Helpers::TagHelper include ActionView::Helpers::UrlHelper end А потом в file_div(files) породил экземпляр help= Helpers.new от которого и вызвал нужные функции. Уж не знаю на сколько криво я это сделал - но оно работает и это главное =) Ruby require 'action_view/helpers/tag_helper' require 'action_view/helpers/url_helper' class Helpers include ActionView::Helpers::TagHelper include ActionView::Helpers::UrlHelper end def file_div(files) help= Helpers.new res= "" files.each do |f| res<< help.content_tag(:li, help.link_to(f.Description, f.Path) ) end res= help.content_tag(:ul, res, :class=>:linked_files) end Ну и собственно получил Ruby namespace :db donamespace :import do # rake db:import:start desc 'import data form OldSite' task :start => :environment do class OldSiteConnect < ActiveRecord::Base establish_connection( :adapter => "mysql", :host => "localhost", :username => "root", :password => "", :database => "OldSite", :encoding => "utf8" ) end require 'action_view/helpers/tag_helper' require 'action_view/helpers/url_helper' class Helpers include ActionView::Helpers::TagHelper include ActionView::Helpers::UrlHelper end def file_div(files) help= Helpers.new res= "" files.each do |f| res<< help.content_tag(:li, help.link_to(f.Description, f.Path) ) end res= help.content_tag(:ul, res, :class=>:linked_files) end logins= %w{ town1 town2 town3 town4 town5 } logins.each do |login| user= User.find_by_login(login) eval(" class OldSiteSection < OldSiteConnect set_table_name '#{login}_sections' end class OldSitePage < OldSiteConnect set_table_name '#{login}_pages' end class OldSiteLinkedFiles < OldSiteConnect set_table_name '#{login}_linked_files' end ") #OldSitePage.find:first sections= OldSiteSection.find(:all, :order=>"Prev_Id ASC") ids_set= Hash.new sections.each do |s| # Старый id страницы old_id= s.Page_Id # Старая страница basic_page= OldSitePage.find(old_id) # Найти файлы если они прикреплены к странице files= OldSiteLinkedFiles.find(:all, :conditions => ['Page_Id = ? and Linked = ?', old_id, 1]) title= basic_page.Description.gsub(">", '>').gsub("<", '<').gsub(""", "'") content= basic_page.Content.gsub(">", '>').gsub("<", '<').gsub(""", "'") # Добавим список прикрепленных файлов (content = content + file_div(files) ) unless files.empty? # Поправим все пути content= content.gsub("./files/common/", "/uploads/files/#{login}/") content= content.gsub("./files/pages/", "/uploads/files/#{login}/") content= content.gsub("./files/#{login}/common/", "/uploads/files/#{login}/") content= content.gsub("./files/#{login}/pages/", "/uploads/files/#{login}/") page= Page.new( :user_id=>user.id, :title=>title, :content=>content ) page.save new_id= page.id # Добавить в список соответствий id # Если в спискок родителей имеет такой id page.move_to_child_of(Page.find(ids_set[s.Prev_Id])) if ids_set[s.Prev_Id] ids_set[old_id] = new_id end# sections.each do |s| end# logins.each do |login| end# db:import:start end# db:import end#:db Илья Зыкин aka Killich (Илья Зыкин aka Зайко) Учитель школьной информатики, Аспирант, ROR дилетант широкого профиля. ruby on rails 2.3.2 winXP, FreeBSD, RSpec, GIT, SVN, MySQL, InstantRails, REE, Passanger, Apache2.2 Отзывы/предложения/комментарии/вопросы - killich(дикий пёс)mail.ru Обучение rails по email,icq, skype и т.д. Оригинал статьи для rubyclub.com.ua http://rubyclub.com.ua/messages/show/7312-%D0%9F%D0%B5%D1%80%D0%B5%D0%BD%D0%BE%D1%81%D0%B8%D0%BC-%D0%91%D0%94-%D1%81-PHP-%D0%BD%D0%B0-Rails-%D0%BF%D1%80%D0%B8-%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%D0%B8-Rake Название: Killich: Переносим БД с PHP на Rails при помощи Rake Отправлено: phpdude от 11 Август, 2009, 08:49:06 скоро напишу статью как перенсоить бд с помощью mysqldump
Название: Killich: Переносим БД с PHP на Rails при помощи Rake Отправлено: adw0rd от 11 Август, 2009, 01:06:04 я уже написал http://adw0rd.ru/2009/mysqldump-and-cheat-sheet/ Спустя 51 секунду добавил killich, ты еще несколько статей по рельсам запостишь? тогда создам раздел по руби и рельсамНазвание: Killich: Переносим БД с PHP на Rails при помощи Rake Отправлено: Sinkler от 11 Август, 2009, 02:17:24 много красивых букв. не читал, но, killich, +1 =) спасибо
|