Archive for the ‘Ruby on Rails’ Category

Removing associated records

Tuesday, December 16th, 2008

I was doing the following to remove associated records from comments when removing the parent event record:

@event = Event.find(params[:id])
@comments = @event.comments.find(:all)
 
if @event.destroy && Comment.destroy(@comments)
  flash[:notice] = "Event deleted"
else
  flash[:error] = "There was a problem deleting the event"
end
 
redirect_to :action => 'index'

This worked fine, but changing the event model to:

has_many :comments, :dependent => :destroy

… means I can change the controller code to:

@event = Event.find(params[:id])
 
if @event.destroy
  flash[:notice] = "Event deleted"
else
  flash[:error] = "There was a problem deleting the event"
end
 
redirect_to :action => 'index'

On a side note, you have to do Comment.destroy(@comments) since this doesn’t work:

>> @event = Event.find(1)
=> #<event id: 1, name: "Fabric", location_id: 1, lineup: "Mark Farina<br />Terry Francis<br />Jon Marsh<br />James ...", date: "2005-04-23", url: "www.fabriclondon.com", tickets: "", price: "£15/£12 NUS", time: "N/A", user_id: 1>
>> @comments = @event.comments.find(:all)
=> []
>> @comments.destroy
NoMethodError: undefined method `destroy' for []:Array
        from (irb):3
>> Comment.destroy(@comments)
=> []
>>

Rails Finders – Array Conditions

Monday, December 8th, 2008

From http://guides.rubyonrails.com/finders.html:

The reason for doing code like:

+Client.first(:conditions => ["orders_count = ?", params[:orders]])+

instead of:

+Client.first(:conditions => "orders_count = #{params[:orders]}")+

is because of parameter safety. Putting the variable directly into the conditions string will pass the variable to the database as-is. This means that it will be an unescaped variable directly from a user who may have malicious intent. If you do this, you put your entire database at risk because once a user finds out he or she can exploit your database they can do just about anything to it. Never ever put your parameters directly inside the conditions string.

Awesome! I didn’t know that. These Rails Guides are SERIOUSLY awesome! Thank you to the guys involved w/ these!

mongrel_rails

Monday, November 17th, 2008

Today I had a call from a customer running Apache 2.2 and several mongrel instances. It seems that after a reboot the server did not start the mongrel instances. They had a script to be run after boot containing:

# /usr/local/apache2/bin/apachectl start
# /usr/local/bin/mongrel_rails cluster::start

The mongrel_rails cluster::start command failed due to missing configuration file, and a quick google search reveiled the command mongrel_cluster_ctl. As I understand it, mongrel_rails is to start an individual mongrel instance like so:

# cd /path/to/application
# /usr/local/bin/mongrel_rails cluster::start -C config/mongrel_cluster.yml

… while mongrel_cluster_ctl will start all the configurations symlinked in /etc/mongrel_cluster. Running like so did the trick:

# /usr/local/bin/mongrel_cluster_ctl start

Wow, Passenger seems so much more straight-forward!

Before I forget

Saturday, August 23rd, 2008

A few interesting things I’ve picked up over the last few days:

<%= link_to '<<', :back %>

The :back here links back to the referring page.

Another thing I had to do was convert MySQL datetime data into something a bit more readable:

<%= comment.entered.strftime("%d/%m/%Y") %>

That most certainly shouldn’t go in the view, but the key here is strftime.

If you have a view and need to determine which controller or action called the view, you can use:

<% if controller.action_name == "something" %>
<% if controller.controller_name == "something" %>

Finally, I was trying to run a rake db:migration on my laptop and it kept spewing out an error about not being about to load mysql. Wtf, I could have sworn I installed the mysql gem eons ago … but it certainly wasn’t seeing it. Anyway, when I tried installing the gem it was unable to find the MySQL libraries, etc. and gave some options that I could pass to the gem install to specify my MySQL installation. I tried passing the options to gem install in a few different ways but no cigar. As I had to google this before (and again, since I couldn’t remember the syntax), I figured it’d be a good idea to make a note this time around.

$ sudo gem install mysql -- --with-mysql-config=/opt/local/bin/mysql_config5

C’est ca.

Importing data into rails app

Monday, August 18th, 2008

I’ve got a users table under a legacy PHP site which I wanted to import into the users table on my rails app. The problem is I’m using restful_authentication which encrypts the passwords using a salt, etc. so I wanted to leverage the rails model when importing the data. I remembered seeing an article about using the rails console which I thought would potentially get me one step closer. After tracking down that article, I was able to:

$ cd ~/rails/regexp
$ ruby script/console production
Loading production environment (Rails 2.1.0)

From here, I could manually create user objects, etc.:

>> @user = User.new
>> @user.login = "something"
>> @user.name = "some name"
...
>> @user.password = "blah"
>> @user.save

This worked beautifully and encrypted the passwords correctly.

Now, how to automate this in some sort of batch job? Well, ciaran on IRC suggested I use a rake tast. What I’ve since done is created ~/rails/regexp/lib/tasks/userimport.rake containing:

desc "Import users from PHP site to rails site"
task :userimport => :environment do
  @user = User.new
  @user.login = "something"
  @user.name = "some name"
  @user.password = "blah"
  @user.save
end

I could then run the following from the command-line:

$ rake userimport RAILS_ENV=production

Anyway, this gave me access to the user model, allowing me to enter data from the command-line. At that point, it was a simple process of modifying the rake task to connect to the old database, extract the rows, and then loop through them to submit to the user model.

This article here is a good intro to rake, which I’ll need to look at later.

rescue_from

Monday, August 4th, 2008

So, I was updating a few controllers to trap exceptions using something like:

begin
  @event = Event.find(params[:id]
rescue ActiveRecord::RecordNotFound
  flash[:error] = "Event not found"
  redirect_to :action => :index
end

This worked fine but didn’t seem to be overly “DRY”. Googling a bit, I came across rescue_from and have since replaced all those rescues in my controllers with a single instance of something like:

rescue_from ActiveRecord::RecordNotFound, :with => :invalid_record
 
def invalid_record
  flash[:error] = "Event not found"
  redirect_to :action => :index
end

Nice! I’m starting to see why Rails kicks serious ass. :)

flash[:notice] / flash[:error], again

Sunday, July 27th, 2008

So, I’ve ended up changing my code to:

if @user.update_attributes(params[:user])
  flash[:notice] = "Profile updated"
  redirect_to :action => 'edit'
else
  flash.now[:error] = @user.errors.full_messages.first
  render :action => 'edit'
end

Basically, using a redirect_to on the else means you loose your post values when an error is encountered, so changing to render works nicely and using flash.now means those flash notices don’t linger after subsequent clicks. See this page for more info.

flash[:notice] / flash[:error]

Saturday, July 26th, 2008

So, I was using something like this in my controllers:

if @user.update_attributes(params[:user])
  flash[:notice] = "Profile updated"
  redirect_to :action => 'edit'
else
  flash[:error] = @user.errors.full_messages.first
  render :action => 'edit'
end

This worked fine, but if a flash[:notice] or flash[:error] was generated and I then clicked on another page that also displayed these flash messages, the new page would display the previous page’s flash messages. Not nice. A quick search returned this site, which recommended to change the code to:

if @user.update_attributes(params[:user])
  flash[:notice] = "Profile updated"
  redirect_to :action => 'edit'
else
  flash[:error] = @user.errors.full_messages.first
  redirect_to :action => 'edit'
end

Using redirect_to instead of render worked a charm. Yeehaw!

error_messages_for

Saturday, July 19th, 2008

I’m using restful_authentication on a site I’m writing (well, converting from PHP to rails) and initially had this in app/views/users/new.rhtml:

<%= error_messages_for :user %>

The problem here was that it’d spew out a billion errors if someone submit the form without entering any fields. I’ve since removed that line and added the following to app/views/users/new.rhtml:

<%= @user.errors.full_messages.first %>

Now it only prints the first error as opposed to all of them, if there are multiple. This looks a little less overwhelming and is easier to format.

On a side note, you’ll probably want to check that @users.errors exists before trying to display the contents!