Good notes posted to Ruby on Rails
RSS feedmultiple filter example
actually you can have it even shorter with:
before_filter :authorize, :set_locale, :except => :login
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.
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)
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
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)
More Information
More information can be found at ActionController::Verification::ClassMethods
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.
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…
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
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.
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.
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.).
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
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
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.
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
Turn off for individual controllers/actions
To disable protection for all actions in your controller use skip_before_filter:
skip_before_filter :verify_authenticity_token
You can also pass :only and :except to disable protection for specific actions, e.g:
skip_before_filter :verify_authenticity_token, :only => :index
Anchor tag in link_to
Code example
link_to("some text", articles_path(:anchor => "comment"))
will output <a href = “/articles#comment” >some text
Date_select with assert_valid_keys
If you are using date_select with assert_valid_keys you have to allow 3 parameters named field(1i), field(2i) and field(3i).
For example with field
date_select("post", "written_on")
You have to allow following fields:
params[:post].assert_valid_keys( 'written_on(1i)', 'written_on(2i)', 'written_on(3i)' )
Empty elements
If you want to output an empty element (self-closed) like “br”, “img” or “input”, use the tag method instead.
New test syntax
You can use either one and even mix in the same test case if you want:
class Test < Test::Unit::TestCase # old way to define a test method (prefix with test_) def test_should_be_valid_without_content assert Comment.new.valid? end # new way to define a test test "should be valid without content" do assert Comment.new.valid? end end
Security issue with non-HTML formats
Please note that using default to_xml or to_json methods can lead to security holes, as these method expose all attributes of your model by default, including salt, crypted_password, permissions, status or whatever you might have.
You might want to override these methods in your models, e.g.:
def to_xml super( :only => [ :login, :first_name, :last_name ] ) end
Or consider not using responds_to at all, if you only want to provide HTML.
For those who is looking for lost country_select
country_select lives on as a plugin that can be acquired from here: http://github.com/rails/country_select/tree/master
Deprecated
This method is deprecated. You should use:
I18n.translate('activerecord.errors.messages')
Possible gotcha
Please note that exists? doesn’t hold all the conventions of find, i.e. you can’t do:
Person.exists?(:conditions => ['name LIKE ?', "%#{query}%"]) # DOESN'T WORK!
Nothing here
You’re probably looking for I18n::Backend::Simple.
This method will still rewrite all the values of the table
Even if you update only a small boolean flag on your record, update_attribute will generate an UPDATE statement that will include all the fields of the record, including huge BLOB and TEXT columns. Take this in account.
You can't use Symbols, but you can use Regexps
You can’t use Symbol (although Symbol is accepted with render :action => :new), like:
assert_template :new # WON'T WORK!
But you can use Regexp, e.g.:
assert_template /new/ # WORKS OK
Note that the String matched with your Regexp is the full path to the template relative to the view/ directory of your app, so this will not work:
assert_template /^new$/ # WON'T WORK!
However this might:
assert_template /^employees\/new.html.haml$/
Hash conditions require explicit key and value
When condition passed as hash, the behavior is different from a finder method. Finder methods, such as:
find(:all, :user=>user)
will apply the user_id = user.id convention, provided user is an association (e.g. belongs_to :user). The exists? method will not do the same. You must specify the foreign key name and value explicitly, i.e:
exists?(:user_id=>user.id)


