Methods are objects
my_string = "test" size_method = my_string.method(:size) size_method.arity # 0 size_method.call # 4
how could conditionals be evil?
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
endThree 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
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
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
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
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
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
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
endYou 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
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
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 blockwhich 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 hereBy 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
endWe 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
endBut 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
endmaybe 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
endand 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
endI LOVE Ruby! I think I’ll submit a patch to Rails….
