Notes posted by lazylester
RSS feed
And yet another way to get relative path from absolute globbing
If you execute glob within a block passed to Dir.chdir, you get the paths relative to the directory specified by Dir.chdir… like this…
base_dir = '/path/to/dir' files = Dir.chdir(base_dir) do Dir.glob("**/*.yml") end files.first # => 'foo/bar.yml'

Clarifying the confusing example
since exit is a keyword in Ruby, the example may be confusing. The following example might be less so:
module Foo begin # this raises an error b/c baz is not defined here alias_method :other_baz, :baz rescue NameError =>e puts e end def baz puts "first baz called" end # now that baz method is defined, we can define an alias alias_method :other_baz, :baz # we can now overwrite baz. # If we want the original baz, use the alias we just defined def baz puts "second baz called" end def qux puts "qux called" end alias_method :bar, :qux end include Foo # calls the second baz method, b/c it overwrites the first baz #=> "second baz called" # calls the first baz method, due to the alias_method making a copy other_baz #=> "first baz called" bar #=> "qux called" qux #=> "qux called"
The resulting output is:
undefined method `baz' for module `Foo' second baz called first baz called qux called qux called

Save yourself a little typing
We often have a form with a select box that selects a model association. So for example to select a colour, when there is an associated Colour model, a select box will typically select :colour_id.
In this case, ActionView automatically humanizes :colour_id to produce “Colour” as the label text.

arguments do not need to be an array
it’s a small point, but if you look at the source, the method is defined with the splat operator in the arguments:
def select (*fields)
this means that a list of arguments is automatically converted to an array. There is no typo in the description above.
It will also work to pass an array:
select([:field1, :field2])
although the select method interprets this as a single argument, and places it into an array (due to the splat operator), this is then passed to the _select(*fields) method, which immediately calls fields.flatten!
So either a list or an array may be passed, both will work.

time is represented in the Rails app's timezone
From the description of Time.current (which is a Rails extension of Ruby’s native Time class)
“Returns Time.zone.now when Time.zone or config.time_zone are set, otherwise just returns Time.now.”
so 3.days.ago, for example, is in the application’s configured timezone (if it’s configured).

Using fields_for with collection (no association)
accepts_nested_attributes_for has some detractors: http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models
But fields_for is defined both on FormBuilder and FormHelper and is still useful even when accepts_nested_attributes_for is not being used. Consider a “to do list” application where we wish to edit and update a list of tasks (Task model). I like to use a TaskCollectionController to manage the actions on the collection, it feels more RESTful than overloading a TaskController:
class TaskCollectionController < ApplicationController # task_collection_edit GET /task_collection/edit def edit @tasks = Task.all end # task_collection_update PUT /task_collection/update def update # it would be better to use a TaskCollection form object here # to make this controller skinny, # for the sake of brevity I skipped the form object params[:tasks].values.each do |attrs| if attrs[:_destroy] == '1' Task.find(attrs[:id]).destroy elsif attrs[:id].blank? Task.create(attrs.slice!(:id,:_destroy)) else Task.find(attrs[:id]).update_attributes(attrs.slice!(:id,:_destroy)) end end redirect_to task_collection_edit_path end end
In the edit view, I use fields_for to index added tasks, and it also will insert the id as a hidden field for existing tasks:
%h1 Edit Task Collection = form_tag task_collection_update_path, :method => :put do %table#tasks %tr %th Name %th Priority - @tasks.each do |task| = fields_for 'tasks[]', task, :hidden_field_id => true do |task_form| = render :partial => 'task_form', :locals => {:task_form => task_form} = button_tag 'Save' = button_tag "Add task", :type => :button, :id => :add_task %script{:type => 'html/template', :id => 'task_form_template'} = fields_for 'tasks[]', Task.new, :index => 'NEW_RECORD', :hidden_field_id => true do |task_form| render(:partial => 'task_form', :locals => {:task_form => task_form}); end :javascript $(function(){ task_form = function(){ return $('#task_form_template').text().replace(/NEW_RECORD/g, new Date().getTime())} var add_task = function(){ $('#tasks').append(task_form()) } $('#add_task').on('click', add_task) })
:hidden_field_id => true triggers the insertion of the id field, and a placeholder index “NEW_RECORD” is replaced by javascript when a task is added, as others have described here. When there’s no association, the index key is :index, vs. :child_index in the case of an association.
Here’s the partial used in the edit view:
%tr %td= task_form.text_field :name %td= task_form.select :priority, [1,2,3,4,5] %td= remove_item(task_form)
The remove_item helper triggers the deletion of persisted tasks:
module TaskCollectionHelper def remove_item(form_builder) if form_builder.object.new_record? # If the task is a new record, remove the div from the dom link_to_function( 'Remove', "$(this).closest('tr').remove()") else # However if it's a "real" record it has to be deleted from the database, # hide the form and mark for destruction form_builder.hidden_field(:_destroy) + link_to_function( 'Remove', "$(this).closest('tr').hide(); $(this).siblings().attr('value',1)") end end end

A 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

Rails documentation for nested attributes
ActiveRecord/NestedAttributes/ClassMethods
(don’t follow this link, the url interpreter isn’t rendering it correctly :(, but the correct link is at the top of this page)

extend adds class methods too
Because classes are objects. So for example:
module Ispeak def says "greetings aliens!" end end module Ieat def eats "spinach" end end module Inhabitant def says "I'm strong to the finish" end end class Human extend Ispeak # add class methods from Ispeak include Inhabitant # add instance methods from Inhabitant end Human.extend Ieat # add class methods from Ieat puts Human.says # -> greetings aliens! puts Human.eats # -> spinach popeye = Human.new puts popeye.says # -> I'm strong to the finish

Caveat and design hints regarding :counter_cache
(From Obie Fernandez/ The Rails Way, ISBN 978-0321445612. Thanks Obie!)
This caveat:
The value of the counter cache column must be set to zero by default in the database! Otherwise the counter caching won’t work at all. It’s because the way that Rails implements the counter caching behavior is by adding a simple callback that goes directly to the database with an UPDATE command and increments the value of the counter.
And these tips:
If a significant percentage of your association collections will be empty at any given moment, you can optimize performance at the cost of some extra database storage by using counter caches liberally. The reason is that when the counter cache attribute is at zero, Rails won’t even try to query the database for the associated records!
If you’re not careful, and neglect to set a default value of 0 for the counter cache column on the database, or misspell the column name, the counter cache will still seem to work! There is a magic method on all classes with has_many associations called collection_count, just like the counter cache. It will return a correct count value if you don’t have a counter cache option set or the counter cache column value is null!

Comparing Date with Numeric in mixed sort
While:
Date#<=>(other)
can accept a Numeric object as other, the reverse is not true:
Numeric#<=>(other)
cannot accept a Date object as other.
So if you are sorting a list containing a mix of dates and numbers, you can get different results depending on the starting order!
a = Date.parse("2008-01-01") b = Date.parse("2009-10-22") c = Date.parse("2005-01-04") d = 0 [a,b,c,d].sort #=> [0, Tue, 04 Jan 2005, Tue, 01 Jan 2008, Thu, 22 Oct 2009] [b,c,d,a].sort #=> ArgumentError: comparison of Fixnum with Date failed

use #collect instead of #each
The earlier reminder to use #collect instead of #each applies regardless of whether the tag is nested or not.
This is counterintuitive, as #collect returns an array of strings of HTML tags, but ActionView renders it properly.