Notes posted to Ruby on Rails
RSS feedNamed scope better than conditions
In modern versions of Rails, in most cases a named_scope is a better alternative to using :conditions on your has_many relations. Compare:
class User has_many :published_posts, :conditions => {:published => true} end user.published_posts
with:
class Post named_scope :published, :conditions => {:published => true} end class User has_many :posts end user.posts.published
It’s better because the Post’s logic (“am I published?”) should not be coupled within User class. This makes it easier to refactor: e.g. if you wanted to refactor the boolean :published field into a :status field with more available values, you would not have to modify User class. Having to modify User when you refactor some implementation detail of Post class is clearly a code smell.
This also applies to :order, :group, :having and similar options.
As of v2.3.4 you can use Symbols
This commit fixes using a Symbol with assert_template
http://github.com/rails/rails/commit/f383a4aa333cd8a99003eb1bdbb27b6fdea1056c
assert_template :new # works in 2.3.4
Setting a custom Content type
The given example seems to be broken. The :mime_type option as well as the [] access on the Mime::Type class are both not working.
The following code allows the custom setting of content types as intended by the original example:
class PostsController < ActionController::Base def show @post = Post.find(params[:id]) respond_to do |format| format.html format.ics { render :text => post.to_ics, :content_type => Mime::Type.lookup("text/calendar") } format.xml { render :xml => @people.to_xml } end end end
To use the memcached gem
If you use the mem_cache_store it will use “memcache-client” to talk to the actual cache. memcache-client is a pure Ruby library that is bundled with rails. There is a Ruby/C library called “memcached” that uses native bindings to talk to memcache, and it is reportedly (http://blog.evanweaver.com/files/doc/fauna/memcached/files/README.html) up to a 100 times faster than memcache-client. To use that instead,
# in shell $ sudo gem install memcached --no-rdoc --no-ri # in config/production.rb require 'memcached' config.action_controller.cache_store = :mem_cache_store, Memcached::Rails.new("localhost:11211")
This feature (to pass a configured MemCache-like object to cache_store=) is available since rails 2.3.3 and will hopefully be documented in 2.3.5.
How FormBuilders work
What, you were expecting documentation? :)
An excellent survey of how FormBuilders work is here:
http://code.alexreisner.com/articles/form-builders-in-rails.html
Outputs name even if condition is false
Please note that if the condition is false, link_to_if will still output the name given as a plain text (as documented above). If you want nothing printed at all, you’ll have to stay with the old and trusty:
link_to "Login", ... if @current_user.nil?
Outputs name even if condition is false
Please note that if the condition is false, link_to_if will still output the name given as a plain text (as documented above). If you want nothing printed at all, you’ll have to stay with the old and trusty:
link_to "Login", ... if @current_user.nil?
Only works within a transaction
Of course, this has to be done in a transaction, like so:
# we've loaded user earlier and did some checks which took some time # to make sure, updates made meanwhile by other threads don't lead to # optimistic locking errors here, we do this when finally suspending User.transaction do user.lock! user.suspended = 1 user.save! end
Setting child_index while using nested attributes mass assignment with prototype
First of all, drogus idea really helped me. I’m not using jQuery, therefore I implemented my own version:
link_to_function
def add_object_link(name, where, render_options) html = render(render_options) link_to_function name, %{ Element.insert('#{where}', #{html.to_json}.replace(/index_to_replace_with_js/g, new Date().getTime())); } end
Using add_object_link
<%= add_object_link 'Add asset', 'assets', :partial => 'assets/asset', :object => Asset.new, :locals => { :f => f } %>
Update statement won't include all attributes with ActiveRecord::Dirty
With the addition of ActiveRecord::Dirty, the update statement will only feature changed columns, as opposed to the comment of railsmonk below.
Re: Passing parameters to before_filter
I am not sure I get your “method 1” alec-c4; won’t that define the method each time the before_filter is called? Why not just define the method in the controller?
You can pass parameters or call protected methods with instance_eval:
before_filter :only => :show do |controller| controller.instance_eval do redirect_to edit_object_path(params[:id]) end end
No numbers or symbols
“Kyle”, “Дети”, “Niños”, “Quan-lu”, “た ち”
validates_format_of :first_name, :with => /^([^\d\W]|[-])*$/
Have check_box checked by default
In addition to comment below, you can make a column with default value so in your forms it will be enabled by default and behave correctly with validation errors unlike :checked => true
in your migration
add_column :accounts, :ssl_enabled, :boolean, :default => 1
Current version
See ActiveSupport::Memoizable for un-deprecated version.
build_association deletes existing dependent record
Surprisingly (at least I was surprised), when an associated record exists, the build_association method immediately NULLs the foreign key in the database.
So if you write a singleton “new” action for the association in the obvious way (calling build_association), then just visiting the page will disconnect an existing associated record. This violates the principle that a GET request shouldn’t affect the database.
To avoid this, you can check for an existing association first, and redirect to the show action.
Use it to solve FixtureClassNotFound errors.
If you are using a non standard table name by means of set_table_name in your model:
class MyClassName < ActiveRecord::Base set_table_name "mytablename" end
then you will get FixtureClassNotFound errors when you try to use fixtures in you unit tests. To solve this use set_fixture_class inside your test:
require 'test_helper' class MyClassNameTest < ActiveSupport::TestCase set_fixture_class :mytablename => MyClassName end
and rename your fixture file to mytablename.yml
Streaming XML with Builder
To generate larger XMLs, it’s a good idea to a) stream the XML and b) use Active Record batch finders.
Here’s one way of doing it:
def my_action @items = Enumerable::Enumerator.new( Item.some_named_scope, :find_each, :batch_size => 500) respond_to do |format| format.xml do render :text => lambda { |response, output| extend ApplicationHelper xml = Builder::XmlMarkup.new( :target => StreamingOutputWrapper.new(output), :indent => 2) eval(default_template.source, binding, default_template.path) } end end end
The Builder template does not need to be modified.
Use camelize with singular words
To make the business example work, use camelize instead of classify:
"business".camelize # => "Business"
sort_by
array.sort_by {|element| [element.foo, element.bar.name, element.baz]}
Taken from http://redcorundum.blogspot.com/2006/10/using-sortby-instead-of-sort.html
Making ActiveRecord models readonly
To force an ActiveRecord model to be read only you can do something along these lines:
class DelicateInfo < ActiveRecord::Base def readonly? true end end
When you try to save the model it will raise an ActiveRecord::ReadOnlyRecord exception:
info = DelicateInfo.first info.save # => ActiveRecord::ReadOnlyRecord
Note, however, that destroy and delete will still work on the model unless you intercept those calls
form_tag with named route and html class
<% form_tag position_user_card_path(@user, card), :method => :put, :class => ‘position-form’ do %>
This code prevent's frome redirect_to(:back) looping
Prevent redirect_to(:back) looop
begin
# loop check if session[:last_back] != request.env['HTTP_REFERER'] redirect_to(:back) session[:last_back] = request.env['HTTP_REFERER'] else # raise on error raise ActionController::RedirectBackError end
rescue ActionController::RedirectBackError
# fallback on loop or other :back error redirect_to(:action => :index)
end
ActiveResource validation is a little different
Given the following model on the remote end:
class Person < ActiveRecord::Base validates_presence_of :first_name, :last_name, :email end
And this ActiveResource on the client end:
class Person < ActiveResource::Base self.site = "http://api.people.com:3000/" end
Validation messages will only be returned after an attempted save call on the client end - eg:
person = Person.new( :first_name => 'Billy', :emails => "william@anotherplanet.co.za" ) person.valid? # => true person.errors.full_messages # => [] person.save # => false person.valid? # => false person.errors.full_messages # => ["Last name can't be empty"]
In ActiveResource::Base it is suggested that you can perform client site validation with something like this:
class Person < ActiveResource::Base self.site = "http://api.people.com:3000/" protected def validate errors.add("last name", "can't be empty") if last_name.blank? end end
Update attributes on mulitple models
Updates attributes on multiple models, saves each if validations pass.
def update_multiple
@items = Item.find(params[:item_ids]) @items.each do |item| item.attributes = params[:item].reject { |k,v| v.blank? } end if @items.all?(&:valid?) @items.each(&:save!) flash[:notice] = "Updated items!" redirect_to items_path else flash[:notice] = "Please enter valid data." render :action => 'edit_multiple' end end
See also: ActiveRecord::Base#increment
This is a class-level method. For the instance-level equivalent see: ActiveRecord::Base#increment
item = Item.find(1) item.foo_count # => 0 Item.increment_counter(:foo_count, 1) item.foo_count # => 0 item.reload item.foo_count # => 1 item.increment(:foo_count) item.foo_count # => 2
Passing parameters to before_filter
I’ve found on the net 2 ways to pass parameters to before_filter:
method 1:
before_filter do |c| c.class.module_eval do private def custom_filter authorize(args) end end end before_filter :custom_filter
method 2:
before_filter do |c| c.send(:authorize, args) end