Flowdock

Good notes posted to Ruby on Rails

RSS feed
April 16, 2009
3 thanks

Example

This function can be used to pass the ID of selected item, for example:

# with select or collection_select helpers:
{ :onchange => remote_function(:url => { :action => 'do_smth' }, :with => "'id=' + $('the_id').value") }

# and grab ID in controller action as usually: 
YourModel.find(params[:id])
April 16, 2009
3 thanks

Various use cases

Example

user = User.new
user.name = 'Akhil Bansal'
user.save

user =  User.new(:name => 'Akhil')
user.save

User.new do |u|
  u.name = 'Akhil'
  u.save
end
April 9, 2009
11 thanks

Define handlers in order of most generic to most specific

The later the definition of the rescue handler, the higher the priority:

rescue_from Exception, :with => :error_generic
rescue_from Exception::ComputerOnFire, :with => :panic

Declaring the Exception catch-all handler last would have the side-effect of precluding any other handlers from running.

This is what is meant by being “searched…from bottom to top”.

April 9, 2009
4 thanks

Method has moved to ActionController::Rescue::ClassMethods module

This method has simply moved, still works the same way in 2.3+

New location: ActiveSupport::Rescuable::ClassMethods#rescue_from

April 8, 2009
13 thanks

Setting child_index while using nested attributes mass assignment

When using nested attributes mass assignment sometimes you will want to add new records with javascript. You can do it with pure javascript, but if HTML is long your javascript will be long and messy and it will not be DRY as probably you already have a partial for it.

So to add a partial dynamically you can do something like that (notice string “index_to_replace_with_js”):

link_to_function

def add_object_link(name, form, object, partial, where)
  options = {:parent => true}.merge(options)
  html = render(:partial => partial, :locals => { :form => form}, :object => object)
  link_to_function name, %{
    var new_object_id = new Date().getTime() ;
    var html = jQuery(#{js html}.replace(/index_to_replace_with_js/g, new_object_id)).hide();
    html.appendTo(jQuery("#{where}")).slideDown('slow');
  }
end

js method in one of helpers (from minus mor plugin)

def js(data)
  if data.respond_to? :to_json
    data.to_json
  else
    data.inspect.to_json
  end
end

This method will generate link adding generated partial to html.

The thing that is not mentioned in docs is how to set child_index. You must add it as an argument in hash.

Example of partial

<% form.fields_for :tasks, task, :child_index => 
       (task.new_record? ? "index_to_replace_with_js" : nil) do |tasks_form| %>
  <% tasks_form.text_field :name %>
<% end %>

Using add_object_link

<% form_for :project do |form| %>
  <div id="tasks">
    <%# displaying existing tasks %>
  </div>

  <%= add_object_link("New task, form, Task.new, "task", "#tasks") %>

<% end %>

Thanks to child_index after insertion it will change indexes to current time in miliseconds so added tasks will have different names and ids.

April 8, 2009
6 thanks

Superclass of OrderedHash

Note that in Rails 2.3, OrderedHash changed from being a subclass of Array to a subclass of Hash. This is contrary to what the documentation says above.

April 6, 2009
5 thanks

HTML entities in options

Unfortunately everything is escaped with ERB::Util#html_escape. Your only option is either manually construct options or compeletely overwrite this method.

April 6, 2009
6 thanks

Array clustering

Sometimes you don’t want to mangle sequence of an array and just want to group adjacent values. Here’s a nice method to do so (drop it in your initializers directory or something):

module Enumerable
  # clumps adjacent elements together
  # >> [2,2,2,3,3,4,2,2,1].cluster{|x| x}
  # => [[2, 2, 2], [3, 3], [4], [2, 2], [1]]
  def cluster
    cluster = []
    each do |element|
      if cluster.last && yield(cluster.last.last) == yield(element)
        cluster.last << element
      else
        cluster << [element]
      end
    end
    cluster
  end
end

Similarly you can do the clustering on more complex items. For instance you want to cluster Documents on creation date and their type:

Document.all.cluster{|document| [document.created_on, document.type]}
April 6, 2009
3 thanks

Take care when writing regex

When you want to validate a field for a continuous string you’d probably write something like this (if it’s really early in the morning and you didn’t have your coffee yet):

validates_format_of :something => /\w/

At the first sight it looks like it’s working because something = “blahblahblah” is valid. However, so is this: something = “blah meh 55”. It’s just that your regex matched a substring of the value and not the whole thing. The proper regex you’re looking for is actually:

validates_format_of :something => /^\w$/
April 6, 2009
3 thanks

Assets hosts

You can also setup assets hosts in enviroments:

config.action_controller.asset_host = "http://your-assets-server.com"
April 3, 2009
13 thanks

The docs are in AR::Base

The docs you’re looking for are in ActiveRecord::Base

April 1, 2009
6 thanks

Ordering of format blocks is important

The order in which your format blocks appear, like:

format.html { } format.js { }

are used to infer priority in cases where the appropriate format is ambiguous.

March 31, 2009
3 thanks

Override fieldWithErrors markup in Rails > v2

The code posted by @hosiawak will still work in recent versions of Rails, but maybe a more current, idiomatic way to do it is to stick this inside the Rails::Initializer block in environment.rb (obviously you’ll also need to restart your server to pick up the config change):

config.action_view.field_error_proc = Proc.new {|html_tag, instance| 
  %(<span class="fieldWithErrors">#{html_tag}</span>)}
March 27, 2009
4 thanks

Usage

Here’s how to use it, just so it’s perfectly clear:

skip_before_filter :method_to_skip, :only => [:method_name]
March 27, 2009
4 thanks

multiple filter example

actually you can have it even shorter with:

before_filter :authorize, :set_locale, :except => :login
March 23, 2009
4 thanks

So, how do you enable db sessions?

First, run:

rake db:sessions:create

Then, run your pending migrations. This will create the migration you need to run in order to create the sessions table.

Second, go into config/environment.rb and uncomment or put in:

config.action_controller.session_store = :active_record_store
config.action_controller.session = {
   :session_key => '_your_session_name_here',
   :secret      => 'SOME_CRYPTOGRAPHICALLY_SECURE_KEY'
 }

Third, get yourself a secure key with:

rake secret

And finally, paste your new key into the :secret above.

March 21, 2009
4 thanks

Passing optional arguments with defaults to a named_scope

An easy way to do this. (This also shows how you can use joins in a named_scope as well.)

Class User << ActiveRecord::Base
belongs_to :semester 

named_scope :year, lambda { |*year|
  if year.empty? || year.first.nil?
    { :joins => :semester, :conditions => ["year = #{CURRENT_SEMESTER}"]}
  else
    { :joins => :semester, :conditions => ["year = #{year}"]}
  end
  }

end

You can then call:

User.year     # defaults to CURRENT_SEMESTER constant
User.year()  # same as above
User.year(nil)  # same as above; useful if passing a param value that may or may not exist, ie, param[:year]
User.year(2010)
March 21, 2009
7 thanks

Use helpers in your ActionMailer views

It’s very easy to give your mailer access to helpers:

# Let your mailer user the ApplicationHelper methods
class MyMailer < ActionMailer::Base
  helper :application
end
March 20, 2009
9 thanks

Use the current URL, with changes

You can use the current URL, whatever it is, with changes, as in:

# Create a link to the current page in RSS form
url_for(:overwrite_params => {:format => :rss})

This can be super-helpful because it preserves any GET params (like search parameters)

March 12, 2009
3 thanks
March 12, 2009
12 thanks

User a block to extend your associations

You can use blocks to extend your associations with extra methods.

code sample

has_many :children, :dependent => :destroy do
  def at(time)
    proxy_owner.children.find_with_deleted :all, :conditions => [
      "created_at <= :time AND (deleted_at > :time OR deleted_at IS NULL)", { :time => time }
    ]        
  end      
end

Model.children.each # do stuff
Model.children.at( 1.week.ago ).each # do old stuff

you must use ‘proxy_owner’ to link back to your model.

March 10, 2009 - (>= v2.1.0)
8 thanks

Use lambda to avoid caching of generated query

If you’re using a named_scope that includes a changing variable you need to wrap it in a lambda to avoid the query being cached and thus becoming unaffected by future changes to the variable, example:

named_scope :translated, :conditions => { :locale => I18n.locale }

Will always return the same locale after the first hit even though I18n.locale might change. So do this instead:

named_scope :translated, lambda { { :conditions => { :locale => I18n.locale } } }

Ugly, but at least it’s working as we expect it…

March 4, 2009
10 thanks

Differences between normal or-assign operator

Differences between this method and normal memoization with ||=:

  • memoize works with false/nil values

  • Potential arguments are memoized

Take the following example:

def allowed?
  @allowed ||= begin
    # Big calculation
    puts "Worked"
    false
  end
end

allowed? # Outputs "Worked"
allowed? # Outputs "Worked" again

Since @allowed is set to false (this is also applicable with nil), the ||= operator will move on the the next statement and will not be short-circuited.

When you use memoize you will not have this problem.

def allowed?
  # Big calculation
  puts "Worked"
  false
end
memoize :allowed?

allowed? # Outputs "Worked"
allowed? # No output

Now, look at the case where we have parameters:

def random(max=10)
  @random ||= rand(max)
end

random     # => 4
random     # => 4 -- Yay!
random(20) # => 4 -- Oops!

Better use memoize again!

def random(max=10)
  rand(max)
end
memoize :random

random     # => 6
random     # => 6 -- Yay!
random(20) # => 12 -- Double-Yay!
random     # => 6 -- Head a'splode
March 4, 2009
5 thanks

Usage

This defines attr_accessors at a class level instead of instance level.

class Foo
  cattr_accessor :greeting
end

Foo.greeting = "Hello"

This could be compared to, but is not the same as doing this:

class Bar
  class << self
    attr_accessor :greeting
  end
end

Bar.greeting = "Hello"

The difference might not be apparent at first, but cattr_accessor will make the accessor inherited to the instances:

Foo.new.greeting #=> "Hello"
Bar.new.greeting # NoMethodError: undefined method `greeting' for #<Bar:0x18e4d78>

This inheritance is also not copy-on-write in case you assumed that:

Foo.greeting  #=> "Hello"
foo1, foo2 = Foo.new, Foo.new

foo1.greeting = "Hi!"

Foo.greeting  #=> "Hi!"
foo2.greeting #=> "Hi!"

This makes it possible to share common state (queues, semaphores, etc.), configuration (max value, etc.) or temporary values through this.

February 27, 2009
6 thanks

Extend with an anonymous module

You can extend with an anonymous module for one-off cases that won’t be repeated:

belongs_to :container, :polymorphic => true, :extend => ( Module.new do
    def find_target
      ...
    end
  end )

The parentheses are important, will fail silently without them.

February 24, 2009
9 thanks

Specialized versions of find with method_missing

Check ActiveRecord::Base.method_missing for documentation on the family of “magic” find methods (find_by_x, find_all_by_x, find_or_create_by_x, etc.).

February 24, 2009 - (>= v2.2.1)
5 thanks

ATM does not work in Rails 2.3 Edge

add to test/spec_helper to make it work again…

#spec_helper / test_helper
include ActionController::TestProcess
February 23, 2009
6 thanks

Nested with_options

You can nest with_options blocks, and you can even use the same name for the block parameter each time. E.g.:

class Product
  with_options :dependent => :destroy do |product|
    product.with_options :class_name => 'Media' do |product|
      product.has_many :images, :conditions => {:content_type => 'image'}
      product.has_many :videos, :conditions => {:content_type => 'video'}
    end

    product.has_many :comments
  end
end
February 22, 2009
3 thanks

CAUTION! :frequency option description is misleading

To use event-based observer, don’t supply :frequency param at all. :frequency => 0 causes JS error.

Use this option only if time-based observer is what you need.

February 20, 2009
5 thanks

Static and dynamic attachments

You can attach static files directly:

attachment :content_type => "image/jpeg", :body => File.read("someimage.jpg")

and you can also define attachments dynamically by using a block:

attachment "text/csv" do |a|
  a.body = my_data.to_csv
end