Notes posted to Ruby on Rails
RSS feedAASM named scopes
If you are using the aasm plugin/gem, this will generate all named scopes for your various states.
Code example
Class Article < ActiveRecord::Base include AASM aasm_initial_state :created aasm_state :published aasm_state :unpublished aasm_state :deleted aasm_state :created aasm_event :publish do transitions :to => :published, :from => [:created] end aasm_event :unpublish do transitions :to => :unpublished, :from => [:created, :published] end aasm_event :delete do transitions :to => :deleted, :from => [:published, :unpublished] end aasm_states.each { |s| named_scope s, :conditions => { :state => s.to_s } } end
Re: Caveat when using dynamic layouts
Since there’s no way to edit posts on here, I need to correct myself and say that what I posted before doesn’t work, since you can’t specify layout multiple times:
class OrdersController < BaseController layout :determine_layout, :only => :new layout "public", :except => :new # ... end
So don’t do that. The only way to ensure that the other actions get the default theme is to drop :only/:except and do the conditions yourself:
class OrdersController < BaseController layout :determine_layout private def determine_layout %w(new).include?(action_name) ? "some_layout" : "public" end end
All this to say, beware of :only/:except – they aren’t as useful as you think they are.
Caveat when using dynamic layouts
Worth noting that if you have a controller which inherits from another controller which has a layout, and in this child controller you’re determining the layout at runtime using a method for specific actions, the other actions you are excluding will not inherit the layout from the parent controller.
For example, if you’ve got this
class BaseController < ApplicationController layout "public" end class OrdersController < BaseController layout :determine_layout, :only => :new # index, show, new, create, edit, update, destroy ... end
then OrdersController#index, #show, and #edit won’t get the “public” layout – in fact they won’t get a layout at all. So you’ll need to do this instead:
class OrdersController < BaseController layout :determine_layout, :only => :new layout "public", :except => :new # ... end
default_scope on create
If you specify :conditions in your default_scope in form of a Hash, they will also be applied as default values for newly created objects. Example:
class Article default_scope :conditions => {:published => true} end Article.new.published? # => true
However:
class Article default_scope :conditions => 'published = 1' end Article.new.published? # => false
configuration no longer in environment.rb
configure session store in config/initializers/session_store.rb
Saving other objects inside before_save
Don’t call .save or .update_attribute on other objects inside before_save callback.
Saving other objects inside of before_save callback results in flushing changed hash and the original object is not updated.
UPDATE observed sometimes, still investigating
Redirect...
See ActiveRecord::ConnectionAdapters::TableDefinition#column for details of the options you can use.
Possible gotcha
This method returns a Pathname object which handles paths starting with a / as absolute (starting from the root of the filesystem). Compare:
>> Rails.root => #<Pathname:/some/path/to/project> >> Rails.root + "file" => #<Pathname:/some/path/to/project/file> >> Rails.root + "/file" => #<Pathname:/file> >> Rails.root.join "file" => #<Pathname:/some/path/to/project/file> >> Rails.root.join "/file" => #<Pathname:/file>
See documentation for the class method
Since Rails version 2, this instance method no longer exists.
You may be looking for its namesake class method, ActiveRecord::Base.method_missing
Easy workaround for missing :through option
Note that belongs_to does not support :through option like has_many (although IMHO it would make sense in some cases), but you can easily simulate it with delegate.
For example:
class Person < ActiveRecord::Base belongs_to :team ... end class Task < ActiveRecord::Base belongs_to :person delegate :team, :to => :person end
There is of course more ways to do it, but this seems to be the easiest to me.
note: the example format regex is too lenient
The example regex for RFC 2822 email is too lenient. Sure it’s just an example, but it wound up in our code and we just had to fix it to match the RFC.
The regex as given allows any non-@ non-whitespace characters. But the RFC only allows these characters in mailbox names
alpha digit - ! \ # $ % & ' * + \ / = ? ^ _ ` { | } ~ ] +
and . is only allowed between atoms of 1 or more of the above.
Here’s the corrected regex:
:with => /\A([-a-z0-9!\#$%&'*+\/=?^_`{|}~]+\.)*[-a-z0-9!\#$%&'*+\/=?^_`{|}~]+@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
Extract the aggregated scoping options
If you want to get the aggregated scoping options of a chain of named scopes use ActiveRecord::Base.current_scoped_methods
It works in the fashion of:
Shirt.red.medium.alphabetical.current_scoped_methods # ==> { :create => {}, :find => { :conditions => {:color => 'red', :size => 'medium'}, :order => 'shirts.name ASC' } }
Check content_for content
There is a content_for? method in Rails wherewith you can check the content. http://github.com/rails/rails/commit/9cb8c812f2a23ab5653a7888740a014a02c97c18#diff-1
But it’s not in the last stable version (2.3.5) http://github.com/rails/rails/blob/v2.3.5/actionpack/lib/action_view/helpers/capture_helper.rb
re: Options
@ramanavel - The options are dependent on the cache store that you’re using. You’ll need to have a look at what the cache store you’re using allows.
e.g. MemCacheStore allows the use of time criteria based :expires_in, most of the cache stores don’t.
http://guides.rubyonrails.org/caching_with_rails.html might provide a little more information.
reload equivalent for models
The reset_column_information method provides a similar function for the model itself. Most useful during migrations.
the :order parameter is not sanitized
The :order parameter is not sanitized, so doing something like Person.find( :first , :order => params[:order] ) could get you in trouble.
An alternate way to have a string ID as a primary key
You can disable automatically created primary key and add it to manually with mysql:
The migration file:
def self.up create_table( :my_special_table, :id => false ) do |t| t.string :id, :limit => 5, :null => :no end execute "ALTER TABLE my_special_table ADD PRIMARY KEY (id)" end
Then in a before_save filter you can generate the primary key for yourself.
Use a transaction and be aware of uniqueness!
All dates in the database are stored in UTC and all dates in Ruby are in a local timezone
With the timezone support introduced in Rails 2.1 the idea is that all dates in the database are stored in UTC and all dates in Ruby are in a local timezone.
Time.zone.now == Time.now # => false
as Peter Marklund lights up this in his blog:
http://marklunds.com/articles/one/402
“They will only be converted to UTC for you if they are ActiveSupport::TimeWithZone objects, not if they are Time objects. This means that you are fine if you use Time.zone.now, 1.days.ago, or Time.parse(”2008-12-23“).utc, but not if you use Time.now or Time.parse(”2008-12-23“)”
Paying attention to query parameters
Standard action caching ignores query parameters, which means you’d get the same results for a URL with and without query parameters if it was action cached. You can make it pay attention to them by using a custom cache path like so:
caches_action :my_action, :cache_path => Proc.new { |c| c.params }
Or, maybe you want some of the query parameters, but not all to factor into different versions of that action’s cache:
:cache_path => Proc.new { |c| c.params.delete_if { |k,v| k.starts_with?('utm_') } }
Beware of things like pagination if you use expires_in to expire the cache, as pages could get out of sync.
Passing html options (Ruby hash parameters)
When you have two default hash parameters at the end of a function call, you need to use it as the following:
options = { :include_blank => true, :default => @my_object.my_method } date_select :my_object, :my_method, options, :class => 'my_css_class'
You can try it for yourself on this example:
def test_funct a = {}, b = {} puts "a: #{a.inspect}" puts "b: #{b.inspect}" end test_funct :x => 'x', :y => 'y' # all the parameters are collected for the hash a
Alternative hostname generation method
Instead of using a random number to generate the hostname for the single asset, I prefer using source.hash.modulo, so that a given file is always served from the same host. This makes the content more cacheable by browsers and proxies.
ActionController::Base.asset_host = Proc.new { |source| "http://assets#{ source.hash.modulo(9) }.example.com" }
I didn’t benchmark how long it takes, but String#hash should be reasonably fast.
is now a subclass of Hash that preserves order (or _is_ a Hash if running Ruby 1.9 or greater)
You might not realize it preserves order because it delegates inspect to its super-class, Hash, which doesn’t preserve order. But you will see that order is preserved if you iterate or use the keys or values methods:
>> names = ['Amy Irving', 'Jane Doe', 'John Doe', 'John Updike', 'Susan Anthony'] >> ordered = names.group_by { |name| name.split.first } => #<OrderedHash {"John"=>["John Doe", "John Updike"], "Amy"=>["Amy Irving"], "Susan"=>["Susan Anthony"], "Jane"=>["Jane Doe"]}> >> ordered.keys => ["Amy", "Jane", "John", "Susan"] >> ordered.each { |first, full| puts first; full.each { |name| puts " "+name } } Amy Amy Irving Jane Jane Doe John John Doe John Updike Susan Susan Anthony
returns an ActiveSupport::OrderedHash
Returns an ActiveSupport::OrderedHash, which is a subclass of Hash that preserves order. If you’re running Ruby 1.9, it is simply an alias for Hash. Surprisingly, you might not realize that OrderedHash is preserving order since it delegates its inspect method to Hash. More at ActiveSupport::OrderedHash.
W3CDTF Format
Here is the formatted string for the W3CDTF datetime format (http://www.w3.org/TR/NOTE-datetime). It has a semicolon in the timezone part, therefore you cannot use ‘%z’:
Time::DATE_FORMATS[:w3cdtf] = lambda { |time| time.strftime("%Y-%m-%dT%H:%M:%S#{time.formatted_offset}") }
Using html text instead of default response
If you have a string containing html and want to assert_select against it, as the doc states you have to pass in an element (HTML::Node) as the first argument. You can do something like this:
doc = HTML::Document.new('<p><span>example</span></p>') assert_select doc.root, 'span'
Escape brackets in selector
If you need to escape brackets in a selector, this is the way to do it:
assert_select "input[type=hidden][name='user[role_ids][]']"
Reverse version of camelize
Reverse version of camelize is underscore
Undeprecated version
The undeprecated version of this function is here: ActionView::Helpers::PrototypeHelper#link_to_remote


