Notes posted to Ruby on Rails
RSS feedHow to use with exclusive scope
Code example
Article.with_exclusive_scope { find(:all) } #=> "SELECT * FROM 'articles'
from http://ryandaigle.com/articles/2008/11/18/what-s-new-in-edge-rails-default-scoping
Have the check_box checked by default
To have the check box checked by default, pass either :checked => true or :checked => 'checked' in the options. See ActionView::Helpers::InstanceTag#to_check_box_tag for details.
This is ON by default in :has_many
When defining a has_many relationship this behaviour is on by default. See has_many documentation, look for the :validate flag.
add index with :quiet=>true option for indices that are possibly already added
# Allows you to specify indices to add in a migration that will only be created if they do not # already exist, or to remove indices only if they already exist with :quiet=>true module ActiveRecord::ConnectionAdapters::SchemaStatements
def add_index_with_quiet(table_name, column_names, options = {}) quiet = options.delete(:quiet) add_index_without_quiet table_name, column_names, options rescue raise unless quiet and $!.message =~ /^Mysql::Error: Duplicate key name/i puts "Failed to create index #{table_name} #{column_names.inspect} #{options.inspect}" end alias_method_chain :add_index, :quiet def remove_index_with_quiet(table_name, column_names, options = {}) quiet = options.delete(:quiet) raise "no options allowed for remove_index, except quiet with this hack #{__FILE__}:#{__LINE__}" unless options.empty? remove_index_without_quiet table_name, column_names rescue raise unless quiet and $!.message =~ /^Mysql::Error: Can't DROP/i puts "Failed to drop index #{table_name} #{column_names.inspect}" end alias_method_chain :remove_index, :quiet
end
A catch-all format
If you’d like to specify a respond_to only for 1 or a few formats and render something else for all other formats, eg: (action.rss returns a feed but action.html or action.js should just render 404), use format.all:
respond_to do |format| format.rss { render_rss } format.all { render_404 } end
Rails will render an empty string for all formats that don’t specify a response explicitly.
Security hole in 2.3.2
This method has a security hole in Rails 2.3.2. See http://weblog.rubyonrails.org/2009/6/3/security-problem-with-authenticate_with_http_digest for explanation.
Rails 2.3.3 should fix the problem.
ActiveRecord::RecordNotSaved can be triggered by accidental false return values in callbacks
You may have this exception raised if any of the defined callbacks such as ActiveRecord::Base#before_save or ActiveRecord::Base#before_create return false.
This can happen accidentally. For example:
class MyModel < ActiveRecord::Base before_save :assign_default_foo protected def assign_default_foo self.foo = false end end
Since assign_default_foo leaves a false value on the stack, the model will not be saved. A way around this is to simply leave nil or an empty return instead:
class MyModel < ActiveRecord::Base before_save :assign_default_foo protected def assign_default_foo self.foo = false nil end end
Writing and reading a cookie in the same request.
As of 0349278f3da9f7f532330cf295eed35ede3bae66 cookie updates will persist in the current request.
multi scope to sql
validates_uniqueness_of :name, :scope => [:big_category_id, :small_category_id]
SELECT * FROM schedules WHERE (products.name = 'xxxx' AND products.big_category_id= 1 AND products.small_category_id = 1) LIMIT 1
Do not create an [ ] method
I created a helper method to access some meta data using
def [](name) # do stuff end
This breaks ActiveRecord behaviors. all belongs_to relations were broken
eg.
class Image belongs_to :album end i = Image.find :first i.album_id # 1 i.album # nil Album.find 1 # works
If you experience this behavior, you probably created a method that breaks the default systematics (like I did with the [ ] method)
Further To: Memoize will not cache singleton methods
er…it will:
Code example
class PersonType < ActiveRecord::Base class << self # Add the mixin here: extend ActiveSupport::Memoizable def mister find_by_name('Mister') end memoize :mister end end
Make sure your action names don't step on any toes.
In my experience, if you ever have a controller action named “process”, your controller will cease to function, as there is both a class and instance method called process in ActionController::Base.
There are undoubtedly other action names that will cause conflicts, but this one is particular I’ve run into a number of times.
You can call several times
You can call it several times, like:
class Comment < ActiveRecord::Base validate :must_be_friends validate :must_be_awesome ...
or with several arguments:
class Comment < ActiveRecord::Base validate :must_be_friends, :must_be_awesome ...
Alternative Way to Handle
This plugin may also help solve the problem from the model side.
http://github.com/rxcfc/multi_assignment_sanity
Moved
In 2.2 and greater this has moved to ActiveSupport::Dependencies::Loadable#unloadable
How to set request parameters
On previous versions of TestRequest it was possible to set the request_parameters on the new action. This option is now gone, but it’s still possible to set the parameters after initialization.
Code example
request = ActionController::TestRequest.new request.env["action_controller.request.request_parameters"] = { :foo => '42', :bar => '24' }
script/generate can take table name
As far as I can tell script/generate will happily take the plural table name, at least in Rails 2.3.
form_authenticity_token
Instead of disabling the CSRF check you can pass the authenticity_token field in your forms, eg:
<%= hidden_field_tag :authenticity_token, form_authenticity_token -%>
Using gmail SMTP server to send mail
If you’re running Rails >= 2.2.1 [RC2] and Ruby 1.8.7, you don’t need plugin below. Ruby 1.8.7 supports SMTP TLS and Rails 2.2.1 ships with an option to enable it if you’re running Ruby 1.8.7.
All You need to do is:
ActionMailer::Base.smtp_settings = { :enable_starttls_auto => true }
RESTful actions
REST adds many constraints. It restricts your controllers to seven actions. Normally this is okay, but sometimes you need to add your own custom actions.
Question
Can someone add some more information to this?
Formatted route helpers are gone
In Rails >= 2.3 you can’t use formatted_xxx url helpers anymore.
However, you can still pass a :format option to url helpers, eg:
articles_path(:format => :csv) # => /articles.csv
Setting name and id for select_tag
Sometimes you need to use select_tag instead of select (because you’re after more control or need to use optgroups, for example), but still want the id/name conventions that select would give.
In this case, all you need to do is set the first parameter to whatever would be produced by select, and it’ll take care of the id and name attribute automatically, and thus ensure the form data is parsed correctly after submission.
For example, if you want to do something like:
form_for :comment do |f| f.select :article_id ...
which would give a select tag with id of “comment_article_id” and a name attribute of “comment[article_id]”, which be parsed into the params hash of:
'comment' => {'article_id' => ...
you can instead do
form_for :comment do |f| select_tag 'comment[article_id]' ...
which will give the same id and name attributes for the select tag and hence the same params hash in the controller
Re: Find random record
How about if you wanted to find a random set of records instead of a singular record, what would be the best way?
Thank you
Re: Find random record
Ordering by RAND() is not a wise idea when you have a large table with lots of rows. Your database will have to calculate a different random value for every row in your database – O(N) – then sort the entire table by those values – O(N log N).
There are a number of better ways to get a random record from your table. Some examples:
-
If your table is not sparse, choose a random ID and get that row (or the nearest row):
rand_id = rand(Model.count) rand_record = Model.first(:conditions => [ "id >= ?", rand_id]) # don't use OFFSET on MySQL; it's very slow
-
If your table is sparse, or does not have a primary key, consider adding an indexed column of random numbers between 0 and N. You can then order by this column quickly and choose a value using a method similar to the above example.
Find random record
It’s as simple as:
Things.first(:order => 'RAND()')
Of course depending on your database it could be ‘RANDOM()’ or something similar.
Caveat and design hints regarding :counter_cache
(From Obie Fernandez/ The Rails Way, ISBN 978-0321445612. Thanks Obie!)
This caveat:
The value of the counter cache column must be set to zero by default in the database! Otherwise the counter caching won’t work at all. It’s because the way that Rails implements the counter caching behavior is by adding a simple callback that goes directly to the database with an UPDATE command and increments the value of the counter.
And these tips:
If a significant percentage of your association collections will be empty at any given moment, you can optimize performance at the cost of some extra database storage by using counter caches liberally. The reason is that when the counter cache attribute is at zero, Rails won’t even try to query the database for the associated records!
If you’re not careful, and neglect to set a default value of 0 for the counter cache column on the database, or misspell the column name, the counter cache will still seem to work! There is a magic method on all classes with has_many associations called collection_count, just like the counter cache. It will return a correct count value if you don’t have a counter cache option set or the counter cache column value is null!


