Flowdock

Good notes posted to Ruby on Rails

RSS feed
August 28, 2008
8 thanks

Get year to show in descending order (Today to 1920 for example)

The way people think of time start and end would be 1920 to today. This made me think “but I want it show the current year first then down.” Well it’s as simple as swapping the start_year and end_year.

date_select :date, :start_year => Date.current.year, :end_year => 1920

# => 2008, 2007, 2006, 2005 ... 1920
August 28, 2008
3 thanks

Moved to ActiveRecord::Calculations::ClassMethods

It appears that this method has simply been moved to the module ActiveRecord::Calculations::ClassMethods. You can still use the example listed in the docs here, it will simply call the method ActiveRecord::Calculations::ClassMethods#count.

August 28, 2008 - (v2.1.0)
3 thanks

When using RESTful routes

I had issues using expire_page with RESTful controllers when expiring anything other than the index action because I was basing my expire_page calls off this example.

The solution is to use my_resource_path for example when User with id 5 is updated, you would have to do:

expire_page user_path(user) # or
expire_page formatted_user_path(user, :xml)

This may apply to early versions but I have only tested on v2.1.0

August 27, 2008
16 thanks

Only attr_accessible attributes will be updated

If your model specified attr_accessible attributes, only those attributes will be updated.

Use attr_accessible to prevent mass assignment (by users) of attributes that should not be editable by a user. Mass assignment is used in create and update methods of your standard controller.

For a normal user account, for example, you only want login and password to be editable by a user. It should not be possible to change the status attribute through mass assignment.

class User < ActiveRecord::Base
  attr_accessible :login, :password
end

So, doing the following will merrily return true, but will not update the status attribute.

@user.update_attributes(:status => 'active')

If you want to update the status attribute, you should assign it separately.

@user.status = 'active'
save
August 26, 2008
6 thanks

Prototype hinted_text_field application_helper

Place this in your helper. It will show a message inside the text box and remove it when someone clicks on it. If they don’t enter a value when they leave the field it’ll replace the message. (Requires javascript :defaults).

def hinted_text_field_tag(name, value = nil, hint = "Click and enter text", options={})
  value = value.nil? ? hint : value
  text_field_tag name, value, {:onclick => "if($(this).value == '#{hint}'){$(this).value = ''}", :onblur => "if($(this).value == ''){$(this).value = '#{hint}'}" }.update(options.stringify_keys)
end

# inside form_for example

hinted_text_field_tag :search, params[:search], "Enter name, brand or mfg.", :size => 30  
# => <input id="search" name="search" onblur="if($(this).value == ''){$(this).value = 'Enter name, brand or mfg.'}" onclick="if($(this).value == 'Enter name, brand or mfg.'){$(this).value = ''}" size="30" type="text" value="Enter name, brand or mfg." />
August 26, 2008
8 thanks

Customizing prompt

The :prompt option not only accepts a boolean value. It can also be given a string to define another than the standard prompt ‘Please select’. Referring to the example it could read:

collection_select(:post, :author_id, Author.find(:all),
                  :id, :name_with_initial,
                 {:prompt => 'Please select the author of this post'})
August 25, 2008 - (v1.1.6 - v2.1.0)
3 thanks

Delete collections with check box tags

Following autonomous’ directions works wonders on /edit but needs slight modifications when dealing with pagination on /index.

In a /index type listing page we can no longer assume that the list of ids coming back represents changes to all objects so we need to provide some context, that the list of object modifications in our params array is a list of modifications for some set of objects.

We can only assume subsets because pagination or filtering may reduce the set of objects we’re working on.

In our case we had a user management page which listed all users and showed whether they were activated or not. The following code is what we used to ensure that modifications to the first page of objects wouldn’t affect all the other pages.

index.rhtml

<% @users.each do |user| %>
  <%= hidden_field_tag('seen[]', user.id) -%>
  <%= check_box_tag 'activated[]', user.id -%>
<% end %>

role_controller.rb

def index
  if request.post?
    activated_ids = params[:activated].collect {|id| id.to_i} if params[:activated]
    seen_ids = params[:seen].collect {|id| id.to_i} if params[:seen]

    if activated_ids
      seen_ids.each do |id|
        r = User.find_by_id(id)
        r.activated = activated_ids.include?(id)
        r.save
      end
    end
  end
end
August 25, 2008 - (>= v2.1.0)
6 thanks

Rake tasks for gem dependencies

You can manage installation and other tasks for these dependencies with rake tasks, for example:

rake gems:install              # Installs all required gems for this application
rake gems:unpack               # Unpacks the specified gem into vendor/gems

To get all rake tasks about gems:

rake -T gems
August 25, 2008 - (v2.1.0)
4 thanks

Disable default date

If you want a date selector that initially doesn’t have a date selected you can pass it the option :include_blank.

date_select("project", "due_date", :include_blank => true)
August 25, 2008
5 thanks

%w(true false) != [true, false]

Don´t confuse the right:

validates_inclusion_of :published, :in => [true, false]

with the wrong:

validates_inclusion_of :published, :in => %w(true false)

cause:

%w(true false) == ["true", "false"]
August 23, 2008
3 thanks

Include two level has many model example

class Issue < ActiveRecord::Base

  has_many :journals
end

class Journal < ActiveRecord::Base
  belongs_to :issue
  has_many :details, :class_name => "JournalDetail", :dependent => :delete_all
end

class JournalDetail < ActiveRecord::Base
  belongs_to :journal
end

<hr/>

issue = Issue.find(:first, :include => {:journals => :details}

log record follow:

SELECT * FROM `issues` LIMIT 1
SELECT `journals`.* FROM `journals` WHERE (`journals`.`journalized_id` IN (1) and `journals`.`journalized_type` = 'Issue' AND (dustbin <> 1))
SELECT `journal_details`.* FROM `journal_details` WHERE (`journal_details`.journal_id IN (1,2,876177898,935815637))

when execute follow code, then not build sql sentent:

issue.journals
issue.journals[0].details
August 22, 2008
4 thanks

Doesn't add an index

Typically you will want to have an index on foreign keys but this method doesn’t assume that. Outside of the create_table block you should follow this with add_index :

add_index :table_name, :goat_id
# and, if polymorphic:
add_index :table_name, :goat_type
August 22, 2008 - (>= v2.1.0)
13 thanks

Specifying :include no longer necessarily joins the association

Before Rails 2.1, adding an :include=>[:association] in your find method caused ActiveRecord to generate SQL using a join. Since 2.1, it MAY NOT execute as a join.

The join executes a large query and returned potentially duplicate records for a one-to-many association. After 2.1, the query is broken down and eager-loaded using an additional query per association, passing the set of id’s to load, and avoiding the duplicate rows.

The new method eliminates duplicates, but can incur more database overhead. If you are loading a very large set of records (more than a “page”), you may need to “force” the join or use find_by_sql instead.

When you specify a “table.column” syntax within a

:conditions=>["child.name=?", name]  

or

:order=>'child.name'

then ActiveRecord will build the older, full query with the join because you are referencing columns from another table to build. This will cause the duplicate rows to reappear.

Whenever you reference a column from another table in a condition or order clause, ALWAYS use the table name to prefix the column, even if it not ambiguous among the tables involved. Otherwise the query will not be executed as a join and you will receive an SQL error referencing the “missing” column.

You can “force” a join by adding a reference to the other tables in your :conditions or :options parameters, even if the test or sort is irrelevant.

August 20, 2008
5 thanks

Iterate and join blocks

Following LacKac’s idea, we can write render_join (useful to render a collection with a small chunks of code, where a render :partial + :spacer_template would be overkill):

def render_join(collection, join_string, &block)
  output = collection.collect do |item| 
    capture(item, &block)
  end.join(join_string)
  concat(output, block.binding)
end

An example of use:

<% render_join(@items, '<br />') do |item| %>
   <p>Item title: <%= item.title %></p>
<% end %>
August 20, 2008
3 thanks

Current Database Name

The MySQL database adapter extends this and allows you to call

ActiveRecord::Base.connection.current_database

to get the current databases name. Useful when you are actively changing the database you are connected to and sometimes need to check the current one.

http://apidock.com/rails/ActiveRecord/ConnectionAdapters/MysqlAdapter/current_database

August 19, 2008 - (v2.1.0)
8 thanks

preload_associations manually

Usually you preload associations using :include => [ ... ]. In Rails 2.1 each association is fetched with a separate query. Something like:

Post.find(:all, :include => [:tags, :user])

will produce 3 queries - each for posts, tags and users.


But sometimes you have a complex query, which uses :joins => :other_association and conditions between multiple tables, but not the ones you need to include. Then everything is mixed back in one query like in old versions of Rails.

Another case may be when it is not possible to use :include at all, for example while using find_by_sql, but you still want/need to preload associated records.

In rails 2.1 find uses preload_associations internally, when it is possible (There are no joins or conditions between tables).

So then you can preload asociations manually from within your model:

class Post < ActiveRecord::Base

has_many :tags
belongs_to :user
...

def self.find_complex_with_includes
  posts = find_by_sql(...) # or find(:all, :joins => .....)
  preload_associations(posts, [:tags, :user])
  posts
end

end

and then do

@posts = Post.find_complex_with_includes

August 19, 2008 - (v2.1.0)
6 thanks

Namespace or modules in routes

If you have grouped controllers into a module, e.g. admin then you can specify this in the routes using the namespace method:

map.namespace :admin do |admin|
  admin.resources :categories
end

which will map the categories resource giving urls like

/admin/categories/

/admin/categories/new

It will also generate the named routes such as new_admin_category_url and admin_category_path

August 19, 2008
6 thanks

script/generate syntax

To add a post_id field to a comments table, run this:

script\generate migration add_post_id_to_comment post_id:integer

See that it´s not the table name(plural), but the model name(singular),<br /> and post_id:references, does not works like in create_table.

This is the generated migration:

class AddPostIdToComment < ActiveRecord::Migration
 def self.up
   add_column :comments, :post_id, :integer
 end

 def self.down
   remove_column :comments, :post_id
 end
end
August 17, 2008
7 thanks

Using sweepers in script/runner

If you need to use some of your sweepers in a script/runner script or some rake task you can use this snipped:

require 'action_controller/test_process'

sweepers = [ProductSweeper, UserSweeper]

ActiveRecord::Base.observers = sweepers
ActiveRecord::Base.instantiate_observers

controller = ActionController::Base.new
controller.request = ActionController::TestRequest.new
controller.instance_eval do
  @url = ActionController::UrlRewriter.new(request, {})
end

sweepers.each do |sweeper|
  sweeper.instance.controller = controller
end

Your script will fire the ActiveRecord callbacks defined in that sweepers and you can use expire_cache, expire_fragment and also the routing helpers you have defined (hash_for_user_path, hash_for_product_path, etc.).

August 17, 2008
6 thanks

Explanation about :dependent option

It may seem that :dependent option is only used when the object that has the collection is destroyed, but it is also used every time a associated object is deleted, so if you use

object.collection.delete(associated_object)

your object will be deleted, destroyed or nullified, depending on the value of :dependent option.

With has_many :through associations this option is ignored at least in versions up to 2.1.0, so even if you set :dependent option to :destroy, your join objects will be deleted, not firing any callbacks you have set on destroy events.

If you need to act when your join model is deleted you can use a sweeper or an observer and the association callbacks like this:

# product.rb
class Product
  has_many :categorizations
  has_many :categories, :through => :categorizations,
    :before_remove => :fire_before_remove_in_categorizations

private
  def fire_before_remove_in_categorizations(category)
    categorization = self.categorizations.find_by_category_id(category.id)
    categorization.class.changed
    categorization.class.notify_observers(:before_remove, categorization)
  end
end

# categorization_sweeper.rb
# do not forget to load this sweeper during initialization
class CategorizationSweeper < ActionController::Caching::Sweeper
  observe Categorization

  def before_remove(categorization)
    # expire_cache, expire_fragment, whatever
  end
end

One thing you should be aware of it is that you are using before_remove, so you have to be careful because your record may be not be removed (another callback raising an exception or the database not deleting the record) so you can not be sure your object will be delete. Expiring caches is safe because even if your record is not destroyed your cache will be regerated correctly.

You can not use after_remove, because at that point the join model do not exists anymore, so you can not fire its callbacks. But you have the model id and the associated model id, so if you do not need expiring caches maybe you can use this approach (expiring caches can be only done in a sweeper or in a controller, but with after_remove you are bound to your model).

August 14, 2008
4 thanks

Calls attribute setter for each key/value in the hash

This is a convenience to set multiple attributes at the same time. It calls the “setter” method

self.attribute=(value)

for each key in the hash. If you have overridden the setter to add functionality, it will be called.

This also allows you to create non-table attributes that affect the record. For instance, a full_name=() method could parse the string and set the first_name=() and last_name() accordingly.

August 14, 2008 - (v2.0.0 - v2.1.0)
7 thanks

with password md5 encrypted

If you are afraid to let your plain password on the code, you can do this instead:

 require 'digest'

 class AdminController < ApplicationController
  before_filter :authenticate

  def authenticate
    authenticate_or_request_with_http_basic('Administration') do |username, password|
      md5_of_password = Digest::MD5.hexdigest(password)
      username == 'admin' && md5_of_password == '5ebe2294ecd0e0f08eab7690d2a6ee69'
    end
  end
end

where ‘5ebe2294ecd0e0f08eab7690d2a6ee69’ is the md5 of the word ‘secret’.

You can get your own with this free webservice: <br /> http://qi64.appspot.com/md5/secret (replace ‘secret’ with your secret word).

August 14, 2008
5 thanks

Optional classes

This piece of syntax saves me allot of time. Note the if statement.

Code example

content_tag(:div, "Hello World", :class => ("active" if i_am_an_active_item?))
August 13, 2008
7 thanks

Update element after remote call

Not mentioned in the documentation, you can add :update option to the remote_form_for and pass the id of element you’d like to update after ajax action as you do with link_to_remote, for example:

<% remote_form_for "comment", :update => "form" } do |f| %>
  # your form here
<% end %>

Or

<% remote_form_for "comment", :update => {:success => "form", :failure => "errors"} do |f| %>
  # your form here
<% end %>
August 13, 2008
7 thanks

Re: Helper method taking a block

The same using the ActionView::Helpers::TagHelper#content_tag and ActionView::Helpers::CaptureHelper#capture methods:

def render_tree(collection, &block)
  concat(
    content_tag(:ul,
      collection.collect { |item|
        content_tag(:li, capture(item, &block))
      }.join("\n")
    ),
    block.binding
  )
end

The benefit is that it’s easier to improve with html attributes (just add a hash of options to the content_tag call) and it makes just one call to concat (which probably makes it faster).

August 13, 2008 - (v1.2.6 - v2.1.0)
3 thanks

Insertion/Deletion callbacks

All ActiveRecord associations except for has_many :through support callbacks for pre- and post-insertion/deletion via the following, self-documenting parameters:

Adding to an Association

:before_add

:after_add

Removing from an Association

:before_remove

:after_remove

The flexibility that these callbacks offer is quite handy, but I’ll demonstrate with a silly example: logging each insertion and deletion.

class Ship < ActiveRecord::Base

  has_many :pirates, 
    :after_add => :say_hello,
    :before_remove => :say_goodbye

  private

  def say_hello(pirate)
    STDOUT.write("hello #{pirate.name} ")
  end

  def say_goodbye(pirate)
    STDOUT.write("goodbye #{pirate.name} ")
  end

end

Now, we’ll see confirmation when we add/remove Pirates in the console from our ship (and yes, this must be the ghetto):

>> jolly_roger = PirateShip.new

=> #<Ship id: nil, created_at: nil, updated_at: nil>
>> jolly_roger.pirates << Pirate.create(:name => 'Black Bart')
hello Black Bart => [#<Pirate id: 1, name: "Black Bart", created_at: "2008-07-29 14:41:13", updated_at: "2008-08-11 11:51:25">
>> jolly_roger.pirates.first.delete
goodbye Black Bart => []
August 12, 2008
6 thanks

Helper method taking a block

Following the similar egzample by autonomous, here’s a simpler version when you just need to write a flexible helper method that takes a block.

For example, suppose you have a method that renders a tree:

def render_tree(ary, &block)
  concat("<ul>", block.binding)
  for elem in ary
    concat("<li>", block.binding)
    yield elem
    concat("</li>", block.binding)
  end
  concat("</ul>", block.binding)
end

You can use it in your view, eg:

<% render_tree(@objects) do |elem| -%>
  <%= elem.title -%>
  <%= link_to 'delete', elem -%>
<% end -%>

that would return for egzample:

<ul>
  <li>
    Test title
    <a href="delete">/elems/1</a>
  </li>
</ul>

Testing concat

To test such helper methods, use the following pattern (a utility method added to your Rspec/unit test suite:

def render_for(root, options = {})
  _erbout = ''
  render_tree(root, options) do |node|
    _erbout.concat(node.title)
  end
  _erbout
end

and test like this (RSpec example):

it "should return abc" do
  render_for(object).should == 'abc'
end
August 12, 2008 - (>= v2.1.0)
4 thanks

polymorphic_url and namespaces and nested resources

You can use polymorphic_url with namespaces and nested resources using array as parameter:

polymorphic_url([:admin, @post])

will return:

admin_post_url(@post)
August 12, 2008
12 thanks

Overriding the default div class="fieldWithErrors"

By default fields that are invalid are wrapped in:

<div class="fieldWithErrors">
  <input type="text" name="blah">
</div>

To override and wrap in spans instead of divs place the following in your environment.rb:

ActionView::Base.field_error_proc = Proc.new { |html_tag, instance| "<span class=\"fieldWithErrors\">#{html_tag}</span>" }

or to not use wrapping at all:

ActionView::Base.field_error_proc = Proc.new { |html_tag, instance| "#{html_tag}" }