NoobOnRails

Ruby on Rails tips, tricks and hints for the aspiring rails hero.



Friday, April 27, 2007

NoobOnRails is dead


So, in case you didn't know, I haven't been posting as much because of my full time Rails gig with integrum. But, I decided to jump back into the blogging world but at a new address, okwithfailure.com. I hope to be blogging there pretty consistently since I know have plenty to blog about. Hope to see you there!

Wednesday, February 21, 2007

Freezing to latest edge rails will break your app


But only for a brief second! The culprit? The switching of the default Rails session management method over to a cookie based management system. Right after I froze to edge, I started my app up and immediately tried to access it. Blam, it blew up in my face. Right there in the server console it was telling me I needed to stick

session :secret => "secret phrase here"


in my Application.rb. Doing that made everything better. You can read and dig into all what Jeremy added in this change set here.

acts_as_list makes lists drop dead easy


Using ActiveRecord's acts_as_list makes dealing with listed items a breeze. A lot of the examples I have seen assume that you'd be using acts_as_list on a parent's child items, the belongs_to side of a has_many relationship. Chances are, most of the time you'll be using acts_as_list, you'll be using it in the same situation but you can use it on a childless object as well. Here's the code...

For this example, I'll go with a list of Books that I am displaying in a list, I have a Book.rb model and a Books controller and I'm listing books in my Books < style="font-weight: bold;">script/generate add_position_to_books_table

That creates the actual migration file. The insides of that file could look something like this...


class AddPositionToBook <>
def self.up
add_column :books, :position, :integer
#i didn't add a default becuase I'm going to set one in the controller

#if I currently had a list of books, I want to go through them and line them up
#so I need to reset the column information for the books table so that I can
#acutally use the position column in this same migration file
Book.reset_column_information


#have to grab all the books so I can cycle through them
book = Books.find(:all)

#iterate through the books and for each one, grab the actual book and it's
#position in the array so I can use their position to set their position, if
#that makes sense
book.each_with_index do |b, i|
b.position = i+1
#save it with the bang so it I did something naughty, it'll blow up on me
#it's not necessary b.save should work too
b.save!
end

end

def self.down
#remove the column I just added because not every migration is perfect
remove_column :books, :position
end
end


Cool. Now we can rake db:migrate from the command line to get the column in our database. Now we have the column, let's put it to work.

In my Book.rb model, I can stick acts_as_list near the top for readability. Something like...

class Book <>
acts_as_list
#other stuffs like validations, spiffy methods etc
end


will work. Hot, we're so close...kind of. Now Let's look at the actual list view.

In my view I'm keeping things real simple. I just have all my books listed as s in one big table, nothing special. of course I have the table and th declarations above this but here...


<% for book in @books %>
<tr>
<td><%=h book.title %></td>
<td><%=h book.description</td>
</tr>
<% end %>



...is where the real magic happens. Now let's throw our position goodies in there.

In my view, maybe after my description <td>, I can add a section for position. I would also have to add the corresponding <th> header but I'll just show you the position row...

<td><%=h book.position %> </td>

Ok so the positions are now showing on the list. But if I think about it, I also need to add it to the form for when I add a new book. It would also be cool if I could also have it fill with what should be the last position. So let's add a field to the form for position and then move on to the controller. Here's the field in the form partial...

<td></td>
<td><%= text_field 'book', 'position' %></td>


Cool. Now I have it in the form and I could submit a test one so I could see it create successfully in the server log. But I want it to already be filled in when I first get to the new book form. I want it to prefill with the last position number. I'm picky like that. Here's how I made it happen...

in the new method...


def new

#grab all of the books so we know how many we have
b = Book.find(:all)

#create a new book object because this is the new method
@book = Book.new

#if we have less than one book, let's just set this position value to 1
if b.size < 1
@book.position = 1
else

#if we have more than one book, let's get the position of the last book
#and set this new book's position (in the form field at least), to
#1+ that value
@book.position = b.last.position+1
end
end




Now we move into the Create method to make sure our users don't try to put in crazy spaced out values. If my last book is at a position of 6, I don't want to be able to put in something with the position of 4535. So, in my create method in my Book controller, I put this...


def create
#first with grab the book params hash from the request sent in by the form
@book = Book.new(params[:book])

#get an array of all of the books just so I can find out how many I have
#this is probably would not be the best use of resources if I had
#millions of books, beats me
b = Book.find(:all)

if @book.save
#if the book is saved, let's check it's position against that array
#of books we just created
if @book.position > b.last.position+2

#if the books position is greater than my last book in my list
#change it to fit right after the last one, for example
#if my last item has a position of 6, let's set this new one to
#a position of 7
@book.position = b.last.position+1
end
flash[:notice] = 'Your Book was successfully created.'
redirect_to :action => 'list'
else
render :action => 'new'
end
end



Cool. Now that's working. But currently, the only way to edit the position is to manually edit each book. We're better than that. Let's add some buttons to in the list view that will let us move the books up or down the list.

In the list view, I turn this...

<td><%=h book.position %> </td>

into this...


<td><%=h book.position %>

<% unless @book.first.position == book.position %>

<%= link_to "up", { :action => 'move', :method => 'move_higher', :book_id => book.id } %>
<%= link_to "top", { :action =>'move', :method => 'move_to_top', :book_id => book.id } %>

<% end %>

<% unless book.last? %>

<% unless book.position == (@books.first.position || @books.last.position) %> | <% end %>

<%= link_to "down, { :action => 'move', :method => 'move_lower', :book_id => book.id } %>
<%= link_to "bottom, { :action => 'move', :method => 'move_to_bottom', :book_id => book.id } %>

<% end %>

</td>




Woah. Now what the heck is that doing right? If you want to know, either go grab Rob Orsini's Rails Cookbook from which I grabbed most of this code. The links, each one really, call the 'move' method in our books controller as well as pass a method param which specifies which acts_as_list method we want that link to trigger. We also pass the book_id so our app knows which book to move. "But there's no move method in the books controller!!" I can hear you scream. Don't fret, here it is...



def move

if ["move_lower", "move_higher", "move_to_top", "move_to_bottom"].include?(params[:method]) and params[:book_id] =~ /^\d+$/
#if the incoming params contain any of these methods and a numeric book_id,
#let's find the book with that id and send it the acts_as_list specific method
#that rode in with the params from whatever link was clicked on
Book.find(params[:book_id]).send(params[:method])
end
#after we're done updating the position (which gets done in the background
#thanks to acts_as_list, let's just go back to the list page,
#refreshing the page basically because I didn't say this was an RJS
#tutorial, maybe next time
redirect_to :action => :list
end



And that is it! Clicking on the up, top, down, bottom buttons on your list page will move the items up and down. If you need to tweak acts_as_taggable just a touch, mess with the scope or anything like that, check out what the rails edge docs have to say here.

Goodbye .rhtml, we knew you well


So I hope you didn't have any emotional attachments to the .rhtml extension because accroding to the recent changeset 6178 it is on it's way out. Say goodbye to the the .rhtml and .rxml extensions and hello the the .erb and .builder extensions. Why, you ask? as to make it a point that the extension shouldn't determine the content. Here's the message form the changelog...

Added .erb and .builder as preferred aliases to the now deprecated .rhtml and .rxml extensions [Chad Fowler]. This is done to separate the renderer from the mime type. .erb templates are often used to render emails, atom, csv, whatever. So labeling them .rhtml doesn't make too much sense. The same goes for .rxml, which can be used to build everything from HTML to Atom to whatever. .rhtml and .rxml will continue to work until Rails 3.0, though. So this is a slow phasing out. All generators and examples will start using the new aliases, though.


So you don't have to completely stop using it right this very second today since you have until Rails 3 dot Oh to phase out the old extensions but it wouldn't hurt to start getting used to the new extensions.

Labels: ,

Tuesday, January 30, 2007

Microsoft Visual Studio goes red



Not the "his filled red with rage" kin fo red either. The Sapphire Steel Software chaps did it. Ruby in Steel is now available for public consumption and lots of money. Ruby in Steel is a professional full blown Ruby IDE integrated into Microsoft's Visual Studio. If you hop on the Ruby in Steel bandwagon now, you can pick it up for the limited time price of $199. It will normally run for $249. For the $$$ adverse, there is a version you can download that doesn't have all of the whiz-bang features but it may be good enough.

If you want to know what all you can do with it, check out the feature list. Woah, now that's a list.

Helpful Mac OS X tips


Now that I've joined a team of full on Railers who all develop on Macs, I've had to jump in to the Mac OS X environment with both feet. After just staring at my Macbook for about a good 30 minutes when I first got it, I quickly had to adapt my old windows habits and shortcuts to my new Mac environment. This is mostly inspired by a recent post at Juixe TechKnow about the Mac OS X F11 key. So here's my list, feel free to add on...

press F11 - is like "Show Desktop" in Windows. It moves all windows out of the way. Pressing it again brings the windows back.

press F12 - Shows you your widgets(proper name?). I still have the defaults on mine which are the Calculator, Weather, Clock and Calendar.

press F10 - Tiles the windows of whatever program you have open. For example, if you have 4 FireFox windows open because you detest tabs, pressing F10 will tile them for you.

press F9 - Tiles all of your non-minimized windows.

press Apple+Shift+(Number 4) - All together - Changes your cursor into a little target looking icon which you can click and drag to create a screenshot of the area you selected. The screenshot gets saved to your desktop with a name like "Picture 1.png". Awesome.

Terminal Tip - while in Terminal, you don't even have to fully type out your directory or file names. Just type the first few letters and hit tab and if that file is the only one with that name, it will fill it in for you. for example, if you're in the root of your rails app, my-awesome-computer:~/desktop/appz/rails4life and you type in 'scr' and then hit the tab key, you get script. Then type 'se' and hit tab and you get 'server'.

Textmate - Best text editor ever. My tip? Use it.

So those are the tips I've found handy so far. Anything else I should know about?

Labels: , ,

Sunday, January 28, 2007

Acts_as_taggable is no more!


We all knew it was bound to happen, but Evan Weaver made it official. Weaver has killed off acts_as_taggable. Not directly, but since no one is really doing much with the acts as taggable gem or plugin these days, they might as well be dead. But don't fret, Evan's has_many_polymorphs plugin will cure what ails you. He has up plenty of good examples of how to use his plugin on his site and even how to migrate your data and database structure from using acts_as_taggable to get them ready to use his plugin. I haven't used it yet, but it looks pretty solid.

Here's the plug-in page.

Friday, January 19, 2007

For the love of your Migrations, Reset your column info !


So a co-worker and I were hacking on migrations when one of them began to fail silently. It a migration we created to just add some data and not actually do anything structurally. The migration itself looked something like this...

class IMINURDBMIGRATINGURDATAZ <> def self.up
fruits.each do |f|
a = Fruits.find_by_type(f[0])
a.smelliness = f[1]
a.save!
end
end

def self.down
end

def self.fruits
[['apple', 'like a flower'],
['orange', 'like citrus heaven'],
['kiwi', 'like sweet tarts in syrup'],
['onion', 'like how you would imagine death would smell']]
end
end



So what the up is doing is basically finding each fruit by type and setting the smelliness for each one. Looks good right? We thought so too. We ran it and it didn't work. So we popped into console (ruby/script console) and tried the exact same code there, it works. Hmm, that's odd. We blow all the tables away and try it again. No bueno. 45 minutes of head scratching and "WTF!?!" later and with the help of our boss, we figure out that it's because we kept running all of the migrations in succession. Because we were doing so and that a separate, previous migration was adding the smelliness table, we couldn't write to the smelliness column yet because we weren't reloading the column information from the object itself.

So, we fixed it by adding

Fruit.reset_column_information

right above our iteration in the up method, like so...

class IMINURDBMIGRATINGURDATAZ <> Fruit.reset_column_information

def self.up
fruits.each do |f|
a = Fruits.find_by_type(f[0])
a.smelliness = f[1]
a.save!
end
end

def self.down
end

def self.fruits
[['apple', 'like a flower'],
['orange', 'like citrus heaven'],
['kiwi', 'like sweet tarts in syrup'],
['onion', 'like how you would imagine death would smell']]
end
end



I hope this saves someone else out there some time because it wasn't easy to find.

Labels: , ,

Wednesday, October 04, 2006

Becoming addicted to testing


If you're a follower of the faith of Extreme Programming, you probably already know this. If you're aspiring to convert to XP (extreme abbreviation for Extreme Programming) and want want to just be an all round better programmer, you need to build your tests. For me, it was hard getting used to this new methodology. I was just so used to self testing that after a while it became second nature. How did I overcome this nasty habit? With the oh so awesome ZenTest gem which canbe grabbed with the usual

gem install zentest

This gem contains some great libraries, of which you can read more about here. But the reason I wrote this post today is autotest. Once you have the zentest installed, and if you're cool enough to be developing in a *nix environment, all you need to do is cd into the root of your rails app and type in

autotest -rails

and you're off and running. If you developing in Windows, you'll get an error if you just try that right off the bat. The error will mention something about not knowing the HOME value so what we need to do is set it. Still in your railsapp root directory, type in

set HOME=C:\path\to\your\rails\app

After you've set Home, go ahead and fire up autotest with the autotest -rails command and your console/DOS window will spring to life. It also wouldn't hurt to run this in the Test environment so you can also prepare your test db (you have one in your database.yml right?) with a little:

rake db:test:prepare

Now you have no reason NOT to be testing, especially with it being this easy. Enjoy :)

Sunday, October 01, 2006

Looking for Work [update]


I figured I'd throw this out there to get some exposure and to let everyone know my status. I do have a full time job but I'm currently looking for Ruby on Rails related work. I'm taking all comers so if you're serious, I'm currently up for discussion. Shoot me an e-mail via the "e-mail me" link to the right or you could even try to catch me on yahoo. If you want a peek at my resume, I'm thinking about sticking it online somewhere or I can always e-mail it out.

update: I'm not looking anymore. I'm currently and happily employed.