Wednesday, April 22, 2009

Time, DateTime and to_yaml

This is interesting. If you convert an instance of DateTime to YAML and reload it using YAML.load, it comes back as an instance of Time, minus the fractional seconds.

Also, if you convert a DateTime to UTC then to a Time, it drops the fractional seconds. Converting a DateTime directly to a Time does NOT drop the fractional seconds, nor does converting a Time to UTC. Weird.

As you can see, I'm using ActiveSupport. I don't really know how much (or if any) that is affecting this weird behavior.

Heh, I don't really know if "fractional seconds" is proper terminology either.

Sunday, July 20, 2008

eager loading + limit + order in named scope = ActiveRecord 2.1.0 bug

I found this bug while writing my pagination plugin that uses named scopes. It's caused by an oversight in construct_finder_sql_for_association_limiting and manifests when the following three conditions are met:

  • there is a has_many association being eager loaded
  • the query is being limited
  • there is order specified by a named scope

I fixed the bug by making modifications to construct_finder_sql_for_association_limiting. You can apply the fix by including the following code into your Rails project:

module ActiveRecord
  module Associations
    module ClassMethods

      def construct_finder_sql_for_association_limiting(options, join_dependency)
        scope       = scope(:find) || {}
        order       = [options[:order], scope[:order]].compact.join(', ')

        # Only join tables referenced in order or conditions since this is particularly slow on the pre-query.
        tables_from_conditions = conditions_tables(options)
        tables_from_order      = order_tables(options)
        all_tables             = tables_from_conditions + tables_from_order
        distinct_join_associations = all_tables.uniq.map{|table|
          join_dependency.joins_for_table_name(table)
        }.flatten.compact.uniq

        is_distinct = !options[:joins].blank? || include_eager_conditions?(options, tables_from_conditions) || include_eager_order?(options, tables_from_order)
        sql = "SELECT "
        if is_distinct
          sql << connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", order)
        else
          sql << primary_key
        end
        sql << " FROM #{connection.quote_table_name table_name} "

        if is_distinct
          sql << distinct_join_associations.collect(&:association_join).join
          add_joins!(sql, options, scope)
        end

        add_conditions!(sql, options[:conditions], scope)
        add_group!(sql, options[:group], scope)

        if order and is_distinct
          connection.add_order_by_for_association_limiting!(sql, :order => order)
        else
          add_order!(sql, options[:order], scope)
        end

        add_limit!(sql, options, scope)

        return sanitize_sql(sql)
      end
      
    end
  end
end

If you are curious about what exactly was changed, see this diff pastie.

Thursday, July 17, 2008

callback chains and metaclasses

For whatever reason (don't ask) our Rails app needed to be able to define callbacks on objects. As it stands now, you can only define callbacks on classes. Well, metaclasses are classes that pertain to a single specific object, so I figured I could add the callbacks there and everything will work.

Please excuse my extremely contrived example.

p = Post.find(...)
if p.contains_porn?
  class << p
    before_save :filter_content
  end
end
p.save # should trigger filter_content

Well it doesn't work.

After digging through the ActiveSupport source code, I saw that it only looks for callback chains in the object's class, not metaclass. That's not too hard to fix...

module ActiveSupport
  module Callbacks
    def run_callbacks(kind, options = {}, &block)
      callback_chain_method = "#{kind}_callback_chain"
      # Meta class inherits Class so we don't have to merge it in 1.9
      if RUBY_VERSION >= '1.9'
        metaclass.send(callback_chain_method).run(self, options, &block)
      else
        callbacks = self.class.send(callback_chain_method) | metaclass.send(callback_chain_method)
        callbacks.run(self, options, &block)
      end
    end
  end
end

Credit to Josh Peek for the Ruby 1.9 fix

Ok, so that got it working... with one major caveat: you cannot serialize objects that use callbacks. Ruby cannot serialize objects that have metaclasses and the run_callbacks method creates a metaclass whether you use it or not.

That's fine with me, I don't really like serializing objects anyways, but apparently it's a major problem for other people...

http://github.com/rails/rails/commit/e0846c8417093853f4f7f62732983e990c28d669
http://rails.lighthouseapp.com/projects/8994/tickets/575-callbacks-don-t-work-from-extended-modules

Josh Peek suggested a solution where we store the callback chains on the objects themselves, so object specific callbacks would be done like this...

p = Post.find(...)
p.before_save :filter_content if p.contains_porn?
p.save # should trigger filter_content

That seems like a very simple, clean and rational solution. I'll look into coding it up later.

Sunday, May 18, 2008

Metaprogramming Fun in Ruby

My friend, who is just learning Ruby, asked how he can automatically wrap some code around each user defined method in a class. The requirement (from his bosses) is to log when each method is being called.

To be more concrete, we want to factor out lines 3, 5, 9 and 11 from the following snippet.

    1 class AutoLogger
    2   def method_a
    3     puts "start logging method_a"
    4     puts "calling method_a"
    5     puts "end logging method_a"
    6   end
    7   
    8   def method_b
    9     puts "start logging method_b"
   10     puts "calling method_b"
   11     puts "end logging method_b"
   12   end
   13 end

The problem really boils down to two problems. First, how to redefine a method to be the original method body wrapped between the logging code. Second, how do we specify which methods in a class should be wrapped.

Each solution (except for the first one) relies on using Module#define_method to redefine the method in question. Sometimes we pass in a block that defines the original method, other times we make an UnboundMethod out of the original method and call it from the block that makes the new method body, binding it to self which at execution time is an instance of our class.

My friend said ideally, he wants every "user defined" method to automatically be wrapped.

The first solution I came up with was using method_missing. This solution has the drawback of having to refactor all your code to call log_some_method instead of some_method.

    1 class AutoLogger
    2   
    3   def method_missing(name, *args)
    4     name = name.to_s
    5     if name[0,4] == 'log_'
    6       method_name = name[4..-1]
    7       puts "start logging #{method_name}"
    8       self.send(method_name.to_sym, *args)
    9       puts "end logging #{method_name}"
   10     else
   11       super
   12     end
   13   end
   14   
   15   def method_a
   16     puts "calling method_a"
   17   end
   18   
   19   def method_b(arg1, arg2)
   20     puts "calling method_b(#{arg1}, #{arg2})"
   21   end
   22   
   23 end
   24 
   25 o = AutoLogger.new
   26 o.method_a
   27 o.method_b("test", 1)
   28 o.log_method_a
   29 o.log_method_b("test", 1)
   30 
   31 ##########
   32 # OUTPUT #
   33 ##########
   34 # calling method_a
   35 # calling method_b(test, 1)
   36 # start logging method_a
   37 # calling method_a
   38 # end logging method_a
   39 # start logging method_b
   40 # calling method_b(test, 1)
   41 # end logging method_b

With this approach, you can see that the original methods are kept intact and are callable. You have to call the special prefixed version of them to get the logging.

The next solution uses a special method to define methods instead of the def keyword.

    1 class AutoLogger
    2   
    3   def self.def_with_logging(method_name, &block)
    4     define_method(method_name) do |*args|
    5       puts "start logging #{method_name}"
    6       yield *args
    7       puts "end logging #{method_name}"
    8     end
    9   end
   10   
   11   def_with_logging(:method_a) do
   12     puts "calling method_a"
   13   end
   14   
   15   def_with_logging(:method_b) do |arg1, arg2|
   16     puts "calling method_b(#{arg1}, #{arg2})"
   17   end
   18   
   19 end
   20 
   21 o = AutoLogger.new
   22 o.method_a
   23 o.method_b("test", 1)
   24 
   25 ##########
   26 # OUTPUT #
   27 ##########
   28 # start logging method_a
   29 # calling method_a
   30 # end logging method_a
   31 # start logging method_b
   32 # calling method_b(test, 1)
   33 # end logging method_b

Again, this solution requires the programmer to refactor a lot of existing code (rewriting all your methods using def_with_logging instead of def).

The third solution is more "Rails-like" by adding a method to the class itself which can be called to add logging to existing methods.

    1 class AutoLogger
    2   @@inited = false
    3   
    4   def self.do_logging_for(*method_names)
    5     @@method_names ||= []
    6     @@method_names += method_names
    7   end
    8   
    9   do_logging_for :method_a, :method_b
   10   
   11   def initialize
   12     unless @@inited
   13       @@method_names.each do |method_name|
   14         self.class.class_eval do
   15           method = instance_method(method_name)
   16           define_method(method_name) do |*args|
   17             puts "start logging #{method_name}"
   18             method.bind(self).call(*args)
   19             puts "end logging #{method_name}"
   20           end
   21         end
   22       end
   23       @@inited = true
   24     end
   25   end
   26   
   27   def method_a
   28     puts "calling method_a"
   29   end
   30   
   31   def method_b(arg1, arg2)
   32     puts "calling method_b(#{arg1}, #{arg2})"
   33   end
   34   
   35 end
   36 
   37 o = AutoLogger.new
   38 o.method_a
   39 o.method_b("test", 1)
   40 
   41 ##########
   42 # OUTPUT #
   43 ##########
   44 # start logging method_a
   45 # calling method_a
   46 # end logging method_a
   47 # start logging method_b
   48 # calling method_b(test, 1)
   49 # end logging method_b

Notice how most of the work is deferred to initialize. This is so we can call do_logging_for before we define the methods it works on.

The fourth approach takes the least work because it does not require the programmer to do anything special or extra to get the desired effect. It all happens automatically.

    1 class AutoLogger
    2   
    3   def self.method_added(method_name)
    4     
    5     # so we don't get stuck in infinite recursion
    6     @@seen_methods ||= {}
    7     return if @@seen_methods.has_key?(method_name)
    8     @@seen_methods[method_name] = true
    9     
   10     method = instance_method(method_name)
   11     define_method(method_name) do |*args|
   12       puts "start logging #{method_name}"
   13       method.bind(self).call(*args)
   14       puts "end logging #{method_name}"
   15     end
   16   end
   17   
   18   def method_a
   19     puts "calling method_a"
   20   end
   21   
   22   def method_b(arg1, arg2)
   23     puts "calling method_b(#{arg1}, #{arg2})"
   24   end
   25   
   26 end
   27 
   28 o = AutoLogger.new
   29 o.method_a
   30 o.method_b("test", 1)
   31 
   32 ##########
   33 # OUTPUT #
   34 ##########
   35 # start logging method_a
   36 # calling method_a
   37 # end logging method_a
   38 # start logging method_b
   39 # calling method_b(test, 1)
   40 # end logging method_b

Class#method_added is a callback that gets fired, well, when you think it does. The interesting thing to note here is that we can go into infinite recursion because of how we redefinite each method. When a method is a defined the callback is triggered, in the callback we redefine the method which causes the callback to be triggered again.

That's it. Pretty neat what you can do with Ruby, huh?

Monday, March 24, 2008

param_accessible

By popular (cue laughter) demand, I have added whitelisting to the popular (cue hysterical laughter) param_protected plugin. It is done via the param_accessible method.

I have also added support to properly handle array params.

For more details (i.e. documentation), please see the README file.

Wednesday, March 19, 2008

An HTML checkbox that submits when unchecked.

I call this... The Fake Checkbox!   :)

See, the problem is a normal HTML checkbox only submits to the server when it's checked. If it's unchecked, nothing gets sent to the server. Sometimes I want a value (like, umm, false?) sent to the server whether it's checked or unchecked.

So how do we go about making a checkbox that does this? The idea is simple -- make a normal checkbox with Javascript attached to it that updates a hidden input field when checked or unchecked.

Making a Rails helper to do this is simple -- put this in application_helper.rb.

    1 def fake_check_box_tag(name, checked_values, is_checked, html_options = {})
    2   onchange        = html_options[:onchange] || ""
    3   checked_value   = checked_values[:checked] || 'true'
    4   unchecked_value = checked_values[:unchecked] || 'false'
    5   unique_id       = rand.to_s[-5, 5]
    6   poser_id        = "fake_check_box_poser_#{unique_id}"
    7   value_id        = "fake_check_box_value_#{unique_id}"
    8   onchange        = onchange + "; doFakeCheckBoxClick(this, #{unique_id}, '#{checked_value}', '#{unchecked_value}')"
    9   
   10   html_options.delete(:object_id)
   11   html_options[:onchange] = onchange
   12   html_options[:id] = poser_id
   13   
   14   html = ''
   15   html += hidden_field_tag  name, is_checked ? checked_value : unchecked_value, :id => value_id
   16   html += check_box_tag     nil, 'true', is_checked, html_options
   17   html
   18 end

Then you need to define this Javascript function.

    1 function doFakeCheckBoxClick(me, unique_id, checked_value, unchecked_value)  {
    2   unique_id = 'fake_check_box_value_' + unique_id
    3   if (me.checked)
    4     $(unique_id).value = checked_value;
    5   else
    6     $(unique_id).value = unchecked_value;
    7 }

Now you can call it just like almost like the Rail's built in check_box_tag method.

    1 fake_check_box_tag "person[is_female]",
    2                    { :checked   => 'yes',
    3                      :unchecked => 'no' },
    4                    true,
    5                    { :onchange => "alert('checkbox clicked!')" }

In this example, if the checkbox is checked, the following will be true in your action:

params[:person][:is_female] == 'yes'

If it isn't checked, then this will be true:

params[:person[:is_female] == 'no'

The 3rd parameter (true) says that the checkbox will initially be checked.

Monday, January 21, 2008

Rail Plugin: param_protected

It is a Ruby on Rails plugin that provides param_protected and param_accessible methods on controllers analogous to the attr_protected and attr_accessible methods for models.

It is a very simple -- all it does it filter out specified parameters from a request.

Why?


Good question... you can ready about why attr_protected sucks here, or you can just read my following little rant...

What's the goal of attr_protected? To protect us from user input, not from ourselves. When I used attr_protected, I had to refactor tons of code in models, controllers and tests (that already worked well) to not use the mass attribute setters.

Was this massive code refactoring really worth the protection from the very few places were I do something like:

User.new(params[:user])

or

User.update_attributes(params[:user])

Truth of the matter, I was hardly ever passing params (or a subset thereof) to a mass attribute setter. So no, it wasn't worth the massive refactoring job.

Installation


git clone git://github.com/cjbottaro/param_protected.git vendor/plugins/param_protected

Usage


class UsersController < ApplicationController
  param_protected :user_id
end

class AccountController < ApplicationController
  param_accessible :account_id
end

param_protected is used to blacklist and param_accessible is used to whitelist.

You can give it an array of param names to filter:

param_protected [:user_id, :some_other_param]

param_protected and param_accessible are both just before filters, so the usual :only and :except arguments can be used:

param_protected :user_id, :only => :some_action
param_protected :user_id, :only => [:some_action, :another_action]
param_protected :user_id, :except => :some_action
param_protected :user_id, :except => [:some_action, :another_action]

You can protect nested params also (removes params[:user][:id] and params[:user][:password]):

param_protected :user => [:id, :password]

You can get all crazy with that much like how ActiveRecord::Base#find's :include argument works.

param_accessible [:account_name, {:user => [:id, :password, :address => [:city, :state, :zip]]}]

Caveats (IMPORTANT!!!!)


Because param_protected is really a before filter (uses prepend_before_filter), you must take special care to ensure that it runs before any of your other before filters!! If it is not, some of your before filters might have access to some params they shouldn't.

Tests


rake test should work (from the plugin's root dir), though it's far from comprehensive.

Documentation


Please see the README for usage instructions and examples.