Notes posted to Ruby on Rails

RSS feed
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.

November 4, 2009
2 thanks

Using hidden tags

To use an <input type=“hidden” /> tag, use the following syntax:

<% form_for(@post) do |f| %>
  <%= f.hidden_field :user_id, { :value => user.id } %>
<% end %>
November 3, 2009
0 thanks

Setting a custom Content type

The given example seems to be broken. The :mime_type option as well as the [] access on the Mime::Type class are both not working.

The following code allows the custom setting of content types as intended by the original example:

class  PostsController < ActionController::Base
  def show
     @post = Post.find(params[:id])

    respond_to do |format|
      format.html
      format.ics { render :text => post.to_ics, :content_type => Mime::Type.lookup("text/calendar")  }
      format.xml { render :xml => @people.to_xml }
    end
  end
end
November 2, 2009
0 thanks

To use the memcached gem

If you use the mem_cache_store it will use “memcache-client” to talk to the actual cache. memcache-client is a pure Ruby library that is bundled with rails. There is a Ruby/C library called “memcached” that uses native bindings to talk to memcache, and it is reportedly (http://blog.evanweaver.com/files/doc/fauna/memcached/files/README.html) up to a 100 times faster than memcache-client. To use that instead,

# in shell
$ sudo gem install memcached --no-rdoc --no-ri

# in config/production.rb
require 'memcached'
config.action_controller.cache_store =
  :mem_cache_store, Memcached::Rails.new("localhost:11211")

This feature (to pass a configured MemCache-like object to cache_store=) is available since rails 2.3.3 and will hopefully be documented in 2.3.5.

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
0 thanks

Outputs name even if condition is false

Please note that if the condition is false, link_to_if will still output the name given as a plain text (as documented above). If you want nothing printed at all, you’ll have to stay with the old and trusty:

link_to "Login", ... if @current_user.nil?
October 30, 2009
2 thanks

Outputs name even if condition is false

Please note that if the condition is false, link_to_if will still output the name given as a plain text (as documented above). If you want nothing printed at all, you’ll have to stay with the old and trusty:

link_to "Login", ... if @current_user.nil?
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 27, 2009 - (>= v1.0.0)
1 thank

Only works within a transaction

Of course, this has to be done in a transaction, like so:

# we've loaded user earlier and did some checks which took some time
# to make sure, updates made meanwhile by other threads don't lead to
# optimistic locking errors here, we do this when finally suspending
User.transaction do
  user.lock!
  user.suspended = 1
  user.save!
end
October 23, 2009
2 thanks

Setting child_index while using nested attributes mass assignment with prototype

First of all, drogus idea really helped me. I’m not using jQuery, therefore I implemented my own version:

link_to_function

def add_object_link(name, where, render_options)
  html = render(render_options)

  link_to_function name, %{
    Element.insert('#{where}', #{html.to_json}.replace(/index_to_replace_with_js/g, new Date().getTime()));
  }
end

Using add_object_link

<%= add_object_link 'Add asset', 'assets', :partial => 'assets/asset', :object => Asset.new, :locals => { :f => f } %>
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 17, 2009
1 thank

Re: Passing parameters to before_filter

I am not sure I get your “method 1” alec-c4; won’t that define the method each time the before_filter is called? Why not just define the method in the controller?

You can pass parameters or call protected methods with instance_eval:

before_filter :only => :show do |controller|
  controller.instance_eval do
    redirect_to edit_object_path(params[:id])
  end
end
October 16, 2009 - (>= v2.1.0)
1 thank

No numbers or symbols

“Kyle”, “Дети”, “Niños”, “Quan-lu”, “た ち”

validates_format_of :first_name, :with => /^([^\d\W]|[-])*$/
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 15, 2009
2 thanks
October 14, 2009
2 thanks

build_association deletes existing dependent record

Surprisingly (at least I was surprised), when an associated record exists, the build_association method immediately NULLs the foreign key in the database.

So if you write a singleton “new” action for the association in the obvious way (calling build_association), then just visiting the page will disconnect an existing associated record. This violates the principle that a GET request shouldn’t affect the database.

To avoid this, you can check for an existing association first, and redirect to the show action.

October 12, 2009
0 thanks

Use it to solve FixtureClassNotFound errors.

If you are using a non standard table name by means of set_table_name in your model:

class MyClassName < ActiveRecord::Base
  set_table_name "mytablename"
end

then you will get FixtureClassNotFound errors when you try to use fixtures in you unit tests. To solve this use set_fixture_class inside your test:

require 'test_helper'
class MyClassNameTest < ActiveSupport::TestCase
  set_fixture_class :mytablename => MyClassName  
end

and rename your fixture file to mytablename.yml

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

Use camelize with singular words

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

"business".camelize     # => "Business"
October 3, 2009
0 thanks

sort_by

array.sort_by {|element| [element.foo, element.bar.name, element.baz]}

Taken from http://redcorundum.blogspot.com/2006/10/using-sortby-instead-of-sort.html

October 2, 2009
2 thanks

Making ActiveRecord models readonly

To force an ActiveRecord model to be read only you can do something along these lines:

class DelicateInfo < ActiveRecord::Base

 def readonly?
  true
 end

end

When you try to save the model it will raise an ActiveRecord::ReadOnlyRecord exception:

info = DelicateInfo.first
info.save # => ActiveRecord::ReadOnlyRecord

Note, however, that destroy and delete will still work on the model unless you intercept those calls

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 %>

October 1, 2009
0 thanks

This code prevent's frome redirect_to(:back) looping

Prevent redirect_to(:back) looop

begin

# loop check
if session[:last_back] != request.env['HTTP_REFERER']
  redirect_to(:back)
  session[:last_back] = request.env['HTTP_REFERER']
else
  # raise on error
  raise ActionController::RedirectBackError
end

rescue ActionController::RedirectBackError

# fallback on loop or other :back error
redirect_to(:action => :index)

end

October 1, 2009
0 thanks

ActiveResource validation is a little different

Given the following model on the remote end:

class Person < ActiveRecord::Base
 validates_presence_of :first_name, :last_name, :email
end

And this ActiveResource on the client end:

class Person < ActiveResource::Base
 self.site = "http://api.people.com:3000/" 
end

Validation messages will only be returned after an attempted save call on the client end - eg:

person = Person.new( :first_name => 'Billy', :emails => "william@anotherplanet.co.za" )
person.valid?                 # => true
person.errors.full_messages   # => []
person.save                   # => false
person.valid?                 # => false
person.errors.full_messages   # => ["Last name can't be empty"]

In ActiveResource::Base it is suggested that you can perform client site validation with something like this:

class Person < ActiveResource::Base
 self.site = "http://api.people.com:3000/"
 protected
  def validate
   errors.add("last name", "can't be empty") if last_name.blank?
  end
end
October 1, 2009
0 thanks

Update attributes on mulitple models

Updates attributes on multiple models, saves each if validations pass.

def update_multiple

  @items = Item.find(params[:item_ids])
  @items.each do |item|
    item.attributes = params[:item].reject { |k,v| v.blank? }
  end
  if @items.all?(&:valid?)
    @items.each(&:save!)
    flash[:notice] = "Updated items!"
    redirect_to items_path
  else
    flash[:notice] = "Please enter valid data."
    render :action => 'edit_multiple'
  end
end
September 30, 2009
2 thanks

See also: ActiveRecord::Base#increment

This is a class-level method. For the instance-level equivalent see: ActiveRecord::Base#increment

item = Item.find(1)
item.foo_count # => 0
Item.increment_counter(:foo_count, 1)
item.foo_count # => 0
item.reload
item.foo_count # => 1
item.increment(:foo_count)
item.foo_count # => 2
September 29, 2009
1 thank

Passing parameters to before_filter

I’ve found on the net 2 ways to pass parameters to before_filter:

method 1:

before_filter do |c|
c.class.module_eval do
private
def custom_filter
authorize(args)
end
end
end
before_filter :custom_filter

method 2:

before_filter do |c|
c.send(:authorize, args)
end