Recent good notes
RSS feedExample
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'
The docs are in the base class
Look in ActionController::Base for the docs.
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}%"])
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
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.
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
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.)
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.
See Dir#glob
See glob for more usage information and comments.
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
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.
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 }
Use camelize with singular words
To make the business example work, use camelize instead of classify:
"business".camelize # => "Business"
form_tag with named route and html class
<% form_tag position_user_card_path(@user, card), :method => :put, :class => ‘position-form’ do %>
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', ':') %>
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
Symbol#to_proc
@tadman - or simply defining:
class Symbol def to_proc proc { |obj, *args| obj.send(self, *args) } end end
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();"
With multiple parameters
Example
remote_function( :url => some_remote_function_path, :with => "'key1='+$('elem_id').value +'&key2='+$('elem_id').value+ '&this_elem_value='+value" )
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.
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],
Documentation
This method only returns a cache manager object of sorts, to see what you can do with it, see ActiveSupport::Cache::Store.
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 .
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
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.
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
%% - Literal “%” character