Recent good notes

RSS feed
November 23, 2009
3 thanks

Make directory if not exists

If the directory already exists, mkdir raises exception. To prevent this:

Dir.mkdir(dir) unless File.exists?(dir)
November 18, 2009 - (>= v1_8_6_287)
5 thanks

Example

code:

class Klass
  def set(string)
    var_name = "@#{string}"  # the '@' is required
    self.instance_variable_set(var_name, 'bar')
  end
  def puts_foo
    puts @foo
  end
end
k = Klass.new
k.puts_foo  # nil
k.set('foo')
k.puts_foo  # 'bar'
November 16, 2009
3 thanks
November 12, 2009
8 thanks

Use hash form of updates argument

The examples are unfortunate, because passing a string as the updates argument is an invitation to SQL injection attacks. Don’t do this!

Billing.update_all("author='#{author}'")

Use the hash form of updates instead:

Billing.update_all(:author => author)

Then the SQL adapter will quote everything safely. Even if [you think] you’re sure there’s no quoting issue, it’s better to cultivate the habit of using the hash form just in case you missed something.

Same with conditions–use the hash or array form rather than a string if there are variables involved.

BTW, to do this and give options, of course you’ll need to put the braces back in:

Billing.update_all({:author => author},
                   ['title like ?', "#{prefix}%"])
November 5, 2009
10 thanks

define_method with parameters

Just to be clear, you can do this:

define_method(:my_method) do |foo, bar| # or even |*args|
  # do something
end

This means same as:

def my_method(foo, bar)
  # do something
end

If you want to define method with parameters that have default values, you need to get a bit more creative and do something like this:

define_method(:my_method) do |foo, bar|
  bar ||= {}
  # do something
end
November 5, 2009 - (>= v2.1.0)
7 thanks

Named scope better than conditions

In modern versions of Rails, in most cases a named_scope is a better alternative to using :conditions on your has_many relations. Compare:

class User
  has_many :published_posts, :conditions => {:published => true}
end
user.published_posts

with:

class Post
  named_scope :published, :conditions => {:published => true}
end
class User
  has_many :posts
end
user.posts.published

It’s better because the Post’s logic (“am I published?”) should not be coupled within User class. This makes it easier to refactor: e.g. if you wanted to refactor the boolean :published field into a :status field with more available values, you would not have to modify User class. Having to modify User when you refactor some implementation detail of Post class is clearly a code smell.

This also applies to :order, :group, :having and similar options.

October 30, 2009
7 thanks

How FormBuilders work

What, you were expecting documentation? :)

An excellent survey of how FormBuilders work is here:

http://code.alexreisner.com/articles/form-builders-in-rails.html

October 30, 2009
3 thanks

Map-like Manipulation of Hash Values

Let’s say you want to multiply all values of the following hash by 2:

hash = { :a => 1, :b => 2, :c => 3 }

You can’t use map to do so:

hash.map {|k, v| v*2 }   # => [6, 2, 4]

However, with merge you can:

hash.merge(hash) {|k,v| v*2 }   => {:c=>6, :a=>2, :b=>4}

(The above is Ruby 1.8, in Ruby 1.9 the order is preserved.)

October 28, 2009
7 thanks

#blank?

The opposite of this is #blank?

October 27, 2009
4 thanks

#present?

The opposite of this is #present?

October 22, 2009 - (>= v2.1.0)
4 thanks

Update statement won't include all attributes with ActiveRecord::Dirty

With the addition of ActiveRecord::Dirty, the update statement will only feature changed columns, as opposed to the comment of railsmonk below.

October 20, 2009
3 thanks

See Dir#glob

See glob for more usage information and comments.

October 15, 2009
4 thanks

Have check_box checked by default

In addition to comment below, you can make a column with default value so in your forms it will be enabled by default and behave correctly with validation errors unlike :checked => true

in your migration

add_column :accounts, :ssl_enabled, :boolean, :default => 1
October 7, 2009
4 thanks

Hash#without

Here’s a small helper for doing the “opposite” of this method:

class Hash
  def without(*keys)
    cpy = self.dup
    keys.each { |key| cpy.delete(key) }
    cpy
  end
end

h = { :a => 1, :b => 2, :c => 3 }
h.without(:a)      #=> { :b => 2, :c => 3 }
h.without(:a, :c)  #=> { :b => 2 }
October 7, 2009
3 thanks

Streaming XML with Builder

To generate larger XMLs, it’s a good idea to a) stream the XML and b) use Active Record batch finders.

Here’s one way of doing it:

def my_action
  @items = Enumerable::Enumerator.new(
    Item.some_named_scope,
    :find_each,
    :batch_size => 500)

  respond_to do |format|
    format.xml do
      render :text => lambda { |response, output|
        extend ApplicationHelper

        xml = Builder::XmlMarkup.new(
          :target => StreamingOutputWrapper.new(output),
          :indent => 2)
        eval(default_template.source, binding, default_template.path)
      }
    end
  end
end

The Builder template does not need to be modified.

October 5, 2009
3 thanks

collect/map

If you’d like to use this method for something like Enumerable#collect, you are looking at the wrong place. This method will return the initial integer, not the values from the block.

a = 20.times { |n| n * 2 } #=> 20

Instead, use Range#collect:

a = (0...20).collect { n * 2 }
October 5, 2009
3 thanks

Use camelize with singular words

To make the business example work, use camelize instead of classify:

"business".camelize     # => "Business"
October 2, 2009
5 thanks

form_tag with named route and html class

<% form_tag position_user_card_path(@user, card), :method => :put, :class => ‘position-form’ do %>

September 14, 2009
6 thanks

Pluralize Without Count

Helper method that returns the word without the count.

application_helper.rb

def pluralize_without_count(count, noun, text = nil)
  if count != 0
    count == 1 ? "#{noun}#{text}" : "#{noun.pluralize}#{text}"
  end
end

Example usage:

_form.html.erb

<%= pluralize_without_count(item.categories.count, 'Category', ':') %>
September 9, 2009
3 thanks

Will discard any order option

order_by(:created_at).find_each == FAIL!!!

class ActiveRecord::Base
  # normal find_each does not use given order but uses id asc
  def self.find_each_with_order(options={})
    raise "offset is not yet supported" if options[:offset]

    page = 1
    limit = options[:limit] || 1000

    loop do
      offset = (page-1) * limit
      batch = find(:all, options.merge(:limit=>limit, :offset=>offset))
      page += 1

      batch.each{|x| yield x }

      break if batch.size < limit
    end
  end
end
August 20, 2009
4 thanks

Symbol#to_proc

@tadman - or simply defining:

class Symbol
  def to_proc
    proc { |obj, *args| obj.send(self, *args) }
  end
end
August 18, 2009
3 thanks

Auto-submitting select tag

If you want your form to be submitted when user selects something, use:

:onchange => "this.form.submit();"

For example:

select_tag "people", "<option>David</option>", :onchange => "this.form.submit();"
August 13, 2009
3 thanks

With multiple parameters

Example

remote_function(
   :url => some_remote_function_path, 
   :with => "'key1='+$('elem_id').value +'&key2='+$('elem_id').value+ '&this_elem_value='+value"
) 
August 13, 2009
3 thanks

with_exclusive_scope example by Ramon broken in latest Rails

The example Ramon gave works within the model itself, i.e.

class Article
  def closed
    with_exclusive_scope { find(:all) }
  end
end

However, from what I can see, this approach does not work within a controller. You may be wanting to use

Article.with_exclusive_scope { find(:all) }  #=> "SELECT * FROM 'articles'

But it will error out about find(:all) not existing on ArticlesController. To get around this, you must now do

Article.with_exclusive_scope { Article.find(:all) }  #=> "SELECT * FROM 'articles'

In otherwards, find(:all) isn’t being executed in the scope of the model, but in the controller in which its called.

Took me a minute or two to find out, so I thought I’d let others know.

August 7, 2009
7 thanks

Join multiple tables

It’s easy to join multiple tables too. In this case we have:

class Article
  belongs_to :feed
end

class Feed
  has_many :articles
  belongs_to :source
end

class Source
  has_many :feeds
  # t.bool :visible
end

You can search articles and specify a condition on the sources table.

Article.find(:all, 
  :conditions => { :feeds => { :sources => { :visible => true }}}, 
  :joins => [:feed => :source],
August 7, 2009
3 thanks

Documentation

This method only returns a cache manager object of sorts, to see what you can do with it, see ActiveSupport::Cache::Store.

August 6, 2009
4 thanks

Documentation bug

When adding the :target option, the documentation states that you should user :href_options like so:

auto_link(post_body, :href_options => { :target => '_blank' })

However, I could only get it to work using :html instead:

auto_link(post_body, :html => { :target => '_blank' })

I’m using Rails 2.2.2, but I believe that this also happens for more recent version .

July 28, 2009
8 thanks

Return True

As is the case with the before_validation and before_save callbacks, returning false will break the callback chain. For example, the expire_cache_id method will not run if Rails.cache.expire returns false (as it will if the key is not cached with memcache).

Returning False Example (Bad)

after_save :expire_cache_by_name
after_save :expire_cache_by_id

def expire_cache_by_name
  Rails.cache.expire("my_object:name:#{self.name}")
end

def expire_cache_by_id
  Rails.cache.expire("my_object:#{self.id}")
end

Returning True Example (Good)

def expire_cache_by_name
  Rails.cache.expire("my_object:name:#{self.name}")
  return true
end

def expire_cache_by_id
  Rails.cache.expire("my_object:#{self.id}")
  return true
end
July 27, 2009 - (>= v2.2.1)
3 thanks

Overriding default validation messages

Before Rails 2.2 you could globally customize the default validation error messages by changing AR::Base.default_error_messages. The messages have now been moved to i18n, so to customize them in 2.2 and up, just create a locales/ folder in your config/ folder, copy activerecord/lib/active_record/locale/en.yml (in Rails source) to config/locales/en.yml, and then change the strings inside. As szeryf indicated below, the strings of interest are activerecord.errors.messages.

July 23, 2009 - (>= v1.0.0)
7 thanks

Format meaning

%a - The abbreviated weekday name (“Sun”)

%A - The full weekday name (“Sunday”)

%b - The abbreviated month name (“Jan”)

%B - The full month name (“January”)

%c - The preferred local date and time representation

%d - Day of the month (01..31)

%H - Hour of the day, 24-hour clock (00..23)

%I - Hour of the day, 12-hour clock (01..12)

%j - Day of the year (001..366)

%m - Month of the year (01..12)

%M - Minute of the hour (00..59)

%p - Meridian indicator (“AM” or “PM”)

%S - Second of the minute (00..60)

%U - Week number of the current year, starting with the first Sunday as the first day of the first week (00..53)

%W - Week number of the current year, starting with the first Monday as the firstday of the first week (00..53)

%w - Day of the week (Sunday is 0, 0..6)

%x - Preferred representation for the date alone, no time

%X - Preferred representation for the time alone, no date

%y - Year without a century (00..99)

%Y - Year with century

%Z - Time zone name

%% - Literal “%” character