Notes posted to Ruby on Rails
RSS feedValidate an optional URL field
Let’s say that you have an optional URL field to one of your models and you want to validate the URL. You can accomplish this by using the URI library:
require 'uri' # Put this at the beginning of your model file validates_each :url, :allow_blank => true do |record, field, value| begin valid = (URI.parse(value).scheme =~ /https?/) rescue URI::InvalidURIError valid = false end record.errors.add field, "not a valid url" unless valid end
If you want to add even more testing in there, just go ahead. For now, we just check that the link is to a HTTP resource, but you might have other requirements. This will allow stuff like “http://example” since “example” might be a valid intranet domain. If you want to check for a TLD in there, you can do so with a simple regexp.
For more information about the URI library, check out http://apidock.com/ruby/URI/
You can't have many :through with habtm
Imagine the following
a has_many b b has_and_belongs_to_many c a has_many c :through => b
a.b works fine
b.c works fine
a.c throws an error!
has_many :through where the through association is a habtm is not supported in Rails. The error is:
ActiveRecord::HasManyThroughSourceAssociationMacroError: Invalid source reflection macro :has_and_belongs_to_many for has_many :stories, :through => :subcategories. Use :source to specify the source reflection
Specifying the source reflection still won’t help you though, because this kind of has_many :through isn’t supported at all.
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
Check if value is included in array of valid values
If you want to check the value of an attribute using an array of valid values, the array has to be defined before the validation, so
validates_inclusion_of :name, :in => VALID_NAMES VALID_NAMES = %w(Peter Paul Mary)
won’t work, but
VALID_NAMES = %w(Peter Paul Mary) validates_inclusion_of :name, :in => VALID_NAMES
will.
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)' )
If you want to avoid SQL...
…you’re probably looking for http://apidock.com/rails/ActiveRecord/Calculations/ClassMethods/count
Empty elements
If you want to output an empty element (self-closed) like “br”, “img” or “input”, use the tag method instead.
Remember to sanitize name
While useful when in need of richer markup inside a link, the name parameter isn’t sanitized or escaped and thus should be escaped when its content can’t be guaranteed to be safe.
E.g.
link_to(url, url)
may cause problems with character entities if url contains ampersands.
Correct usage
link_to(h(url), url)
This applies to all dynamic content.
See column
See the end part of the docs on column for example uses.
Reverse of this
If you want to do the reverse of this, e.g. go from a specific date and back to a certain day of the previous week, you can implement it like this:
def last_week(day = :monday) days_into_week = { :monday => 0, :tuesday => 1, :wednesday => 2, :thursday => 3, :friday => 4, :saturday => 5, :sunday => 6} result = (self - 7).beginning_of_week + days_into_week[day] self.acts_like?(:time) ? result.change(:hour => 0) : result end
If you do not want to make your own method of this, but just want to do it in a regular chaining of date methods (like Date.today.next_year.at_midnight), you can do it like the following:
(date - 7).next_week(:tuesday) # Tuesday, last week
Please note that you just need to subtract 7 if you want to move back a week. Only use these methods if you want to go to a specific day of the week.
Reverse naming
The reverse of this is last_month and not previous_month, like one might believe from the naming.
Reverse naming
The reverse of this is last_year and not previous_year, like one might believe from the naming.
use #collect instead of #each
The earlier reminder to use #collect instead of #each applies regardless of whether the tag is nested or not.
This is counterintuitive, as #collect returns an array of strings of HTML tags, but ActionView renders it properly.
Clear and simple rescue
noxyu3m, your code is rescuing all exceptions, not just ActiveRecord::RecordInvalid.
I think this syntax is a bit more clear than using the global variable.
def create @model = Model.new(params[:model) @model.save! rescue => err # rescues all exceptions logger.error(err.to_s) end
Simple rescue
Take it easy:
def create @model = Model.new(params[:model) @model.save! rescue logger.error(!$.to_s) end
Global variable !$ refers to the Exception object.
throws exception
when use use Model.find([1,2,3,4])
throws exception if no record exists with any of this ID
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
using collection=objects
It will fire one insert query per new record
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.
Easier way
See http://apidock.com/rails/ActionController/TestProcess/fixture_file_upload for easier way to test file uploads.
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
:foreign_type option
I’m not sure if this has always been around but in 2.3, belongs_to takes a :foreign_type option on polymorphic associations. This behaves the same way as :foreign_key but for the type field.
Documentation
Good docs can be found here: http://www.artweb-design.de/2008/7/18/the-ruby-on-rails-i18n-core-api
See also: http://rails-i18n.org/wiki for an extensive list of resources.
Deprecated
This method is deprecated. You should use:
I18n.translate('activerecord.errors.messages')
Whacky edge case
The above works great as long as you select the primary key of the owning assocations.
preload_associations calls a group_by on that object so if there is no primary key attributed filled out it will reduce the records to 1 object. ex: rows = foo.find(:all,:select=>“boo.id, foo.name, foo.age, count(DISTINCT foo.id)”, :group=>“foo.name,foo.age having count( DISTINCT foo.id) > 1”,:joins=>“INNER JOIN bar.foo on bar.foo_id = foo.id”)
preload_assications(rows,:bar)
rows.first.bar.name #=> sql call already made in preload rows.last.bar.name #=> just made another sql call to get bar
fix: :select=>“foo.id, boo.id, foo.name, foo.age, count(DISTINCT foo.id)”
now preload_associations will include all the ids found instead of just the 1st one.