Flowdock

Notes posted by lazylester

RSS feed
July 2, 2014
0 thanks

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).

July 11, 2013
0 thanks

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
October 31, 2010
1 thank

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
November 12, 2009
0 thanks

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)

September 9, 2009
1 thank

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
April 30, 2009
1 thank

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!

March 12, 2009
1 thank

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
February 15, 2009
0 thanks

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.