Notes posted to Ruby on Rails
RSS feedAdding 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
Alternative force initial value
@Bounga - Thanks for the suggestion. Didn’t know about that method. An alternative method I have used is just to assign the default value to the object in your controller. For example your “new” action might now look like:
def new @ecard = Ecard.new params[:ecard] @ecard.sender ||= 'contact@host.com' end
Replace allowed tags/attributes
The docs above state how to add and remove tags from the default list. But what if you just want to replace the entire list with a list of your own? You can easily do that with the following code:
ActionView::Base.sanitized_allowed_tags.replace %w(strong em b i hr br ul ol li blockquote) ActionView::Base.sanitized_allowed_attributes.replace %w(href)
Note that if you put this in your initialization block you must use the config.after_initialize hack (to override the default that will be set) but if you put it in an initializer (i.e. a file in the initializers directory) that code is executed after Rails initialization so no need to use any hack. Just use the code above.
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
current_action? and current_controller?
I use them in link_unless_current_controller helper.
def current_action?(options) url_string = CGI.escapeHTML(url_for(options)) params = ActionController::Routing::Routes.recognize_path(url_string, :method => :get) params[:controller] == @controller.controller_name && params[:action] == @controller.action_name end def current_controller?(options) url_string = CGI.escapeHTML(url_for(options)) params = ActionController::Routing::Routes.recognize_path(url_string, :method => :get) params[:controller] == @controller.controller_name end
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)?
Deprecation warning for using options without hash
As of Rails 2.2.0, truncate gives a Deprecation warning if you don’t use a hash for the options. Ex:
The old way
truncate(project.description, 100, "... Read More")
The warning
DEPRECATION WARNING: truncate takes an option hash instead of separate length and omission arguments.
The new way
truncate(project.description, :ommision => "... Read More", :length => 100)
i think that's a bit better resourceful approach...
Code example
auto_discovery_link_tag :atom, formatted_movies_url(:atom), :title=>'New movies'
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) %>
Incorrectly named option
The :add_month_number option should be :add_month_numbers
Belongs_to, Has_many association
I have a belongs_to, has_many association between manufacturer and modelname. (manufacturer has many modelnames).
In the new modelname page, I have a drop down menu that lists all the manufacturers, so I choose eg Dell, and then in modelname.name field I enter inspiron or what ever. To create that drop down I used:
<%= f.select(:manufacturer_id, Manufacturer.find(:all).collect {|u| [u.name, u.id]}, :prompt => 'Select') %>
The reason that works was explained by fcheung and rsl on #rubyonrails. Thanks:
the form builder basically calls the method whose docs you have read, inserting the appropriate first argument
The object is referenced internally when you call f.whatever. in any of those tags, the object is omitted when used on a form block variable
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.
Example
my_instance.connection.clear_query_cache
:cache_path strangeness
I’ve noticed that using an example like this one shown in the docs has some issues with URI escaping:
caches_action :feed, :cache_path => Proc.new { |controller| controller.params[:user_id] ? controller.send(:user_list_url, controller.params[:user_id], controller.params[:id]) : controller.send(:list_url, controller.params[:id]) } end
When I do this, the :myroute_url methods return URLs with escaped parameters, as one would expect, but for some reason Rails unescapes these strings by the time it uses them to store the cache blob. This is messing up my cache expiration routines, because they keep track of the escaped versions of the urls that are returned by the routing methods, not the unescaped versions. It would be easier if Rails didn’t do any magic to the string and simply used exactly what you pass to cache_path.
Method doesn't exists
Don’t confuse it with new_record? in ActiveRecord
Upgrading from Rails v2.1 to v2.2
Don’t forget to run
rake gems:refresh_specs
if you vendored any gem plugins under Rails v2.1 to update them with the proper specs.
Removed form Rails core
country_select and country_options_for_select were removed from Rails core since 2.2 release, but extracted to plugin. http://github.com/rails/country_select/tree/master/README
Also you could be interested in localized_country_select plugin which uses Rails internationalization framework I18n. http://github.com/karmi/localized_country_select/tree/master/README.rdoc
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
Installation
Install the plugin by typing
script/plugin install auto_complete
Remember to restart webservice :)
Problematic :scroll option
If you use the :scroll => true option, note that at http://github.com/madrobby/scriptaculous/wikis/sortable-create it says:
“If you want your sortable list to be scrollable, wrap the list in a div and set the div to scrollable as apposed to making the ul element scrollable. Also, in IE you must set “position:relative” on the scrollable div.”