Notes posted to Ruby on Rails
RSS feedAvoid DoubleRenderError
One can not invoke render twice during an action. Thus if You have a complicated rendering logic but at the end would like to render some default content, or just would like to find out whether render has been called during the current action, use performed?. This also works with “empty” renderings such as head.
Rendering After Exception In respond_to() Block
Remember, format blocks set the response’s content type. This can present problems when handling errors.
class MediaController rescue_from ActionController::MissingFile do |e| # User's browser probably wont display this # Content-Type is application/x-shockwave-flash render :file => File.join(Rails.public_path, '404.html'), :status => 404 end # show details or stream video def show @media = Media.find params[:id] respond_to do |format| format.html format.flv { send_file @media.path, :disposition => 'inline' } end end end
For these situations you must set :content_type when calling render:
render :file => File.join(Rails.public_path, '404.html'), :status => 404, :content_type => 'text/html'
Dynamic exists? methods
There are no dynamic exists? methods analogous to dynamic finders, which means that while you can do this:
Person.find_by_name('David')
you can’t do this:
Person.exists_by_name('David') # DOES NOT WORK
nor this:
Person.exists_by_name?('David') # DOES NOT WORK
However, you can simulate this with dynamic scope:
Person.scoped_by_name('David').exists?
You’ll have to admit that this is so much better than the plain old method:
Person.exists?(:name => "David")
nil Argument Raises An I18n::ArgumentError
You might want to do this:
module ActionView module Helpers module TranslationHelper def localize(*args) #Avoid I18n::ArgumentError for nil values I18n.localize(*args) unless args.first.nil? end # l() still points at old definition alias l localize end end
end
Does not work with polymorphic relations
If you have polymorphic relations, e.g.:
class Bookmark < ActiveRecord::Base belongs_to :thing, :polymorphic => true belongs_to :owner, :polymorphic => true end
and you want to ensure that a thing can bookmarked by an owner at most once, you can’t do this:
validates_uniqueness_of :thing, :scope => :owner
Instead, you must use the real column names, e.g.:
validates_uniqueness_of :thing_id, :scope => [:thing_type, :owner_id, :owner_type]
The :method goes in the :html option
When using a restful form helper and you want to use a method other than POST, remember to put the :method in the :html option.
e.g. To send a DELETE request instead of the usual POST (with a nested resource thrown in for good measure) use:
<% form_for [@post, @comment], :html => { :method => :delete } do |f| -%>
:autosave => false vs. :autosave => nil
The documentation above mentions that :autosave => true always saves the association and that it’s “off” by default. What it doesn’t mention what they mean by “off”.
-
:autosave => nil (the default “off” behavior) will still autosave the association if it has changed or is a new record.
-
:autosave => false seems to prevent autosaving of the association, even if it has changed.
I’ve found :autosave => false to be useful behavior when trying to prevent cyclical dependency loops; there are likely other useful use cases out there.
Careful with this method.
Despite the name and description, it will actually update any changed fields on the model rather than just the desired attribute.
def update_attribute(name, value) send(name.to_s + '=', value) save(false) end
See? Use update_all and pass in the model ID as a condition, instead.
Fetching records when column is set to nil or false
If you want to fetch all records when one column (boolean) is set to nil or false, try this:
Project.all(:conditions => "archived IS NULL OR archived = 'f'")
Always gracefully degrade if JS isn't available
If you always want to degrade when JS isn’t available you can add something like to environment.rb
module ActionView module Helpers module PrototypeHelper def link_to_remote(name, options = {}, html_options = nil) html_options ||= {} html_options[:href] ||= options[:url] link_to_function(name, remote_function(options), html_options || options.delete(:html)) end end end end
Wrong example
In the authentication filter example above, the time condition should be reversed: we only want to find the user if time is still in the future (because it’s the valid-until time).
So the example should look like this:
id, time = @verifier.verify(cookies[:remember_me]) if time > Time.now self.current_user = User.find(id) end
How to test callback methods
When testing callback methods, try to test the callback chain separate from its implementation.
Say this is your model:
class Project belongs_to :owner has_many :milestones after_save :create_milestones after_save :notify_owner private def notify_owner owner.project_created! end def create_milestones milestones.create(:name => 'Milestone 1') end end
You should write your spec like this:
describe Project do describe 'create_milestones' do it 'should create an initial milestone' do project = Project.new project.milestones.should_receive(:create) project.send(:create_milestones) end end describe 'notify_owner' do it 'should notify its owner' do project = Project.new(:owner => mock_model(User)) project.owner.should_receive(:project_created!) project.send(:notify_owner) end end describe 'after_save' do it 'should run the proper callbacks' do project = Project.new project.should_receive(:create_milestones) project.should_receive(:notify_owner) project.run_callbacks(:after_save) end end end
Here is some more advice on how to test callback methods in Rails:
http://gem-session.com/2010/03/how-to-test-callback-methods-in-rails
Using models in your migration
Here is some advice how to call your models in a migration without shooting yourself in the foot:
http://gem-session.com/2010/03/how-to-use-models-in-your-migrations-without-killing-kittens
Basically you can inline models into your migrations to decouple them from changes in your model:
class AddCurrentToVendor < ActiveRecord::Migration class Vendor < ActiveRecord::Base end class Article < ActiveRecord::Base has_many :vendors, :class_name => 'AddCurrentToVendor::Vendor', :order => 'created_at' end def self.up add_column :vendors, :current, :boolean Article.all.each do |article| article.vendors.first.andand.update_attribute(:current, true) end end def self.down remove_column :vendors, :current end end
How to test custom error pages
Here is some advice for testing custom error pages using Webrat and Cucumber:
http://gem-session.com/2010/03/testing-your-custom-error-pages-with-webrat-and-cucumber
Setting primary key from hash
If you try to specify the value for your primary key (usually “id”) through the attributes hash, it will be stripped out:
Post.new(:id => 5, :title => 'Foo') #=> #<Post @id=nil @title="Foo">
You can solve this by setting it directly, perhaps by using a block:
Post.new(:title => "Foo") { |p| p.id = 5 } #=> #<Post @id=5 @title="Foo">
This behavior is something you’d probably only have a problem with when you have custom primary keys. Perhaps you have a User model with a primary key of “name”…
class User < ActiveRecord::Base set_primary_key :name end User.new(params[:user]) # This will never work
You can solve this on a case-to-case basis by calling attributes= directly with the “ignore protected” option:
User.new { |user| user.send(:attributes=, params[:user], false) } # BAD BAD BAD!
You should not do the above example, though. If you do, all protected attributes are ignored, which is very, very bad when you only care about the primary key.
I’d recommend one of the following instead:
# Option 1 – Always allow primary key. Avoid with models created by users class User private def attributes_protected_by_default super - [self.class.primary_key.to_s] end end # Option 2 – Add a new method for this case class User def self.new_with_name(attributes = nil) new(attributes) { |u| u.name = attributes[:name] } end end
As always when something is hard to do in Rails: Think about your design? Is it recommended? Is it sound? Do you really need to have a custom primary key?
named_scopes and Acts as State Machine
As of AASM 2.1 named_scope(s) are defined by default for each state of a model.
singular_class_name method missing
If this method is undefined in ActionView::Base, add to your ApplicationController:
class ApplicationController < ActionController::Base helper_method :singular_class_name end
Validations
out of the box touch will run with validations
config/environments/production.rb
old
ActionController::AbstractRequest.relative_url_root= "/exampleapp"
new
config.action_controller.relative_url_root= "/exampleapp"
Can be used with has_many associations
You can also use this to validate that a has_many association has a specified number of records on the other end:
has_many :members validates_length_of :members, :minimum => 1
Skips validations and callbacks
The method skips validations and callbacks. That is why it should be used with caution.
Code example
person.toggle :active
Available statuses
All the available statuses (extracted from SYMBOL_TO_STATUS_CODE hash) in a slightly more readable form:
:continue => 100 :switching_protocols => 101 :processing => 102 :ok => 200 :created => 201 :accepted => 202 :non_authoritative_information => 203 :no_content => 204 :reset_content => 205 :partial_content => 206 :multi_status => 207 :im_used => 226 :multiple_choices => 300 :moved_permanently => 301 :found => 302 :see_other => 303 :not_modified => 304 :use_proxy => 305 :temporary_redirect => 307 :bad_request => 400 :unauthorized => 401 :payment_required => 402 :forbidden => 403 :not_found => 404 :method_not_allowed => 405 :not_acceptable => 406 :proxy_authentication_required => 407 :request_timeout => 408 :conflict => 409 :gone => 410 :length_required => 411 :precondition_failed => 412 :request_entity_too_large => 413 :request_uri_too_long => 414 :unsupported_media_type => 415 :requested_range_not_satisfiable => 416 :expectation_failed => 417 :unprocessable_entity => 422 :locked => 423 :failed_dependency => 424 :upgrade_required => 426 :internal_server_error => 500 :not_implemented => 501 :bad_gateway => 502 :service_unavailable => 503 :gateway_timeout => 504 :http_version_not_supported => 505 :insufficient_storage => 507 :not_extended => 510
Good way to see what went wrong
Use the message parameter like that:
assert_response :success, @response.body
If this fails (the response isn’t a success), it will display the response body along with the failure message, thus allowing you to quickly find out what went wrong. If the response is e.g. 500, there will probably be some exception stacktrace displayed in the body. And so on.
Specify your own template
You can specify you own template this way:
def notice ... @template = "some_other_name.html.erb" end
AASM named scopes
If you are using the aasm plugin/gem, this will generate all named scopes for your various states.
Code example
Class Article < ActiveRecord::Base include AASM aasm_initial_state :created aasm_state :published aasm_state :unpublished aasm_state :deleted aasm_state :created aasm_event :publish do transitions :to => :published, :from => [:created] end aasm_event :unpublish do transitions :to => :unpublished, :from => [:created, :published] end aasm_event :delete do transitions :to => :deleted, :from => [:published, :unpublished] end aasm_states.each { |s| named_scope s, :conditions => { :state => s.to_s } } end
Re: Caveat when using dynamic layouts
Since there’s no way to edit posts on here, I need to correct myself and say that what I posted before doesn’t work, since you can’t specify layout multiple times:
class OrdersController < BaseController layout :determine_layout, :only => :new layout "public", :except => :new # ... end
So don’t do that. The only way to ensure that the other actions get the default theme is to drop :only/:except and do the conditions yourself:
class OrdersController < BaseController layout :determine_layout private def determine_layout %w(new).include?(action_name) ? "some_layout" : "public" end end
All this to say, beware of :only/:except – they aren’t as useful as you think they are.
Caveat when using dynamic layouts
Worth noting that if you have a controller which inherits from another controller which has a layout, and in this child controller you’re determining the layout at runtime using a method for specific actions, the other actions you are excluding will not inherit the layout from the parent controller.
For example, if you’ve got this
class BaseController < ApplicationController layout "public" end class OrdersController < BaseController layout :determine_layout, :only => :new # index, show, new, create, edit, update, destroy ... end
then OrdersController#index, #show, and #edit won’t get the “public” layout – in fact they won’t get a layout at all. So you’ll need to do this instead:
class OrdersController < BaseController layout :determine_layout, :only => :new layout "public", :except => :new # ... end
default_scope on create
If you specify :conditions in your default_scope in form of a Hash, they will also be applied as default values for newly created objects. Example:
class Article default_scope :conditions => {:published => true} end Article.new.published? # => true
However:
class Article default_scope :conditions => 'published = 1' end Article.new.published? # => false
configuration no longer in environment.rb
configure session store in config/initializers/session_store.rb
Saving other objects inside before_save
Don’t call .save or .update_attribute on other objects inside before_save callback.
Saving other objects inside of before_save callback results in flushing changed hash and the original object is not updated.
UPDATE observed sometimes, still investigating