Rails + PHP sharing the same session

Ya know there’s times in your life you do things that feel ‘wrong’. This somehow feels that way, but one of my buddies wanted to know how it’s done, so here it is:

I’ve got an application I maintain in PHP which has been in production for a good number of years. It’s stable, works really well, however like any application I need to keep putting in enhancements, the pain curve of doing this is exponential with the time I’ve been using Rails, so over a couple of years you can imagine the pain is pretty high. Last week I thought to myself, enough is enough. I’m sick of writing PHP, I could do this so much faster in Rails. I’ve been churning away on a rewrite in the background, in Rails as and where I have time but I wanted a ‘fix it now’ kind of solution. So, why not have the same virtual host serve out both Rails & PHP, so long as the Rails app can find out if a user is currently logged in, then I can piece by piece enhance/replace the PHP app. Turns out it was pretty damned simple. Here’s a quick guide assuming

1- Both apps are sharing the same database
2- In the session you’re storing a user_id of the logged in user
3- App uses cookies

So here we go…

LaCie 2big Triple RAID Drive - Review

Lacie 2big TripleSearching for an affordable freelancer/SOHO backup solution? I think I found one on the weekend. I use JungleDisk for offsite storage of *really* important stuff that I would quite literrally die if I lost, but in this day and age of huge media files, the need for vast amounts of reliable, fast, storage is present. After a previous Maxtor drive started making some horrendous whirring and high pitched electrical whines a replacement needed to be found.

A Saturday morning Google turned up that desktop external HDD’s with RAID arrays are now actually affordable, and realistically the only option for a backup drive. Most external drives seem to be configured in RAID0 (data is split across drives effectively doubling capacity, but the upmost worst for reliability). However some drives are available with configurable RAID settings. The LaCie 2big Triple is one such drive, I found one that was within driving distance in downtown Toronto @ Vistek.

So off I went and picked one up. Should you be south of the border it’s significantly cheaper on Amazon, I paid just under $CAD469+tax - whereas Amazon it’s $US329: LaCie 301254U 1 TB 2big Triple 2-Disk RAID Hard Drive

Unboxed it, set it up. It’s a fairly attractive little unit, the aluminium blending in with any respectable SOHO.

Tips of the day - Query Trace and Equality

Tip #1 Query Trace

So you’re profiling your application, cutting down your SQL queries, looking through the logs - “argh where did that query come from ?!” you say to yourself. Then you remember what Luke G said at the last Toronto Rails Project nite - check out Query Trace.

Oh how this has made my life so much better. It tells you the stack trace at the point the query was executed, colorised nicely so you don’t go blind tailing your log files. Here’s a very simple example so you get the idea..

Query trace output

Get the plugin from here https://terralien.devguard.com/svn/projects/plugins/query_trace/README

Tip #2 Object equality vs checking id’s

When checking for ownership or association between objects, be careful you’re not checking for equality of the objects. For example “did person x create this order y” the temptation is simply to go “if person == order.creator”, where you’re checking the objects themselves. Careful! As this is actually loading up the creator of the order if it’s not already loaded (SELECT * FROM people WHERE person.id = x). Simply check if person.id = order.creator_id, as this won’t require the order’s creator to be loaded, eliminating one less hit to the DB. Little things like this littered through an application can start really slowing things down if you’re not careful.

Rails vs Merb & ActiveRecord/Datamapper

If you’re getting to this page from searching “Rails vs Merb” I suggest you read/view this presentation I did http://work.rowanhick.com/2008/06/11/toronto-rails-night-merb-presentation/

Following on from my presentation last week, I had someone mention I should look at Merb+Datamapper if I’m looking for something awesomely fast. So I decided to put it to the test. This is a *completely* contrived - I’ve done enough benchmarking to know this, so take the following figures with an (extremely) large grain of salt, I’m just posting this for interest. The view being rendered is a real world view that I’m using for a rewrite of a PHP/MySQL app, listing out orders by order status, along with their customer and originating country.

I’ve got a database of Orders(770), OrderStatuses(17), Customers(300), Countries(244). I have a find by sql statement I’m using to pull upto 100 orders like so:

"SELECT orders.id, orders.created_at, customers.name as customer_name, countries.name as country_name, order_statuses.name as status_name FROM orders LEFT OUTER JOIN `customers` ON `customers`.id = `orders`.customer_id LEFT OUTER JOIN `countries` ON `countries`.id = `customers`.country_id LEFT OUTER JOIN `order_statuses` ON `order_statuses`.id = `orders`.order_status_id WHERE (order_status_id < 100) ORDER BY order_statuses.sort_order ASC,order_statuses.id ASC, orders.id DESC LIMIT 100"

The same statement is used in all 3 applications, along with the same generated html. All 3 apps were run daemonized in production mode using mongrel. The resulting page is comprised of a layout + js + css files, that each app is serving identically.

Using Rails: 42.16 req/s (baseline)
Using Merb + ActiveRecord: 57.80 req/s (1.37x)
Using Merb + Datamapper: 72.98 req/s (1.73x)

So lets up the order count to 11550. As the volumes of data goes up (and therefore database tuning becomes more important) these are the following figures…

Using Rails: 22.82 req/s (baseline)
Using Merb + ActiveRecord: 29.85 req/s (1.30x)
Using Merb + Datamapper: 33.91 req/s (1.48x)

Merb+Datamapper still outshines the competition, but the difference in performance isn’t quite as significant, however once the DB is tuned, I bet life gets more interesting….

Again before I get lambasted, take the figures with a grain of salt, the times do vary quite significantly as there are variables aplenty, just look at the general trends.

How to Avoid Hanging Yourself with Rails

The law of unintended consequences. Evidently people are searching on figuring out how to leave this earth in Google and getting to this page because of the title with alarming regularity. If you’re one of these people PLEASE see a psychiatrist, talk to someone you love, take a deep breath, realise that you’ve got your whole life ahead of you, whatever it takes.

This evening I presented at the monthly TSOT Toronto Rails Project Nite. As promised here’s the presentation and links to resources mentioned. Big thanks to TSOT, everyone who came, everyone else who presented and the spirit in the T. Ruby/Rails community.

Presentation - How to Avoid Hanging Yourself with Rails
Faker - faker.rubyforge.org
‘mrj’s ActiveRecord select/include patch - http://dev.rubyonrails.org/attachment/ticket/7147/init.5.rb
include preloading optimisation - http://blog.codefront.net/2008/01/30/living-on-the-edge-of-rails-5-better-eager-loading-and-more/

Some points following on from this:
- YMMV, times were produced using a consistent dataset, results are going to be all over the place dependent on columns, table size, indexes, ordering etc etc.
- By testing with real (/faked) data, you can avoid (to some degree) the premature optimisation problem, you’ll see what your app is going to perform like closer to a real world production environment.
- Word on the street is Datamapper + Merb is wickedly fast, I’m going to run a test with the same dataset just to see what the difference is like
- There were no MySQL optimisations preformed, just a vanilla MySQL install.
- I’m not being negative on the core team about the select/include problem, it’s a hard problem to fix, I’m just making people aware of it so you can save the banging-of-head-on-desk problem others of us have suffered.
- If the presentation bored you to tears, you know it all already, live in Toronto, and are bored in your job - we may have a position for you. Talk to me…

If you’re in T. make sure you get yourself along to the Rails pub and Rails project nites, well worth it for networking and learning respectively. I hope to see every developer in T. putting forward a presentation on their projects, by collaborating and sharing we all learn immensely. Personally I’ve found every presentation exceedingly interesting - keep it up !

Monit restarting Mongrel Cluster - “Execution Failed”

Following in the footsteps of numerous other hapless people seeing this error - I had a frustrating number of hours, trying every combination of stuff that *should* work according to various recipes.

The following is what I had to do to get monit playing nice with mongrel cluster (and thereby avoiding the lovely error “execution failed” when trying to get mongrel restarted via monit)

This is on a CentOS 5 Enterprise x86_64 server. For some reason calling the cluster::start option on mongrel_rails would not work, I had to pass in all the flags manually to mongrel_rails, along with absolute paths for everything. But hey it works … you can manually kill a mongrel process and watch monit bring it back up for you, right as rain !!!

(note next step I’d change the monitrc to actually test a response from the mongrel server in question, rather than rely on the pid file)

Setup:
- mongrel user, in mongrel group.
- mongrel owns /var/www/apps/myappname/
- monit is run as root user
- mongrel user doesn’t have sudo privileges

in /etc/monitrc

check process mongrel_8000
with pidfile /var/www/apps/myappname/shared/pids/mongrel.8000.pid
start program = “/usr/local/bin/ruby /usr/local/bin/mongrel_rails start -d -e production -c /var/www/apps/myappname/current/ –user mongrel –group mongrel -p 8000 -P /var/www/apps/myappname/shared/pids/mongrel.8000.pid -l /var/www/apps/myappname/shared/log/mongrel.8000.log”
[stop program, rules etc following]

[Rinse and repeat for each mongrel you're running in the cluster]

Note that this will not work…
start program = “/usr/local/bin/mongrel_rails cluster::start -C /var/www/apps/myappname/current/config/mongrel_cluster.yml –clean –only 8000″

require, RMagick, and case sensitivity

(At least on Leopard with Ruby 1.8.6)

Ruby will try to reload rmagick if you get the case of rmagick different from the original require. And when that happens it all goes up the whop.

We were using attachment_fu’s image magick processor, then doing a require ‘rmagick’ seperately which resulted in tonnes of errors like this

/usr/local/lib/ruby/gems/1.8/gems/rmagick-1.15.10/lib/rmagick.rb:32:
  warning: already initialized constant PercentGeometry

Keep your case’s of require’s consistent. Even if this is file system dependent behaviour in mixed development environments you can imagine how weird problems could get!

HTML_QuickForm in …. Rails ?

Caution Thinking Out Aloud:

Okay the purists have just decided to kill me. Let the religious wars begin !

No seriously, everytime I do some coding in an old PHP app which is heavily dependent on HTML_QuickForm, I keep questioning how possible would it be to convert it across to Rails, and whether any other persons like myself would think all of their Christmases have come at once. Here’s my hopefully rational thought processes behind this. Rails is a fantastic CRUD machine. If you want to build single model forms with validations in them, absolutely beautiful. Nothing could feel more clean and make you as a developer go ‘thats sweet’. Fantastic.

Now try and do a model-less form, with validations, or combine multiple models within one form. Things like the presenter pattern get bandied about. Real world case example - a form to choose options for defining a report. Nothing that you want to put in a model as it’s not a represenation of a ‘thing’ (although some may argue it is).

I wonder and am sitting on the fence of, whether to try implementing quickform for rails. For those that haven’t been exposed to it, in a nutshell it’s a brilliant little php library that lets you create a representation of a form, with rules, pass the form into a renderer which dumps out the html on a page, but that’s not all, it assists you in processing the form on submission. So some code can look like this (not syntactically correct, but close enough), which does all of the hard work of writing out html, setting up client side js, and server side processing.

//page myform.php
 
$myform = new HtmlQuickForm('form_name', 'myform.php'); 
$abc = $myform->addElement('text', 'product_name', 'Product Name:'); 
$myform->addRule('product_name', 'required', 'You must enter a product name'); 
if ($myform->validate()) {
   //process form
   var_dump( $abc->getValue() ); 
} else {
   //setup form 
   $abc->setValue('123')
}
 
// then in any template which dumps out the rails code
$myForm->display();

It doesn’t seem like much, but put half a dozen or more options on the form, different processing rules, and suddenly it’s done a lot of the heavy lifting for you.

What would it take to add this to Rails ? How would (I personally) fit it in. It doesn’t really sit within either the model, view, or controller. The view should render the output, it doesn’t really fit within a model as it’s not a representation of a thing. It most closely sits in the controller, but you don’t really want to clutter the controller with your form models.
Here’s some thoughts

An initial idea, with everything defined in the controller, then dumped into the view by a @my_form.output

class FormController < Application 
  def do_form
    get_form
    if @myForm.posted 
       #do stuff
    else
       #populate stuff
    end
  end
 
protected
  def create_report
    @myform = RFormBuilder.new do |f|
       f.call_back_url = { :controller => 'form', :action => 'do_form' }
       f.name = 'myform'
    end
    @myform.add_text( :name => 'product_name', :label => 'Product Name', 
              :rule => { :required => :true, :msg => 'You must add this' } )
 
  end
 
end

OR

Maybe we add in a new folder in the main app

  /app/
      /controllers
      /models
      /views
      /forms
        myform.rb

And the treat myform.rb kinda like a model, but kinda not.

 
class MyForm < RFormBuilder
  defaults :name => 'my_form', :call_back_url => '/myform' 
  text_field :name => 'product_name', :label => 'Product Name', :required => :true
 
  #gets called when form is posted   
  def validate
    return true if field_product_type == 'abc'
  end 
end
 
 
# now in FormController 
 
 def do_form
    get_form
    @my_form = MyForm.new
    if @myForm.valid
       #do stuff
       render :text => @my_form.field_product_name
    else
       #populate stuff
    end
  end

What do you think ? Am I stark raving mad. Could this just work ? I think so and am willing to take up the challenge…

Rowan

Gotta love *those* recruiters

From a blanket spam email I (and probably a good portion of the rails community just received)

“I have a direct client who is seeking a strong Programmer who posses experience with Rudy on Rails. If you are qualified and interested, please send me your most current resume in a word.doc format to..”

Yes, Rudy was actually put in bold, just to place even more emphasis on the spelling error.

Want to know what Toronto’s councillors are spending your money on ?

Check this out. Council Expenses (robford.ca)

Absolutely brilliant, and questionable all at the same time…

About

Rowan is a Director of Technology for a large marketing services company, specialising in architecting, developing and putting web applications into production - in particular Ruby on Rails based apps. He lives in Toronto, Canada but speaks in a funny accent as he's originally from New Zealand. He's been working in the software and web business for over a decade. This blog covers Web Application development and deployment in the real world, dealing with topics from business fundamentals to Ruby on Rails, Merb, PHP, Flex, MySQL, Apache and more.

Read more ...

 

 

View Rowan Hick's profile on LinkedIn

 

Subscribe to my RSS feed