Recent notes
RSS feedReg Ex Syntax
Is there any place where there is a full listing of RegEx syntax?
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
NoMethodError: undefined method `each_char'
For some reason the each_char method on String is not available by default in Ruby 1.8.6 and you will be presented with a NoMethodError.
You can resolve this by requiring the jcode lib:
require 'jcode'
clarification
Via Kenneth Kalmer:
From the man page: If salt is a character string starting with the characters “$id$” followed by a string terminated by “$”: $id$salt$encrypted then instead of using the DES machine, id identifies the encryption method used and this then determines how the rest of the password string is interpreted.
irb session
=> “abNANd1rDfiNc” irb(main):002:0> “secret”.crypt(”abasasa”) => “abNANd1rDfiNc” irb(main):003:0> “secret”.crypt(”$1$abasasa”) => “$1$abasasa$2RZY2vd6E2ZEPSDa0eLec0″ irb(main):004:0> “secret”.crypt(”$1$abasa”) => “$1$abasa$ikoKICgwOFdcWgmDl9Asy1″
see http://www.opensourcery.co.za/2009/05/01/quick-nix-shadow-passwords-with-ruby/
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
Test if one array includes the elements of another
You can just use a set difference (aka minus) to see if one array includes all elements of another
not_included = [1,2,3] - (1..9).to_a not_included # => [] not_included = [1,2,3,'A'] - (1..9).to_a not_included # => ["A"]
Use intersection to test if any of the one are in the other:
shared = [1,2,3,'A'] & (1..9).to_a shared # => [1, 2, 3]
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!
attachments and implicit multipart
There is a small gotcha - this caught me up for a while.
If you are using implicit multipart mime types by naming your template xxx.text.html.erb and xxx.text.plain.erb, you will need to change your template name back to the original xxx.erb.
If you use the implicit template name, your attachment will be the only thing in the body of the message - it will ignore your template.
See the “Multipart email” section of the ActionMailer.base documentation.
Video tutorial
If you want to get up to speed with Rails’ caching and haven’t seen it already, definitely check out this video series on Scaling Rails:
Including instance methods to JSON output
Use :methods parameter to include ActiveRecord instance methods to JSON output. :only and :except uses DB columns only.
@events.to_json(:include => { :images => { :only => [], :methods => [:public_url] }})
In the previous example events have multiple images and only public_url instance method is included in the JSON output.
Moved to ActiveSupport::Inflector
This isn’t gone, it’s just been moved to the ActiveSupport module namespace.
Tip: Define from_param(...) as Opposite
Often when defining a to_param method, it’s handy to introduce an opposite method for decoding them. For example:
class User < ActiveRecord::Base def self.from_param(param) find_by_name!(param) end def to_param name end end
While you can just as easily redefine the find() method, this may be confusing since the expectation is that find() works with numerical IDs, or whatever the key column is defined as.
A very thorough explanation of use
Ryan Daigle has a great article about 2.3’s new nest forms which does a really good job of explaining how to use this and some of the potential gotchas. Highly recommended:
http://ryandaigle.com/articles/2009/2/1/what-s-new-in-edge-rails-nested-attributes
has_one through belongs_to not working
code example:
class Company < ActiveRecord::Base has_many :route_lists end class RouteList < ActiveRecord::Base belongs_to :company has_many :routes end class Route < ActiveRecord::Base belongs_to :route_list has_one :company :through => :route_list end
This creates an invalid SQL query, where the keys in the join between route and routelist are switched, when used as an include:
Routes.find :all, :conditions => ["companies.type = ?", "Account"], :include => :company
route_lists.route_list_id = route.id
instead of: route_lists.id = route.route_list_id
Find symlink target path
To find the target of a symlink, use File.readlink
Set :use_route to nil to let Rails pick the best route
Imagine the following case. You have two landing pages, one generic one, and an account specific one. The urls are as follows:
map.landing 'landing', :controller => 'landing', :action => 'index' map.account_landing 'accounts/:account_id/landing', :controller => 'landing', :action => 'index'
Now imagine you want a path to the landing page, using the most specific route possible. If you have an account_id, use it, if not, skip it.
You could do
url_for(:controller => 'landing', :action => 'index', :account_id => current_account)
If current_account is set you’ll get “/accounts/:account_id/landing” if not, you’ll get “/landing”. However, that just looks ugly.
Enter :use_route => nil.
landing_path(:account_id => nil) # => '/landing' landing_path(:account_id => 1) # => '/landing?account_id=1' landing_path(:account_id => nil, :use_route => nil) # => '/landing' landing_path(:account_id => 1, :use_route => nil) # => '/accounts/1/landing'
Setting :use_route to nil, is equivalent to the earlier #url_for example.
have your to_param begin with the object's id
If you overwrite the to_param method in your model class such that it does not begin with its id, you can be in for a nasty surprise:
Example
class User def to_param self.login end ... end
Let’s say you have a user called “bob”, than you might think this works:
>> bob = User.find(3) => #<User id: 3, login: "bob", ...> >> User.find(bob.to_param) ActiveRecord::RecordNotFound: Couldn't find User with ID=bob
But it’s not the reason being that Rails find method looks for a beginning number (d+) and uses that to look up the record (and ignores everything that comes after the last digit). So the solution is to have your to_param return something that begins with the object’s id, like so:
Example
class User def to_param "#{self.id}-#{self.login}" end ... end >> bob = User.find(3) => #<User id: 3, login: "bob", ...>
>> User.find(bob.to_param)
> # id: 3, login: “bob”, …>
>> bob.to_param
> “3-bob”
For the filename use File.basename
File.basename provides what File.dirname omits.
Argument Ordering
Be aware that the order of arguments for this method is the opposite of File.join:
File.expand_path('foo', '/bar') # => "/bar/foo" File.join('foo', '/bar') # => "foo/bar"
See Also: IO Class Methods
There are other more specific methods defined in the IO class: IO.open for files, IO.popen for pipes.
Customize Formatting with a Subclass
Instead of passing in a formatter block, you can always create a subclass that defines the format:
require 'logger' class MyLogger < Logger def format_message(severity, datetime, progname, msg) "[%s %s] %s\n" % [ severity, datetime.strtftime("%H:%M"), msg ] end end
This can be easier than always passing the same formatter option.
Rails and Ruby 1.8.7 Extensions
Note that the use of Symbol#to_proc requires either Rails or Ruby 1.8.7. Prior versions will show:
['a', 'b', 'c'].collect(&:capitalize) # => TypeError: wrong argument type Symbol (expected Proc)
Handy shorthand for array manipulation
You may write something like this:
>> ['a', 'b', 'c'].collect{|letter| letter.capitalize} => ["A", "B", "C"]
But it looks so much nicer this way:
>> ['a', 'b', 'c'].collect(&:capitalize) => ["A", "B", "C"]