Methods are objects

Posted by Steven Soroka Wed, 03 Mar 2010 05:38:00 GMT

You may know that everything in Ruby is an object, but did you know that methods are objects?
  my_string = "test"
  size_method = my_string.method(:size)
  size_method.arity   # 0
  size_method.call  # 4
You can request a method's arity (basically the number of parameters it takes), call it, or even convert it to a proc and pass it to another method. Interesting, n'est pas?

how could conditionals be evil?

Posted by Steven Soroka Tue, 24 Nov 2009 17:06:00 GMT

If you've seen the Anti-If Campaign, and overheard talk about the "if" statement being evil, you might be thinking "Why? It's a core part of any language, and you couldn't write a program without them", and you'd be right.

The conditional is not evil itself, it smells of evil. The conditional invites evil not because of what it does, but because of what it shouldn't be doing. It's an indication of a code smell, and should be a metric that you track. If you don't track it, you need to at least know what to watch for.

For example: I was working on my code today, and I found a constant that violated the Single Responsibility Principle. It managed both A) the various pages in a checkout process, and B) which "next" and "previous" buttons displayed on each page.

  STATES = {
      :details => [nil, :payment],
      :payment => [:details, :final_review],
      :final_review => [:payment, :confirmation],
      :confirmation => [nil, nil]
  }

The subtle problem here is that the confirmation page doesn't have a back button, but the application can easily send the user back a page if the payment processing fails for some legitimate reason (Maybe the payment gateway is down?). This seemingly innocuous violation of SRP caused an explosion of IF statements throughout the application. One of the best examples of this is:

      if !@checkout.valid?
        if @checkout.state.previous.present?
          @checkout.state.previous!
        elsif @checkout.state.is_confirmation?
          @checkout.state.set_state(:final_review)
        end
      end

Three conditionals just to move back a page when there are errors. Once I removed the violation, the code nicely cleaned up to a single line with one reasonable conditional:

      @checkout.state.previous! if !@checkout.valid?

Conditionals themselves are not evil, they're needed to get the job done. Take a good look at your code, though; if you see a lot of "if", "case", ternary, or other conditionals, you likely have a problem somewhere. Move the conditional in to the class that's responsible for it, avoid case statements that do something different depending on some enumerator value or state.

Conditionals smell a little. In numbers, they outright stink!

Running migration code in the console

Posted by Steven Soroka Wed, 28 Oct 2009 16:15:00 GMT

This is a useful debugging/testing/fixing tool, but not a best practice for developing apps. So be careful with it. :-)

git pull --rebase 1

Posted by Steven Soroka Wed, 23 Sep 2009 00:54:00 GMT

rather than using git pull, which merges remote changes in to your local branch, learn when to use git pull –rebase to keep your history and logs clean; but only if you haven’t pushed the commits anywhere yet!

git bisect

Posted by Steven Soroka Thu, 17 Sep 2009 03:54:00 GMT

Ok, this is my first screencast, I think I may move to video blog format…  Feedback welcome.

I know it’s hard to read, you may want to make it full screen. I’ll do better on the next one. :)

paranoia and split personalities, for your models! 1

Posted by Steven Soroka Wed, 15 Jul 2009 17:18:00 GMT

  is_paranoid is exactly what I’ve been looking for.  It’s like acts_as_paranoid, but up to date with ActiveRecord’s new default scopes, which simplifies the gem quite a bit.

One idea I really like was brought on by seeing this example:

 

  class Pirate < ActiveRecord::Base
    is_paranoid :field => [:alive, false, true]
  end

  class DeadPirate < ActiveRecord::Base
    set_table_name :pirates
    is_paranoid :field => [:alive, true, false]
  end

 

Here we have the same table acting like two models based on a default scope.  So simple, yet brilliant.  Why didn’t I think of this earlier?

Consider this:

 

  class User < ActiveRecord::Base
    is_paranoid
  end

  class UserArchive < ActiveRecord::Base
    set_table_name :users
  end

 

You could use the User model for every day use, and if you need to specifically look at inactive users, you use UserArchive.  Spectacularly to the point.  Of course this has uses outside the scope of is_paranoid.

 

CabooseConf is a failure 4

Posted by Steven Soroka Wed, 06 May 2009 15:53:00 GMT

If the purpose of CabooseConf is to get experienced Ruby developers together to hack out great things together, it’s a failure.

While about half of the tables at CabooseConf have developers at them at this second, the majority of them are chatting and browsing the web. The anti-conf could use some leadership and direction; even a list of projects people are working on and who to contact.

As it stands the open-source community is getting more benefit from random people coding while ignoring the sessions they’re sitting in.

Lame.

quick irb method discovery

Posted by Steven Soroka Fri, 12 Dec 2008 21:35:00 GMT

I never like opening the docs and reading about the API. Maybe it seems too much like reading the instructions when you’re putting together a bicycle. How complicated can it be?

Most of you know already if you have a file in your home directory called .irbrc irb (and rails console) will run it on start-up. If you add some code, like so:

class Object
  def m
    methods - Object.methods
  end
end

You can get an object to tell you what methods it knows without being cluttered up by all those Object methods.

For example. I was playing with ruby-git, and I could easily get the Git::Branch object to tell me what methods it knows:

>> Git.open('.').branch('master').m
=> ["remote=", "current", "full", "full=", "name=", "update_ref", 
"stashes", "path", "merge", "path=", "archive", "in_branch", 
"readable?", "delete", "create", "checkout", "gcommit", 
"remote", "writable?"]

Fabulous!

new gems for ansi support and ignore_nil

Posted by Steven Soroka Wed, 12 Nov 2008 07:57:00 GMT

I’ve previously written about ansi support in ruby apps, as well as my ignore_nil approach to dealing with long method chains.

Well, I just released gems for both of them, ansi here, and ignore_nil here. Both can be installed as plugins if you wish.

Enjoy. :)

using with blocks to change context 2

Posted by Steven Soroka Sat, 08 Nov 2008 17:20:00 GMT

I’ve used this in other languages and it’s really handy, and I’ve been missing it a bit from ruby/rails, until now. :)

It’s not unusual to end up carrying object references in Rails, even if they’re only used briefly.

review = current_user.reviews.build
review.feedback = params[:review_feedback]
review.save
review.publish! unless review.spam?

But wait, is that review object used somewhere else in the method? Blocks are so much cleaner. We could use returning() to clean it up…

returning current_user.reviews.build do |review|
  review.feedback = params[:review_feedback]
  review.save
  review.publish! unless review.spam?
end
# ^ review object returned from block

which is nicer to look at, and more-clearly defines the lifespan of the review variable. But why do we need it at all? it’s still repetitive, and we’re supposed to keep our code DRY!

enter with().

with current_user.reviews.build do
  feedback = params[:review_feedback]
  save
  publish! unless spam?
end
# ^ results of publish! or nil returned here

By changing the context–making self the review temporarily–we really clean up the code, and there’s no ambiguity on the lifespan of the review, and no needless repetition! Here’s the code for it:

class Object
  def with(obj, &b)
    obj.instance_eval &b
  end
end

We can even apply this to to Ryan Bates’ famous anonymous scopes and scope .conditions! patch to clean the code up even more. Here it is if you’re not familiar with it:

def find_products
  returning Product.scoped({}) do |scope|
    scope.conditions! "products.name LIKE ?", "%#{keywords}%" unless keywords.blank?
    scope.conditions! "products.price >= ?", minimum_price unless minimum_price.blank?
    scope.conditions! "products.price <= ?", maximum_price unless maximum_price.blank?
    scope.conditions! "products.category_id = ?", category_id unless category_id.blank?
  end
end

But we can do even better. :)

def find_products
  with Product.scoped({}) do
    conditions! "products.name LIKE ?", "%#{keywords}%" unless keywords.blank?
    conditions! "products.price >= ?", minimum_price unless minimum_price.blank?
    conditions! "products.price <= ?", maximum_price unless maximum_price.blank?
    conditions! "products.category_id = ?", category_id unless category_id.blank?
    self
  end
end

maybe we could even modify returning() to use our version, since it’s a lot cleaner. :)

class Object
  def returning(value, &block)
    if block_given?
      value.instance_eval &block
    else
      yield(value)
    end
    value
  end
end

and then

def find_products
  returning Product.scoped({}) do
    conditions! "products.name LIKE ?", "%#{keywords}%" unless keywords.blank?
    conditions! "products.price >= ?", minimum_price unless minimum_price.blank?
    conditions! "products.price <= ?", maximum_price unless maximum_price.blank?
    conditions! "products.category_id = ?", category_id unless category_id.blank?
  end
end

I LOVE Ruby! I think I’ll submit a patch to Rails….