Courtesy of Twitter

Posts Tagged ‘rails’

Faking It

Monday, July 14th, 2008

Today I was doing the one thing I truly, truly love doing and that’s complaining about writing RSpec tests. I came across a doozy of a problem involving RSpec testing and faking subdomains. Here’s a stripped down version of what I did:

def login_as(user)
  session["user"] = users(user).id
  request.host = User.find(session["user"]).company.domain + ".example.com"
end

Just pop that into your spec/spec_helper.rb and then you can use login_as(:user) which will find the fixture with the name of “user” and then go from there to setting your faked host as being from, for example, blah.example.com.

Pretty simple, shame Google didn’t turn up any relevant results without me having to dig deeper than usual.

A bit of refactoring love

Friday, June 13th, 2008

Find, Find, Find, Find, I don’t think so…

As explained in previous posts, Rails controllers have 7 default actions (index, new, create, show, edit, update, destroy). Four of these seven actions make the same find call, Model.find(params[:id]) and this tutorial is to tidy that up so you’re not repeating yourself over four different actions. To clean this up we’ll just call a before filter:

class ForumsController < ApplicationController
  before_filter :find_forum
 
  # Actions go here
 
  private
    def find_forum 
      @forum = Forum.find(params[:id])
    end
end

Now you may be thinking, “Why are we doing that? That’s 5 lines!”. Think about if you wanted to change the find statement, and now you’ll begin to picture why. Changing one line is much easier than changing four. For example, if I wanted to find forums by their slugs instead of an ID I would simply change @forum = Forum.find(params[:id]) to @forum = Forum.find_by_slug(params[:id]). Of course, for this to work with the restful routes helpers the way we expect it to (e.g. forum_path(@forum) -> /forums/the-first-forum), we’ll need to re-define #to_param in our model:

class Forum
  def to_param
    slug
  end
end

Common Lookups

Sometimes you’ll have data initialised for your forms and you’ll want to initialise this data multiple times. Instead of repeating yourself like this:

class ForumsController
 
  def new
    @forum = Forum.new
    @something_special = SomethingSpecial.find(:all, :order => "id DESC")
  end
 
  def create
    @forum = Forum.new(params[:forum])
    if @forum.save
      flash[:success] = "A forum has been created."
      redirect_to @forum
    else
      flash[:failure] = "A forum could not be created."
      @something_special = SomethingSpecial.find(:all, :order => "id DESC")
      render :action => "new"
    end
  end
 
end

You could instead have:

class ForumsController
 
  def new
    @forum = Forum.new
    common_lookups
  end
 
  def create
    @forum = Forum.new(params[:forum])
    if @forum.save
      flash[:success] = "A forum has been created."
      redirect_to @forum
    else
      flash[:failure] = "A forum could not be created."
      common_lookups
      render :action => "new"
    end
  end
 
  private
    def common_lookups
      @something_special = SomethingSpecial.find(:all, :order => "id DESC")
    end
end

Shorter Routing

One last thing that I’d like to show you is shorter routing. Ever since the restful routing helpers were added, routing to specific controllers and their actions has become easier and easier. Rails 2.0 makes it extremely easy, but first we’ll see how far we’ve come:

  1. <%= link_to @forum, { :controller => “forums”, :action => “show”, :id => @forum.id } %>
  2. <%= link_to @forum, forum_path(@forum) %>
  3. <%= link_to @forum, forum_path %>
  4. <%= link_to @forum, @forum %>
  5. <%= link_to @forum %>

As long as there’s a #to_s method in the Forum model it will insert that as the phrase shown to the user for the link. All of the above should produce the same URL, with the exception of the first which will produce /forums/show/1, and going down the list they’re just shorter ways of writing the same thing. If you had nested routes such as forum_topic_path(@forum, @topic) you could do <%= link_to @topic, [@forum, @topic] %> as the extremely short version of it. The reason why we can’t do just <%= link_to [@forum, @topic] %> is because this will show the to_s version of @forum, followed immediately by the to_s version of @topic.

Updating a select box based on another

Tuesday, May 20th, 2008

In two projects now I’ve had to use code where I update a select box (machines) in regards to what’s selected in the first (customers). mark[oz] from #rubyonrails asked how to do this last week and I told him I would write a blog post the following night. 5 days later, here it is.

I use this when I’m creating a new call and the user needs to select which customer and then which machine. I have the following in my CallsController:

def new
 
@call = Call.new
 
@customers = Customer.find(:all, :order => "name ASC")
 
end

Relatively simple. We instantiate a new call object so the form knows where to go, and we define @customers so the first select box has some information.

Of course in our Customer model we have:

class Customer < ActiveRecord::Base
has_many :machines
end

And the machine model:

class Machine < ActiveRecord::Base
belongs_to :customer
end

These define the relationships used within the system.

Next is the new.html.erb page itself.

<strong>New Call</strong>
<% form_for @call do |@f| %>
<%= render :partial => "form" %>
<%= submit_tag "Create" %>
<% end %>

And a stripped-down version of the form partial:

<%= @f.label "customer_id" %>
<%= @f.select "customer_id", @customers.map { |c| [c.name, c.id] }%>
<%= observe_field "call_customer_id", :url => by_customer_machines_path, :with => "customer_id" %>
<%= @f.label "machine_id" %>
<%= @f.select "machine_id", "Please select a customer" %>

Right now that we have the form all set up, lets set up our machines controller

class MachinesController < ApplicationController
#CRUD operations go here
 
def by_customer
@machines = Machine.find_by_customer_id(params[:customer_id])
end
end

And our by_customer.html.erb:

<%= options_from_collection_for_select(@machines, "id", "name") %>

And finally the config/routes.rb:

map.resources :customers do |customer|
customer.resources :machines
end
 
map.resources :machines, :collection => { :by_customer => :post }

Now when we select a customer from the top drop down, the machines drop down should be populated too.

ActiveResource & ZenDesk

Wednesday, May 14th, 2008

With many, many thanks to Frederick Cheung, who without this would’ve been way more painful and time-consuming. My original question and our discussion can be found here.

At my new job we signed up with ZenDesk, which acts as our helpdesk/ticketing system for our clients who sign up to our site and buy our product. Because ZenDesk uses emails instead of plain ol’ usernames for authentication, Ruby chucked a fit when we tried doing stuff like:

class User < ActiveResource::Base
self.site = "http://ouremail@ourwebsite.com:ourpassword@ourplace.zendesk.com"
end

Ruby’s URI class just didn’t like that first @ sign in there! So Fred originally recommended we try to encode it, %40. That didn’t work. Then the next morning Fred suggests doing this:

class User < ActiveResource::Base
self.site = "http://ourplace.zendesk.com"
 
def (self.site).user
"ouremail@ourwebsite.com"
end
 
def (self.site).password
"ourpassword"
end
 
end

And ‘lo and behold the thing worked!

So for all you savvy kids using Rails’ ActiveResource and trying to make it play nice with ZenDesk, that’s how we did it.

Then I went a little further with the refactoring and just made a ZenDesk model:

class ZenDesk < ActiveResource::Base
 
self.site = "http://ourplace.zendesk.com"
 
def (self.site).user
"ouremail@ourwebsite.com"
end
 
def (self.site).password
"ourpassword"
end
 
end
end

And then got the models I wanted to inherit from that.

class User < ZenDesk
end

Brilliant!

Also one more note. If you’re going through and testing out creating Organisations/Users in ZenDesk through ActiveResource, don’t forget to delete them as you go! It’s time-consuming clicking edit, and then the delete button for 50 odd objects… but of course you could do this:

for i in original_object_id..last_object_id
User.find(i).destroy
end

Which, if you did it, say, all in the same day like I did, should delete all the users within that range.

Self-Referential Relationships

Tuesday, April 29th, 2008

I’ve seen this question asked time and time again, so I’m going to write a short tutorial about how to do it. The question is self-refferential relationships for a model, often the User model to determine the relationship between two different users. I’ll assume that you’ve already got a Rails application and at least a User model for this. We’ll use a has_many :through relationship to define which users are related to who.

Let’s generate a model for the relationship: script/generate model relationship. This will generate a migration which we’ll create our relationships table with.

db/migrate/xxx_create_relationships.rb

class CreateRelationships < ActiveRecord::Migration
def self.up
 
create_table :relationships do |t|
t.integer :user_id, :friend_id
t.string :relationship_type
end
end
 
def self.down
drop_table :relationships
end
end

And that should do us. Run rake db:migrate to add the table in.

Now we go into our User model and we add in the following:

app/models/user.rb

class User < ActiveRecord::Base
has_many :relationships
has_many :friends, :through => :relationships
end

And in our relationship.rb model:

app/models/user.rb

class Relationship < ActiveRecord::Base
belongs_to :friend, :class_name => "User"
belongs_to :user
end

And now we see if it works:

script/console

>> u = User.find_by_name("Ryan")
=> #
>> u.friends << User.find_by_name("Charlie")
=> #
>> u.save
=> true

Now what if we want to change that relationship field? We’ll add in two methods in to the user model to find the relationship for a specific user.

app/models/user.rb

  def to_i
id
end
 
def find_relationship_with(user)
Relationship.find_by_friend_id(user.to_i)
end

The first method, to_i, will return just the id for the user. The reason why we do this is because in the next method, find_relationship_with we pass in a single argument, user. Now because we’ve defined the to_i method on our User model, this means we can either pass in a user id or a user object to this method, and it will call to_i on whatever we pass in, ending up with an id. When the method’s done, it will return a relationship object which you can then modify.

Best of luck.

Administration Namespacing

Sunday, March 16th, 2008

A while after completing work at SeaLink, Tom asked me about my forum hobby project and why it wasn’t working. This led to me working on it for a few days and getting it all up to scratch again, and this involved moving it over to Rails 2.0 (that’s how long I hadn’t worked on it for), and using the awesomeness that is namespacing.

Namespacing is where you have some controllers in a separate area of your site. In my example, I have an admin folder in app/controllers which contains all the controllers for the administration section of my site, and all the relevant actions. Just inside the app/controllers directory, I have the other controllers which do all the basic stuff such as showing forums, basically anything a standard user can do.

It seems to be a fairly common question asked in places, so I figured if I sat down and wrote this, I would have something to send to people, much like my Restful Routes tutorial, which ideally should’ve covered namespacing too.

First of all we’re going to create our namespace. To do that, we open up config/routes.rb and specify our namespace:

map.namespace(:admin) do |admin|
admin.resources :forums, :topics, :posts
end

Now what this does is defines some routes for us. If you’ve seen map.resources you’ll know that this defines routes for us, for example doing map.resources :forums will define methods such as forums_path which is the same as { :controller => “forums”, :action => “index” } and forum_path(@forum) which is the same as { :controller => “forums”, :action => “show”, :id => @forum.id }. These methods really are lifesavers and save you a hell of a lot of typing. The routes defined by this namespace method however are prefixed with whatever argument you pass it, in this case we’ve passed it :admin so it’s going to give us routes like admin_forums_path, which is the same as { :controller = > “admin/forums”, :action => “index” }, and as you can see again saves us a lot of typing.

Now that we have our namespace, we can create our subfolders. These subfolders are placed in app/controllers and app/views and are given the same name as the namespace, admin. So go ahead and do that now. In app/controllers/admin is where we place our controllers. As an example, here’s what my forums controller’s edit and update actions look like:

class Admin::ForumsController < Admin::ApplicationController
before_filter :store_location, :only => [:index, :show]
 
def edit
 
@forum = Forum.find(params[:id])
 
@forums = Forum.find(:all, :order => "title") - [@forum] - @forum.descendants
 
end
 
def update
 
@forum = Forum.find(params[:id])
 
if @forum.update_attributes(params[:forum])
 
flash[:notice] = "Forum has been updated."
 
redirect
 
else
 
flash[:notice] = "Forum has not been updated."
 
render :action => "edit"
 
end
 
end
 
end

What I really want to show you in here is only the first line the class is defined as Admin::ForumsController, which shows that we’re namespacing it. We don’t have to define the Admin prefix anywhere. What we do have to define however is our non-existant Admin::ApplicationController. In my code, I’ve defined my own Admin::ApplicationController as a means of calling methods that should be called before all admin actions, such as my non_admin_redirect method, which is defined in lib/authenticated_system.rb and goes something like this:

  def non_admin_redirect
 
    if !is_admin?
 
      flash[:notice] = "You need to be an admin to do that."
 
      redirect_back_or_default(:controller => "/accounts", :action => "login")
 
    end
 
  end

To define your Admin::ApplicationController, make a file in app/controllers/admin called application_controller.rb. Even though the main application controller is defined as application.rb in app/controllers, that file is automatically loaded by Rails. If we named our application_controller to just application.rb, it would not be automatically loaded because Rails only looks for application.rb and files ending in _controller.rb in the app/controllers directory, so we name ours application_controller.rb so it plays nice with Rails.

In here we define our class, layout and helper:

class Admin::ApplicationController < ApplicationController
  layout "admin"
  helper "admin"
  before_filter :non_admin_redirect
end

I’ve defined a new layout here because my admin layout is different to my main layout, but still includes some elements from it (thanks to nested_layouts)

The before_filter is triggered before every action in the admin controller to make sure it’s an admin doing the action rather than a standard user.

And that’s all there is to it, really. It’s all pretty simple. Now all you’ve gotta do is generate your views. Remember to place them in app/views/admin/the_controller’s_name, otherwise you’ll run into problems.

It seems I forgot to mention how it’s supposed to work when you’re calling the method to go to the namespaced path, well that’s simple. If you have a forum you would like to edit, the correct method is edit_admin_forum_path(forum_object), because you want to edit, in the namespace of admin, a certain forum. For paths not requiring a prefix, such as the show and index actions, they are admin_forum_path(forum_object) and admin_forums_path respectively.

For an action such as an update action, it would be admin_forum_path(forum_object) with a :method => :put option specified in whatever you’re using. Usually you won’t have to do this, because the form_for helper would do it for you, but in some cases you might have to.

Rails Forum

Tuesday, February 19th, 2008

Please note: This is now hosted at Github. Please use this page to get the latest changeset.

As a result of Tom (0.01%) and mine’s (99.9%) work on-and-off for a few months, we’ve managed to get to a first release of Rails Forum. The goal is to “be inspired by” and supercede PunBB and I think we’re almost there.

The PunBB about page states that:

Some features that have so far not been implemented are: private messaging, file attachments in posts, polls, linking to off-site avatars, advanced text formatting controls, subforums etc etc. Some of these features might still get implemented, just not in the near future.

We have private messaging, and we use Gravatar for our avatar system and we have subforums. There’s only three and a half things on that list (polls, real off-site avatars, adavanced text formatting controls and file attachments) that we have still to do. Polls are easy enough and probably my next goal, off-site avatars will be a bit tricky but I think we’ll manage and advanced text formatting controls are as easy as installing something like TinyMCE. File attachments can be done with attachment_fu.

What I would like to see is the whole site converted over to HAML as I enjoy working in it’s strictly tabbed environment and it’s just so much easier to read as you’re not reading what is basically the same thing twice. Another thing I would like to see is some bloody tests written for it! We were bad and only wrote a few tests. Pagination would be lovely too!

To run this forum system, you’ll need to download and extract it onto your computer. Then install Ruby and then Rubygems. After that, do

gem install mongrel rails chronic RedCloth tzinfo

To install mongrel and rails. Hopefully that’s all you’ll need.

My First Rails Patch

Monday, January 14th, 2008

Today I wrote my first Rails patch: http://dev.rubyonrails.org/ticket/10799

I was infuriated at this method over the weekend when I found out that it didn’t use the current year, but instead always guessed Feb. to be 28 days regardless of the current year. My patch uses the current year.

+1 it if you think it’s a good idea.

This patch is now verified! This means that it will be more than likely to be included inside of Rails 2.1, whenever that’s released! Thanks to DevF and nzkoz from #rails-contrib on freenode for suggesting that I write tests as well as telling me a cool few tricks.

This patch is now in Rails! http://dev.rubyonrails.org/changeset/8715

More Migration Sexiness - remove_columns & add_columns

Wednesday, December 19th, 2007

After seeing a post on errtheblog about them making migrations sexier, I was inspired on the train ride this morning to write something similar. I had a table and I wanted to drop many columns for it and I ended up writing something like:

 remove_column :table, :column_1
 
remove_column :table, :column_2
 
remove_column :table, :column_3
 
remove_column :table, :column_4...

and by now you’re starting to get the idea. I have to not only type out remove_column four times, but also the table name! What a waste of time!

So I hacked up some code and put it into lib/custom_methods.rb.

module ActiveRecord
  module ConnectionAdapters
    class MysqlAdapter
      def remove_columns(table_name, *columns)
        columns.each { |column| remove_column table_name, column }
      end
 
      def add_columns(table_name, type, *columns)
        columns.each { |column| add_column table_name, column, type}
      end
    end
  end
end

So now instead of the monstrosity above I can now type:

remove_columns :table, :column_1, :column_2, :column_3, :column_4

to remove all the columns.

Also inspired by remove_columns was add_columns (already spied by the observant few). There’s a little difference in add_columns compared with add_column, the type is now the second argument instead of the third:

add_columns :table, :string, :column_1, :column_2, :column_3

More Migration Sexiness - remove_columns & add_columns

Wednesday, December 19th, 2007

After seeing a post on errtheblog about them making migrations sexier, I was inspired on the train ride this morning to write something similar. I had a table and I wanted to drop many columns for it and I ended up writing something like:

 remove_column :table, :column_1
 
remove_column :table, :column_2
 
remove_column :table, :column_3
 
remove_column :table, :column_4...

and by now you’re starting to get the idea. I have to not only type out remove_column four times, but also the table name! What a waste of time!

So I hacked up some code and put it into lib/custom_methods.rb.

 module ActiveRecord
module ConnectionAdapters
class MysqlAdapter
def remove_columns(table_name, *columns)
columns.each { |column| remove_column table_name, column }
end
def add_columns(table_name, type, *columns)
columns.each { |column| add_column table_name, column, type}
end
end
end
end

So now instead of the monstrosity above I can now type:

remove_columns :table, :column_1, :column_2, :column_3, :column_4

to remove all the columns.

Also inspired by remove_columns was add_columns (already spied by the observant few). There’s a little difference in add_columns compared with add_column, the type is now the second argument instead of the third:

add_columns :table, :string, :column_1, :column_2, :column_3