In recent weeks I was busy converting a fairly large PHP application to Rails. The existing PHP application is about 65.500 lines of intermingled PHP and HTML/CSS code. Yep, a classic PHP application without any database abstraction layer, no templating, no MVC. This is why I dubbed it “large”, but replacing that with “crappy” would be fine too
I divided the project in a couple of deliverables:
- Data model analysis, redesign, data migration
- HTML conversion to layout/partials
- Business logic analysis and conversion to Ruby
- Integration with external web application
- Testing
- Production deployment
In typical agile/xp/scrum/ad-hoc/you-name-it fashion, all but the last deliverable were not actually delivered in full until production day.
Since I wanted to get something visible as soon as possible and with semi-live data the first thing I did was analyze the current data model. The model consisted of 46 tables. 8 tables were discarded right away (code/data rot). After further analysis the table count was reduced to 23. Next up was converting and migrating the current data set to the new data model. The PHP site was using Mysql 4.23 while the Rails version would be using PostgreSQL 8.1. This was BTW also the first Rails project where I religiously used migrations and migrations absolutely rock!. A total of 53 migration scripts were generated during the course of development. Back to conversion; Instead of writing CSV/YAML exporter/importer scripts I used the following mechanism to import/convert legacy data objects:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# Read in Rails config config = Rails::Configuration.new # Set up the class for the table/objects we want to convert # Of course the legacy database does not follow Rails conventions # so we always use table_name to fix this class OldVenue < ActiveRecord::Base def self.table_name "adressen" end end # Set up a connection to the legacy MySQL database ActiveRecord::Base.establish_connection config.database_configuration["old_production"] # Save the connection mysql_connection = OldVenue.connection # Establish a connection to our new shiny PostgreSQL database ActiveRecord::Base.establish_connection config.database_configuration["production"] # But restore the mysql connection for the legacy ActiveRecord class OldVenue.connection = mysql_connection |
The above code allows the conversion script to access both MySQL and PostgreSQL databases simultaneously.This means I can do something like:
1 2 3 4 5 6 7 8 9 10 |
old_venues = OldVenue.find(:all) for o in old_venues do n = Venue.new n.id = o.id n.name = o.naam n.zip = o.postcode # More field assignments n.save end |
..Instant data migration from MySQL to PostgreSQL without messy CSV/YAML export/import!
Hi, I made a file in lib and put the first peice of code in it, this ran error free using: ruby script/runner ‘load "get_old_data.rb"’, but as soon as I try and use the OldVenue (in my case OldSite) I get a syntax error, all I’m doing is a standard find(:all) which I have put directly after the first peice of code…. Any ideas would be welcome
Kris,
Please post your code here.
-andy
anybody know if it’s possible to mod rewrite old php urls to work with your new rails app? i have been trying to figure this out both through routes.db and .htaccess and have yet to stumble on something that works….
Elegant conversion routine!
Very nice, we work on several legacy projects and this will be handy.
Yes, but why are you doing this? PHP is way faster. PHP is in more environments. What motivated you to move a "large" PHP application to a Ruby one?
I am neither a PHP user nor a Ruby user so that info would be interesting to me as well.
Bob: I believe the reason you’re looking for is hidden in the word "crappy"
Bob - Ruby can be installed on pretty much every environment that PHP is on. It’s actually faster than PHP, far more eligant, easier to code in, more logical, more… Well, the list goes on.
PHP = years of hacks piled one ontop of another, it is not an object orientated language, it doesn’t have nearly the same amount of usability as Ruby.
Ruby = built and updated properly, logical, and… Well, it’s just the best out there. The framework is amazing.
Hi;
I think everyone has his own idea regarding php or ruby.
As written in the article the project which is going to be rewritten has no db-layer, template or mvc and might not be even written oop. It is a mess of the programmer who wrote it. This does not mean that php is bad, it means only the programmer suck.
Php is not object oriented but can be, it just depends on the programmers skills. It can also be a plus not to force always the oop.
Basicly all of the pluses or minuses of a scripting are relative. You just have to chose the right language which works for you.
And there is no way that ruby is faster than php if you write php correctly. You can get trough always without object which will be faster and less memory and resource of the system will be used.
It is true that you can make more mistakes with php than ruby but if this yould be an argument than ansi c wouldnt exist anymore.
Wow! What a cool way to migrate data. I’m beginning to think I’m going to have to learn Ruby. I keep seeing all these great features the language and frameworks support.
I hardly ever use ‘for’ statements in ruby ever… I really prefer passing blocks:
<pre>
<code>
old_venues.each{ |o|
…
}
</code>
</pre>
And how do you handle relations?
@p3t0r: this article is a bit geared towards the PHP coder, hence the for construct. Blocks are much cooler! Relations are dealt with manually. Simply defining the other classes in the relations like above and adding the connecting logic in Ruby (unavoidable if you also change the relations in the new model). Of course you can always try and co-erce the legacy db structure and naming conventions to ActiveRecord’s world view. See the class_name stuff above, same can be done for defining for example the :foreign_key in :belongs_to and :habtm constructs.<br/>
<br/>
@Bob: forget PHP vs. Rails speed debates. With proper tweaking both PHP and Rails applications can fly! The biggest reason for the switch were ‘maintainability’ and ‘Test Driven Development’. I’m not saying you cannot get these with PHP, but Rails IMHO gives you a lot of these things practically free! <a href="http://wiki.rubyonrails.org/rails/pages/UsingMigrations" target="_blank">Rails migrations</a> literally saved me hours of manual database administration cruft. It also enabled me to quickly rollback changes to the DB structure, not to mention achieving further DB abstraction! But the biggest reason to rewrite it for me personally is that it’s waay more fun developing (and even maintaining) a Rails app!! If it’s fun, it doesn’t feel like work :-)<br/>
<br/>
The next installment of this "series" will be posted this coming week.<br/>
<br/>
-andy
I am a bit confused by your code examples, did you shove that all into a migration file and then rake migrate?
Also how are you establishing your db connections? Did you modify your .yaml file to include the legacy database as well?
I also recently migrate ad PHP application to Rails, I tried explaining the process I went through in this "blog post":http://www.railsontherun.com/2007/3/30/migrating-legacy-app-part-1
Steven, yes in the above example, ‘production’ and ‘old_production’ are 2 environments which need to be defined in your class or better, in the database.yml and then used to connect.
The following line of code
ActiveRecord::Base.establish_connection config.database_configuration["old_production"]
could be replaced by:
ActiveRecord::Base.establish_connection ActiveRecord::Base.configurations['old_production']
or even simpler:
ActiveRecord.establish_connection(ActiveRecord::Base.configurations['old_production'])
If you have many tables to migrate, though, the best is to only have 1 Legacy class creating the connection to the legacy database and then all the other one to extend this "LegacyActiveRecord" class.
I hope this helps.
-Matt
I used this example successfully several months ago, but then needed to run it again yesterday and couldn’t remember how I did it the first time. Here’s how I run it:
The database_configuration["old_production"] in the example is pointing to a database connection named "old_production" in the database.yml file.
I saved the script in /lib under my rails app root directory, and to run it use:
../script/runner ‘load "my_conversions_script.rb"’