Notes posted to Ruby on Rails
RSS feedA named_scope also responds to model class methods
for instance
class Student < ActiveRecord::Base
named_scope :sophomore, :conditions => 'year=2' def self.eligible_to_vote select{|s| s.age >= 18} end end ss = Student.sophomore.eligible_to_vote
Pluralize Without Count (inline version)
= pluralize(item.categories.count, ‘Category’).sub(/d+s/, ”)
Polymorphic has_many within inherited class gotcha
Given I have following classes
class User < ActiveRecord::Base end class ::User::Agent < ::User has_many :leads, :as => :creator end
I would expect, that running
User::Agent.first.leads
will result in following query
SELECT "leads".* FROM "leads" WHERE ("leads".creator_id = 6 AND "leads".creator_type = 'User::Agent')
however it results in
SELECT "leads".* FROM "leads" WHERE ("leads".creator_id = 6 AND "leads".creator_type = 'User')
Possible solutions:
-
Make User class use STI - polymorphic relations will then retrieve correct class from :type field (however in my situation it was not an option)
-
If You do never instantiate User class itself, mark it as abstract
class User < ActiveRecord::Base self.abstract_class = true end
-
If You do instantiate User class, as last resort You can overwrite base_class for User::Agent
class ::User::Agent < ::User has_many :leads, :as => :creator def self.base_class self end end
-
If none of above is an option and You do not care that You will lose some of relation’s features, You can always
class User::Agent < ::User has_many :leads, :as => :creator, :finder_sql => %q(SELECT "leads".* FROM "leads" WHERE ("leads".creator_id = #{id} AND "leads".creator_type = 'User::Agent')) end
Use super to override, and not alias_method_chain
Somehow, If you want to extend the behaviour of attributes=,
alias_method_chain does not work. It simply breaks (could not find out how exactly).
def attributes_with_some_feature=(new_attributes, guard_protected_attributes = true) attributes_without_some_feature=(new_attributes, guard_protected_attributes) end alias_method_chain :attributes=, :some_feature
this breaks the code. dynamic finders and assignments didn’t work as before (Even though no behaviour has changed yet).
Instead,
def attributes=(new_attributes, guard_protected_attributes = true) # custom code super(new_attributes, guard_protected_attributes) end
does work as expected.
I prefer using alias_method_chain for breaking open existing functionality, but in this case it won’t work.
null to no effects
change_column will not query to replace the null values when you change null to false, even if you have a default set. This may cause the query to fail (may depend on the database used).
change_column_null will optionally take a value to replace nulls if you are setting null to false. If you want to set a default and disallow nulls you likely can’t do both in one change_column call.
doesn't work directly off a class.
for some reason this method only works on relation objects, not directly on an AR class.
# doesn't work User.offset(3).limit(1) # does work User.limit(1).offset(3)
there’s an closed ticket for this here http://rails.lighthouseapp.com/projects/8994/tickets/5688-modeloffsetxlimitx-unknown-offset-method-exception and should be resolved in the next release of rails.
still broken
add_index is a different method. I think this is just a bug and it’s broken.
Validate number
option like :greater_than still supported
use like this
Code example
validates :position, :presence => true, :numericality => {:greater_than => 0}
use raw() instead
Don’t use this method unless you’re sure your string isn’t nil. Instead use the raw() method, which wont raise an exception on nil.
Looking for the docs?
Check the ClassMethods – the docs on filters are there.
needs to be paired with respond_to
Needs to be paired with respond_to at the top of your class.
class MyController < ApplicationController respond_to :js, :html
Sending array parameters
Another technique to use when you need to send an array parameter is pass in the :multiple option.
check_box("puppy", "commands", {:multiple => true}, "sit", nil) check_box("puppy", "commands", {:multiple => true}, "fetch", nil) check_box("puppy", "commands", {:multiple => true}, "roll_over", nil)
If all checkboxes are checked, the paramters will be:
"puppy" => {"commands" => ["sit", "fetch", "roll_over"]}
NOTE: because of the gotcha, the hidden fields will be inserted and any unchecked boxes will be sent as “” (empty string). You will need to filter those values out in your model:
class Dog < ActiveRecord::Base def commands=(commands) commands.reject(&:blank?) end end
Replacement
Use sanitize or connection.quote instead.
Use ModelClass.model_name.human
eg. Person.model_name.human will return the i18n name for the model.
see for more info: ActiveModel::Translation
Preserve order of elements within fields_for
I had the @colleagues collection prepared that I wanted to be rendered within fields_for block. However it was searchlogic object with filtering/sorting applied, and the sort order was not preserved in resultant view.
<%= f.fields_for :evaluators, @colleagues do |builder| %> <%= render "colleague", :f => builder %> <% end %>
Solution to this problem was typecasting this collection to array
<%= f.fields_for :evaluators, @colleagues.to_a do |builder| %> <%= render "colleague", :f => builder %> <% end %>
Custom validator with i18n support
Here is modified EmailValidator from the example above:
class EmailValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) record.errors.add(attribute, options[:message] || :email) unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i end end
And locale:
en: activerecord: errors: messages: email: "is not an email"
Anchor option
It’s not documented, but :anchor is an option.
polymorphic_path(commentable, :anchor => 'comments')
will return:
/article/1#comments
Mistake in example for Rails 3
This code doesn’t work:
activemodel: attribute: post: cost: "Total cost"
Everything is OK with this one:
activerecord: attributes: post: cost: "Total cost"
Nice german translation independet of structure of a sentence
Eine pragmatische Lösung für das Übersetzungsproblem der Rails Methode distance_of_time_in_words(). Im Deutschen wird je nach Satzbau eine andere Ausgabe benötigt.
Vor mehr als 5 Monaten“/”Vor etwa einem Jahr“ — statt wie im Original ”Dauer: mehr als 5 Monate“/”Dauer: etwa 1 Jahr
About the options argument
The options are not documented, but of course you can use the same options than submit_tag.
Note that all non-documented options are simply passed to the input tag. Amongst other things, this allows you to change the default name attribute (commit):
form.submit 'Cancel', :name => 'cancel'
That’s very handy in forms with multiple submit buttons, this way the controller can easily check in the params which action was submitted.
using joins, group, having
Code example
named_scope :red, :joins => [:color_lis => :color], :group => "color.id", :having => ["color.xx IS NULL"]
No Layout (fixed typo)
@wiseleyb: Seems to be a typo, should be:
render_to_string(:action => "users/profile", :layout => false)
Documentation of I18N
lives f.ex. here: http://rdoc.info/github/svenfuchs/i18n/master/I18n
Re: Doesn't work? Don't think it ever has.
Instead of
create_table :user_follows, :force => true do |t| t.references :user t.references :followed_user t.timestamps t.index :user t.index :followed_user end
Try
create_table :user_follows, :force => true do |t| t.references :user t.references :followed_user t.timestamps end index :user_follows , :user index :user_follows , :followed_user
It looks like the provided examples are incorrect…
No concurrency
If you want to handle concurrency, this doesn’t work:
a = Article.first b = Article.first a.increment!(:view_count) b.increment!(:view_count) a.reload.view_count # -> 1 b.reload.view_count # -> 1
Instead, use SQL:
def increment_with_sql!(attribute, by = 1) raise ArgumentError("Invalid attribute: #{attribute}") unless attribute_names.include?(attribute.to_s) original_value_sql = "CASE WHEN `#{attribute}` IS NULL THEN 0 ELSE `#{attribute}` END" self.class.update_all("`#{attribute}` = #{original_value_sql} + #{by.to_i}", "id = #{id}") reload end
bad idea.
Just a note, ypetya’s idea of using a before filter to set the primary key wont scale. transactions will eventually step on each other and probably end up with duplicate key ids, unless you have some other method to ensure uniqueness.
You’d be better off using mysql to generate the default integer primary key and have a secondary string “key” field.
Doesn't work? Don't think it ever has.
This doesn’t work for me. I do something like:
create_table :user_follows, :force => true do |t| t.references :user t.references :followed_user t.timestamps t.index :user t.index :followed_user end
and I get:
rake aborted! An error has occurred, all later migrations canceled: undefined method `index' for #<ActiveRecord::ConnectionAdapters::TableDefinition:0x106c02220>
add_index has the same effect.