Flowdock

Recent notes

RSS feed
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 20, 2008
2 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 18, 2008
0 thanks

Last element of an array alternative

You can also access the last element of an array with -1

[ "w", "x", "y", "z" ][-1]  #=> "z"
August 17, 2008
6 thanks

Re: Convert an Array of Arrays to a Hash using inject

If you’re sure you have a two-level array (no other arrays inside the pairs) and exactly two items in each pair, then it’s faster and shorter to use this:

array = [['A', 'a'], ['B', 'b'], ['C', 'c']]
hash = Hash[*array.flatten]

For more than two-level deep arrays this will give the wrong result or even an error (for some inputs).

array = [['A', 'a'], ['B', 'b'], ['C', ['a', 'b', 'c']]]
hash = Hash[*array.flatten]
# => {"A"=>"a", "B"=>"b", "C"=>"a", "b"=>"c"}

But if you’re running Ruby 1.8.7 or greater you can pass an argument to Array#flatten and have it flatten only one level deep:

# on Ruby 1.8.7+
hash = Hash[*array.flatten(1)]
# => {"A"=>"a", "B"=>"b", "C"=>["a", "b", "c"]}
August 17, 2008
4 thanks

Regexes with groups and split

When you use a Regex with capture groups, all capture groups are included in the results (interleaved with the “real” results) but they do not count for the limit argument.

Examples:

"abc.,cde.,efg.,ghi".split(/.(,)/)
=> ["abc", ",", "cde", ",", "efg", ",", "ghi"]
"abc.,cde.,efg.,ghi".split(/(.)(,)/)
=> ["abc", ".", ",", "cde", ".", ",", "efg", ".", ",", "ghi"]
"abc.,cde.,efg.,ghi".split(/(.(,))/)
=> ["abc", ".,", ",", "cde", ".,", ",", "efg", ".,", ",", "ghi"]
"abc.,cde.,efg.,ghi".split(/(.(,))/, 2)
=> ["abc", ".,", ",", "cde.,efg.,ghi"]
"abc.,cde.,efg.,ghi".split(/(.(,))/, 3)
=> ["abc", ".,", ",", "cde", ".,", ",", "efg.,ghi"]
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
2 thanks

Using observers with script/runner

If yoo need to use some observers but you don’t want then in the initialization you can do this in your script:

ActiveRecord::Base.observers = [ProductObserver, UserObserver]
ActiveRecord::Base.instantiate_observers

Your observers should work during the execution of the script.

(For the sweepers the solution is a bit different, look at my Caching module note for the complete solution).

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 17, 2008
1 thank

collection update

in the FirmsController

@firm.people.update(params[:people].keys,params.values)

in the View

<% form_for(@firm) do |f| %>

<%= f.error_messages %>
<%= f.text_field :name %>
<%@firm.people.each do |person|%>
<%fields_for "people[]", person do |pf|%>
      <%= pf.text_field :name %>
<%end%>
<%= f.submit "Save" %>

<%end%>

August 17, 2008
1 thank

collection update

in the FirmsController

@firm.people.update(params[:people].keys,params.values)

in the View

<% form_for(@firm) do |f| %>

<%= f.error_messages %>
<%= f.text_field :name %>
<%@firm.people.each do |person|%>
<%fields_for "people[]", person do |pf|%>
      <%= pf.text_field :name %>
<%end%>
<%= f.submit "Save" %>

<%end%>

August 16, 2008
1 thank

Examples of Setting the Prefix and Suffix

Both of the following will work for setting the prefix or suffix:

class Mouse < ActiveRecord::Base
  self.table_name_prefix = 'forum_'
end

and

class Mouse < ActiveRecord::Base
  def self.table_name_prefix
    'forum_'
  end
end
August 15, 2008
9 thanks

anything matcher

The anything matcher will match any ruby object:

1.should == anything
nil.should == anything
'string'.should == anything

var.should_receive(:method).with(param1, anything)
August 15, 2008
4 thanks

Cheking if a number is prime?

It’s a class for generating an enumerator for prime numbers and traversing over them.

It’s really slow and will be replaced in ruby 1.9 with a faster one.

Note: if you just want to test whether a number is prime or not, you can use this piece of code:

class Fixnum
  def prime?
    ('1' * self) !~ /^1?$|^(11+?)\1+$/
  end
end

10.prime?
August 15, 2008
8 thanks

Optional Argument for detect/find [Not Documented]

detect/find’s optional argument lets you specify a proc or lambda whose return value will be the result in cases where no object in the collection matches the criteria.

classic_rock_bands = ["AC/DC", "Black Sabbath","Queen", "Ted Nugent and the Amboy Dukes","Scorpions", "Van Halen"]
default_band = Proc.new {"ABBA"}
classic_rock_bands.find(default_band) {|band| band > "Van Halen"}
=> "ABBA"

or

random_band = lambda do
  fallback_bands = ["Britney Spears", "Christina Aguilera", "Ashlee Simpson"]
  fallback_bands[rand(fallback_bands.size)]
end
classic_rock_bands.find(random_band) {|band| band > "Van Halen"}
=> "Britney Spears"
August 15, 2008
0 thanks

Convert a Hash to an Array of Arrays using map

Although you‘ll always have to_a and it‘s faster, this trick is too cool to ignore…

Convert a Hash to an Array of Arrays using Enumerable#map

August 15, 2008
3 thanks

Convert a Hash to an Array of Arrays using map

Although you’ll always have to_a and it’s faster, this trick is too cool to ignore…

When the block is omitted, collect or map uses this implied block: {|item| item}, which means when applied on an hash without a block, collect/map returns an array containing a set of two-item arrays, one for each key/value pair in the hash. For each two-item array, item 0 is the key and item 1 is the corresponding value.

burgers = {"Big Mac" => 300, "Whopper with cheese" => 450, "Wendy's Double with cheese" => 320}

burgers.map
=> [["Wendy's Double with cheese", 320], ["Big Mac", 300], ["Whopper with cheese", 450]]

see also:

August 15, 2008
2 thanks

Using any? on Empty Arrays and Hashes

When applied to an empty array or hash, with or without a block, any? always returns false. That’s because with an empty collection, there are no values to process and return a true value.

August 15, 2008
3 thanks

Testing Arrays for nils with Enumerable#all?

When the block is omitted, all? uses this implied block: {|item| item}.

Since everything in Ruby evaluates to true except for false and nil, using all? without a block on an array is effectively a test to see if all the items in the collection evaluate to true (or conversely, if there are any false or nil values in the array).

Using all? without a block on a hash is meaningless, as it will always return true.

August 15, 2008
1 thank

Enumerable#all? and Hashes

When used on a hash and a block is provided, all? passes each key/value pair as a two-element array to the block, which you can “catch” as either:

  1. A two-element array, with the key in element 0 and its corresponding value in element 1, or

  2. Two separate items, the first being the key, the second being the corresponding value.

August 15, 2008
4 thanks

Using all? on Empty Arrays and Hashes

When applied to an empty array or hash, with or without a block, all? always returns true. That’s because with an empty collection, there are no values to process and return a false value. so, watch out, if your array or hash is empty for any reason you will get a true which might not be what you expect it to be.

August 14, 2008 - (v1_8_6_287)
1 thank
August 14, 2008 - (v1_8_6_287)
11 thanks

Convert an Array to a Hash

The Hash.[] method converts an even number of parameters to a Hash. (The Hash[] method depends on the Hash class, but don’t confuse the method with the class itself). For example:

Hash['A', 'a', 'B', 'b']
# => {"A"=>"a", "B"=>"b"}

You can convert an array to a hash using the Hash[] method:

array = ['A', 'a', 'B', 'b', 'C', 'c']
hash = Hash[*array]
# => {"A"=>"a", "B"=>"b", "C"=>"c"}  

The * (splat) operator converts the array into an argument list, as expected by Hash[].

You can similarly convert an array of arrays to a Hash, by adding flatten:

array = [['A', 'a'], ['B', 'b'], ['C', 'c']]
hash = Hash[*array.flatten]  
# => {"A"=>"a", "B"=>"b", "C"=>"c"}

This also comes in handy when you have a list of words that you want to convert to a Hash:

Hash[*%w(
  A a
  B b
  C c
)]
# => {"A"=>"a", "B"=>"b", "C"=>"c"}
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
13 thanks

Testing an options hash receives certain parameters

This method is very useful for testing methods that use the ruby idiom of accepting a hash with configurable options.

class Example
  def self.find(options = {})
    ...
  end
end

We can use hash_including to ensure that certain options are passed in when mocking it.

Example.should_receive(:find).with(hash_including(:conditions => 'some conditions'))

Example.find(:conditions => 'some_conditions', :order => 1)
# => Passes expectation

Example.find(:order => 1)
# => Fails expectation

This can also be used to great effect with the anything matcher. For example:

hash_including(:key => anything)

hash_including(anything => 'value')
August 14, 2008
5 thanks

Convert an Array of Arrays to a Hash using inject

Converting an array of arrays to a hash using inject:

array = [['A', 'a'], ['B', 'b'], ['C', 'c']]

hash = array.inject({}) do |memo, values|
  memo[values.first] = values.last
  memo
end

hash
# => {'A' => 'a', 'B' => 'b', 'C' => 'c'}
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).