Flowdock

Notes posted by lazylester

RSS feed
May 2, 2017
0 thanks

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'
January 5, 2017
0 thanks

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
March 26, 2015
0 thanks

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.

January 5, 2015
1 thank

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.

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
2 thanks

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.