Good notes posted to Ruby on Rails
RSS feedGetting the object in a partial
If you need to get the object for the form inside a partial, and can’t use the instance variable, use the #object method… This is particularly useful when you’re dealing with single-table inheritance subclasses (e.g. MyOtherClass inherits from MyClass) or when you are using the same partial across different controllers.
new.html.erb
<% form_for(@my_object) do %> <%= render :partial => 'form' %> <%= submit_tag 'Create' %> <% end %>
_form.html.erb
<% if f.object.class.is_a? MyClass %> <%# do something... %> <% elsif f.object.is_a? MyOtherClass %> <%# do something else... %> <% end %>
Use collect in nested content_tags
Remember to use #collect instead of #each in nested content_tags
arr = ['a','b','c'] content_tag :div do arr.collect { |letter| content_tag(:scan, letter) end #=> <div> # <scan>a</scan> # <scan>b</scan> # <scan>c</scan> # </div>
If you used #each you would get this (which is probably a mistake):
#=> <div> # abc # </div>
two ways to disable single table inheritance
-
Don’t use the column name ‘type’
-
Or if the first is no option for you: Tell Rails to look for a not existing column like:
class MyModel < ActiveRecord::Base
# disable STI inheritance_column = :_type_disabled end
alternative (working with 2.2.X)
ActionController::Base.relative_url_root
Routes = RouteSet.new
In config/routes.rb you can see this:
ActionController::Routing::Routes.draw do |map| #routes end
If you want to look at the code in ActionController::Routing you won’t find the definition of Routes. That’s because it’s actually an instance of the class RouteSet, defined in action_controller/routing.rb
Routes = RouteSet.new
How to test different responses of respond_to
You can shorten this:
@request.env['HTTP_ACCEPT'] = "application/rss"
To this:
@request.accept = "application/rss"
Also, if you send more than one Mime type, it will render the first one:
@request.accept = "text/javascript, text/html" #=> renders JS @request.accept = "text/html, text/javascript" #=> renders HTML
Using fields_for with has_many associations
If you want to edit each element of an array of objects (such as with a has_many type association), you will need to include “[]” in your field parameter name, like so:
<% fields_for "object[]" do |subfield| -%> [...] <% end -%>
Because you named the field parameter “object[]”, fields_for will assume you have an instance variable @object to use for the fields’ values. To fake this, you can do something like:
<% objects.each do |@object| -%> <% fields_for "object[]" do |subfield| -%> [...] <% end -%> <% end -%>
If that looks like sacrilegious Rails code to you, then you could consider:
<% objects.each do |object| -%> <% fields_for "object[]", object do |subfield| -%> [...] <% end -%> <% end -%>
In either case, params[:object] will be a hash where the ID of each object (determined via ActiveRecord::Base#to_param ) is associated with a hash of its new values:
params = { 'object' => { '123' => { 'field' => 'newval' }, '159' => { 'field' => 'newval' } } }
Reloading memoized values
Memoize is used to cache the result of a method. It’s roughly equivalent of having:
def memoized_method(*args) @result[args] ||= ( # do calculation here ) end
However, the result is cached so that it’s not calculated for every request.
To recalculate cached value use either
obj.memoized_method(:reload)
or
obj.memoized_method(true)
Javascript encoding DOES work!
grosser assertion is false :
mail_to('xxx@xxx.com', nil, :encode => :javascript) # => "<script type=\"text/javascript\">eval(unescape('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%78%78%78%40%78%78%78%2e%63%6f%6d%22%3e%78%78%78%40%78%78%78%2e%63%6f%6d%3c%2f%61%3e%27%29%3b'))</script>"
Use “nil” as the second parameter to tell mail_to that you want to use the first parameter for both text and email link
Remember to mixin the ActiveSupport::Memoizable module
To use memoize in your model you need to extend the model class with the module, like this:
class Person < ActiveRecord::Base # Mixin the module extend ActiveSupport::Memoizable def expensive_method # do something that is worth remembering end memoize :expensive_method end
If you use memoizable in most of your models you could consider mixing the module into all ActiveRecord models by doing this in an initializer:
ActiveRecord::Base.extend(ActiveSupport::Memoizable)
Universal partial
polymorphic_url is very useful if you want to create an universal partial that works for more than 1 type of object passed to it.
For example in you sidebar you might have a _sidebar.html.erb partial that’s supposed to display links to “Edit” and “Delete” actions. You can write it in such a way that it can be reused for different types of objects (in the example below we pass either a Post or a Note).
your_template.html.erb
<%= render :partial => 'shared/sidebar', :locals => { :obj => Post.new -%>
other_template.html.erb
<%= render :partial => 'shared/sidebar', :locals => { :obj => Note.new -%>
_sidebar.html.erb
<%= link_to "Edit", polymorhpic_url(obj, :action => 'edit') -%> <%= link_to "Delete", polymorphic_url(obj), :method => :delete -%>
link_to some url with current params
Code example
link_to "some text", users_path(:params => params, :more_params => "more params")
Adding params to generated url
Whenever you want to append custom parameters to a to be generated url it might be necessary to stop url_for from escaping.
url_for(:action => 'some_action', :custom1 => 'some_value', :custom2 => 'some_value', :escape => false)
If the escape => false option is not passed the generated url contains & instead of the correct &-sign.
Refactoring excessive code for selects
@garg: It is not recommended to have excessive code in the views. You should refactor your code a bit.
<%= f.select(:manufacturer_id, Manufacturer.find(:all).collect {|u| [u.name, u.id]}, :prompt => 'Select') %>
could be changed to this:
# in app/helpers/manufacturer_helper.rb def manufacturers_for_select Manufacturer.all.collect { |m| [m.name, m.id] } end # in the view <%= f.select(:manufacturer_id, manufacturers_for_select, :prompt => 'Select') %>
I would look into collection_select though:
<%= f.collection_select(:manufacturer_id, Manufacturer.all, :id, :name, :prompt => 'Select') %>
It’s much more clean and you don’t have to define a helper for it to be readable (altough it’s still quite long).
If you have to do this often, you should define a FormBuilder extension, so you get methods like f.manufacturer_select:
<%= f.manufacturer_select(:manufacturer_id, Manufacturer.all) %>
IMO, most projects should have a custom form builder anyway, so the addition would be very small. This is my personal opinion, so you don’t have to listen to it. :-)
Watch out for syntax errors
Watch out when you are using returning with hashes. If you would write code like
def foo(bars) returning {} do |map| bars.each { |bar| map[bar.first] = bar } end end
you will get a syntax error since it looks like you tried to supply two blocks! Instead you should write it with parenthesis around the hash:
def foo(bars) returning({}) do |map| bars.each { |bar| map[bar.first] = bar } end end
Default allowed tags and attributes
I found it a bit hard to find the default tags and attributes in the docs.
As of Rails 2.2.2 they are:
Tags
del, dd, h3, address, big, sub, tt, a, ul, h4, cite, dfn, h5, small, kbd, code, b, ins, img, h6, sup, pre, strong, blockquote, acronym, dt, br, p, div, samp, li, ol, var, em, h1, i, abbr, h2, span, hr
Attributes
name, href, cite, class, title, src, xml:lang, height, datetime, alt, abbr, width
Getting the latest list
You can query for this list yourself with the following code on the console:
>> puts helper.sanitized_allowed_tags.to_a * ", " ... will output tag list ... >> puts helper.sanitized_allowed_attributes.to_a * ", " ... will output attribute list ...
The same principal can probably be applied to sanitize_css.
What to use instead
For versions 2.0+, use ActiveRecord::Base::sanitize_sql_array
link_to_if for named routes
Back before named routes we used to say things like:
<%= link_to_if message.user, 'Poster', :controller => 'users', :action => 'show', :id => message.user %>
This would make the text “Poster” only link if message has a user. But with named routes this has become more complicated. Our first try is something like:
<%= link_to_if message.user, 'Poster', user_path(message.user) %>
Although this looks nice it causes an error since the path is generated prior to the condition not after the condition like using a hash is done. To get around this problem I have found two solutions:
-
If you are linking to the “show” path then you can just pass the object. This will force the path to not be generated until after the condition (like a hash is done). For example:
<%= link_to_if message.user, 'Poster', message.user %>
-
The previous workaround works great if you want to link to the show action. But what if you want to link to another action (say the edit action). In this case the best way I have found is to use the hash_for* helpers generated with the routing resource. So:
<%= link_to message.user, 'Poster', hash_for_edit_user_path(:id => message.user) %>
A little more awkward than the previous workaround but it is the best I can come up with. Any other suggestions (other than going back to manually typing the hash)?
uninitialized constant ActionView::Base::CompiledTemplates::TimeZone
If you get this error, you need to use ActiveSupport::TimeZone.us_zones instead of TimeZone.us_zones.
Example:
<%= form.time_zone_select(:time_zone, ActiveSupport::TimeZone.us_zones) %>
Force initial value
If you want to force an initial value for your text_field which is normally based on your object attribute value, you can use :
text_field :ecard, :sender, :value => 'contact@host.com'
acts_as_state_machine named scopes
If you are using the acts_as_state_machine plugin, this will generate all named scopes for your various states.
Place it after the acts_as_state_machine and state declarations.
class Task < ActiveRecord::Base acts_as_state_machine :initial => :waiting state :waiting state :running state :finished states.each { |s| named_scope s, :conditions => { :state => s.to_s } } end
Then doing a Task.waiting will return the corresponding tasks.
Calling migrations within migrations
It’s very occasionally a wise strategy to call migrations from within other migrations. This is typically done when you are adding a migration that deletes a now-obsolete table.
Let’s say one night when you were drunk or otherwise not thinking straight you did something like this:
class CreateExGirlfriendTexts < ActiveRecord::Migration def self(dot)up create_table :ex_girlfriend_texts { |t| ... } end def self(dot)down drop_table :ex_girlfriend_texts end end
Oops! You could add this for your “undo” migration the next morning:
class FixDrunkMistake < ActiveRecord::Migration def self(dot)up CreateExGirlfriendTexts.down end def self(dot)down CreateExGirlfriendTexts.up end end
Now, in the event you decide you really did like that table, you can always get it back easily. Keep in mind this will be made more complicated if your table is modified over multiple transactions.
Accessing aggregate methods with :group
You can access aggregate methods (such as SUM, COUNT, etc.) when using a JOIN and GROUP BY query by simply naming the aggregate columns and calling them as methods on the returned objects:
hits_by_page = WebpageHit.all({ :select => "webpages.*, COUNT(webpage_hit.id) AS view_count", :joins => :webpage, :group => "webpages.id" }) homepage_hits = hits_by_page[homepage.id].view_count
The view_count method is added to the Webpage model by this call. Note, however, that this method returns a string, and is not typecasted by Rails.
Method doesn't exists
Don’t confuse it with new_record? in ActiveRecord
Types array shorthand
You can have respond_to blocks that look like this:
respond_to do |format| format.html format.xml end
Here each individual format doesn’t receive a block and so Rails automatically tries to render the appropriate view for the mime type (e.g. action.html.erb, action.xml.erb or as a last resort action.erb)
You can do exactly the same thing by passing an array of Mime types to respond_to like this:
respond_to(:html, :xml)
Full List of Supported Formats
With a sample date of December 25th, 2008, at 14:35:05:
:db # => 2008-12-25 14:35:05 :number # => 20081225143505 :time # => 14:35 :short # => 25 Dec 14:35 :long # => December 25, 2008 14:35 :long_ordinal # => December 25th, 2008 14:35 :rfc822 # => Thu, 25 Dec 2008 14:35:05 +0000
Application Helper for Fading Flash Messages
A simple helper method for showing the flash message. Includes optional fade in seconds (view needs javascript_include_tag defaults if you desire fade effect):
def show_flash_message(options={}) html = content_tag(:div, flash.collect{ |key,msg| content_tag(:div, msg, :class => key) }, :id => 'flash-message') if options.key?(:fade) html << content_tag(:script, "setTimeout(\"new Effect.Fade('flash-message');\",#{options[:fade]*1000})", :type => 'text/javascript') end html end
simply call in your views then using:
<%= show_flash_message(:fade => 4) %>
Another Example
Do not mistakenly pass class_name as a key/value pair (Hash form). You will get an error including the text ‘class or module needed’. It should look like this:
serialize :some_array, Array
Or, perhaps clearer would be:
serialize(:some_array, Array)
That may seem obvious, but it is common to be in the habit of passing things as a key/value pair.


