CategoryRuby + Rails

How to automatically build a hash

Sorry if the title misled you, but I couldn’t come up with anything better and equally short. A YAML configuration file can be loaded into a hash, and this works pretty much out of the box. Sometimes, however, I need to also add configuration on the fly to the same hash. Then it becomes a little tedious to manually build missing keys, so I created my own Ando::Hash, which is a thin wrapper around Ruby’s Hash.

module Ando

  class Hash

    def initialize(hash=nil)
      hash ||= {}
      raise ArgumentError, "Expected a Hash object. (#{hash.class})" unless hash.respond_to?(:has_key?)
      @hash = hash
    end

    def [](*args)
      get_last_value(args)
    end

    def []=(*args)
      value = args.pop
      if args.first
        set_last_value(args, value)
      else
        initialize(value)
      end
      value # this is to preserve assignment semantics
    end

    protected

    # hook into @hash
    def get_previous_hash(path)
      current = @hash
      path[0..-2].each do |key|
        current = current[key] || current[key.is_a?(Symbol) ? key.to_s : key.to_sym]
      end
      current
    end

    # initialize intermediate keys to a hash
    def init_last(path, current = @hash)
      key = path.first
      return unless key
      raise ArgumentError, "Expected a Hash object. (#{current.class})" unless current.respond_to?(:has_key?)
      (current[key] = {}) unless (current.has_key?(key) && current[key].respond_to?(:has_key?))
      init_last(path[1..-1], current[key])
    end

    # set the given value to the last key
    def set_last_value(path, value)
      init_last(path)
      previous            = get_previous_hash(path)
      previous[path.last] = value
    end

    # get the value of the last key
    def get_last_value(path)
      previous = get_previous_hash(path)
      if previous.respond_to?(:has_key?) && path.last
        key = path.last
        return previous[key] || previous[key.is_a?(Symbol) ? key.to_s : key.to_sym]
      end
      previous
    end

  end

end

And here is a very short usage example.

>> h = Ando::Hash.new({:a => 1, :b => {:c => 3}})
=> #<Ando::Hash:0x007fd96a87d1b0 @hash={:a=>1, :b=>{:c=>3}}>
>> h[:b]
=> {:c=>3}
>> h[:b, :c]
=> 3
>> h[:a, :b, :c, :d, :e] = 5
=> 5
>> h[:a, :b, :c, :d, :e]
=> 5
>> h[:b]
=> {:c=>3}
>> h[:a]
=> {:b=>{:c=>{:d=>{:e=>5}}}}
>> h
=> #<Ando::Hash:0x007fd96a87d1b0 @hash={:a=>{:b=>{:c=>{:d=>{:e=>5}}}}, :b=>{:c=>3}}>

How to run Rake tasks programmatically

Some time ago I was told to reimplement some Rake tasks such that they would be available from a simple admin interface. Instead of that, I came up with a web interface for Rake tasks, such that exactly the same code can be run from the web and from a console.

At the core there are a couple of global methods: runnable_tasks and run_task.

#see http://stackoverflow.com/questions/4459330/how-do-i-temporarily-redirect-stderr-in-ruby -----------------------
require "stringio"

def capture_stderr
  previous, $stderr = $stderr, StringIO.new
  yield
  $stderr.string
ensure
  $stderr = previous
end

def capture_stdout
  previous, $stdout = $stdout, StringIO.new
  yield
  $stdout.string
ensure
  $stdout = previous
end
#-------------------------------------------------------------------------------------------------------------------

# requires rake such that descriptions are collected too
def require_rake
  return if defined? Rake
  require 'rake'
  Rake::TaskManager.record_task_metadata = true
  require 'rake/testtask'
  require 'rdoc/task'
  require 'tasks/rails'
end

# returns the stdout generated by an execution of the given task
def run_task(task_name)
  require_rake
  capture_stdout { Rake.application[task_name].invoke } # or Rake::Task[task_name].invoke
end

# returns a hash (each key is a task name and each value is a task description)
def runnable_tasks(include_tasks = /.^/, exclude_tasks = /.^/) # include (and exclude) nothing for safety
  require_rake
  list = []
  tasks = Rake.application.tasks
  tasks.each do |task|
    if task.name.match(include_tasks) && ! task.name.match(exclude_tasks)
      list.push(task.name)
    end
  end
  list.sort
  result = {}
  list.each do |item|
    result[item] = Rake::Task[item].comment
  end
  result
end

And here is how that it’s supposed to be used:

result = run_task(task_name) if runnable_tasks.has_key?(task_name)

URI encoding and decoding for Ruby

These are some global methods for dealing with URI encoding and decoding in Ruby, such that it behaves exactly like JavaScript.

      # printable ASCII chars (between 32 and 126) without 0..9, A..Z, a..z
      def symbols
        ' !"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' # symbols.length === 33
      end


      def gsub(input, replace)
        search = Regexp.new(replace.keys.map{|x| "(?:#{Regexp.quote(x)})"}.join('|'))
        input.gsub(search, replace)
      end

      # same as the JavaScript encodeURI
      def encodeURI(value)
        # encodeURI(symbols)  === "%20   ! %22   #   $ %25   &   '   (   )   *   +   , - .   /   :   ; %3C   = %3E   ?   @ %5B %5C %5D %5E _ %60 %7B %7C %7D   ~"
        # CGI.escape(symbols) === "  + %21 %22 %23 %24 %25 %26 %27 %28 %29 %2A %2B %2C - . %2F %3A %3B %3C %3D %3E %3F %40 %5B %5C %5D %5E _ %60 %7B %7C %7D %7E"
        gsub(CGI.escape(value.to_s),
            '+'   => '%20',  '%21' => '!',  '%23' => '#',  '%24' => '$',  '%26' => '&',  '%27' => "'",
            '%28' => '(',    '%29' => ')',  '%2A' => '*',  '%2B' => '+',  '%2C' => ',',  '%2F' => '/',
            '%3A' => ':',    '%3B' => ';',  '%3D' => '=',  '%3F' => '?',  '%40' => '@',  '%7E' => '~'
        )
      end

      # same as the JavaScript encodeURIComponent, also for UTF8 multibyte chars (including gclef)
      def encodeURIComponent(value)
        # encodeURIComponent(symbols) === "%20   ! %22 %23 %24 %25 %26   '   (   )   * %2B %2C - . %2F %3A %3B %3C %3D %3E %3F %40 %5B %5C %5D %5E _ %60 %7B %7C %7D   ~"
        # CGI.escape(symbols)         === "  + %21 %22 %23 %24 %25 %26 %27 %28 %29 %2A %2B %2C - . %2F %3A %3B %3C %3D %3E %3F %40 %5B %5C %5D %5E _ %60 %7B %7C %7D %7E"
        gsub(CGI.escape(value.to_s),
            '+'   => '%20',  '%21' => '!',  '%27' => "'",  '%28' => '(',  '%29' => ')',  '%2A' => '*',
            '%7E' => '~'
        )
      end

      # same as the JavaScript decodeURI
      def decodeURI(value)
        # decodeURI(encodeURI(symbols))     === symbols
        # CGI.unescape(CGI.escape(symbols)) === symbols
        CGI.unescape(gsub(value.to_s,
            '%20' => '+',    '!' => '%21',  '#' => '%23',  '$' => '%24',  '&' => '%26',  "'" => '%27',
            '('   => '%28',  ')' => '%29',  '*' => '%2A',  '+' => '%2B',  ',' => '%2C',  '/' => '%2F',
            ':'   => '%3A',  ';' => '%3B',  '=' => '%3D',  '?' => '%3F',  '@' => '%40',  '~' => '%7E'
        ))
      end

      # same as the JavaScript decodeURIComponent
      def decodeURIComponent(value)
        # decodeURIComponent(encodeURIComponent(symbols)) === symbols
        # CGI.unescape(CGI.escape(symbols))               === symbols
        CGI.unescape(gsub(value.to_s,
            '%20' => '+',    '!' => '%21',  "'" => '%27',  '(' => '%28',  ')' => '%29',  '*' => '%2A',
            '~'   => '%7E'
        ))
      end

More Generic Scopes

This is a followup to my previous post How to share code among ActiveRecord models. In this installment I’m going to show some additional generic scopes that I found useful for building up queries. In particular, some of these scopes are twice as generic, because they are defined by wrappers, so that their real name is specific enough to be meaningful.

Field Scope

This scope sets a field to some value. Think about a state field. Instead of writing

where(:state, :accepted)

you can write

with_state(:accepted)

which is a bit easier to read.

Here it is.

    # field_scope :state
    #   --> named_scope :with_state, ...
    def field_scope(field, cast = :to_s)
      named_scope :"with_#{field}", lambda { |*args|
        where(field => (args.size == 1 ? args.first.send(cast) : args.map(&cast)))
      }
    end

Example:

class Order
  field_scope :state
  named_scope :not_paid, with_state(:to_be_paid, :paying)
end

Order.not_paid.created_before(Date.yesterday).delete

What I like about this generic scope is that a common word like state becomes a specific term that I can later easily find with a simple and fast text search. So, instead of searching for state, whose matches could include a lot of false positives, I can search for with_state which is specific enough to only match what I’m really looking for. Anyway this is a dull surrogate of what future IDEs (able to understand semantics) will allow, like search for specific usages of common words.

Belonging-To Scope

This scope works analogously to the previous one, but for belongs_to relations. Think about a user_id field. Instead of writing

where(:user_id => tom.id)

you can write

belonging_to(tom)

which is a bit easier to read.

It’s almost always possible to guess the name of the field from the class of the object a model is supposed to belong to. If that’s not the case, I’ve provided an as option that accepts the name of the field (without the _id suffix).

      model.named_scope :belonging_to, lambda { |obj, options = {}|
        options = {
            :as => (obj.nil? || obj.is_a?(Integer)) ? 'user' : obj.class.to_s.underscore
        }.merge(options || {})
        where(:"#{options[:as]}_id" => (obj.respond_to?(:id) ? obj.id : obj))
      }

Example:

class Document
  belongs_to :user
  belongs_to :editor, :class_name => 'User'
end

owned_by_ann = Document.belonging_to(User.with_name('Ann'))
edited_by_me = Document.belonging_to(current_user, :as => :editor)

Polymorphic Scope

This scope works analogously to the previous one, but for belongs_to relations that are also polymorphic. In this case, I decided to retain simplicity by means of the with_ prefix. Think about a liked field that has been defined like this

belongs_to :liked, :polymorphic => true

Then it’s a bit tedious (and rude) to explicitly refer to liked_table and liked_id in queries, so this is how to elegantly magic them away.

    def sti_member?
      column_names.include?(inheritance_column)
    end

    def sti_root?
      return nil unless sti_member?
      superclass == ActiveRecord::Base
    end

    def sti_root
      return nil unless sti_member?
      klass = self
      while ! klass.sti_root?
        klass = klass.superclass
      end
      klass
    end

    # polymorphic_scope :favorite
    #   --> named_scope :with_favorite, ...
    def polymorphic_scope(field)
      named_scope :"with_#{field}", lambda { |obj|
        if obj.is_a?(Class)
          where(:"#{field}_type" => obj.to_s)
        else
          where(:"#{field}_type" => (obj.class.sti_root || obj.class).to_s, :"#{field}_id" => obj.id)
        end
      }
    end

All the sti_* methods account for Single Table Inheritance, which is a little tricky to deal with because of how it works behind the scenes.

Example:

class Like
  belongs_to :liked, :polymorphic => true
  polymorphic_scope :liked
end

class User
  has_many :likes
end

favorite_singers = current_user.likes.with_liked(Artist)
favorite_people = current_user.likes.with_liked(User)
favorite_songs = current_user.likes.with_liked(Song)

liked_lady_gaga = current_user.likes.with_liked(Singer.with_name('Lady_Gaga'))
puts "You like Lady Gaga since #{liked_lady_gaga.created_at}" unless liked_lady_gaga.nil?

Note how natural it all becomes, working equally well for classes (also for STI classes, like Artist < User) and objects (like Lady Gaga).

How to share code among ActiveRecord models

My last project was migrating a Rails 2.3 app to Rails 3.2. At some point I was updating tens of definitions of named scopes, a pretty easy and tedious task.

When I see many similar code snippets, generalizations popup in my mind. That never fails, and this time I found many named scopes that could be parametrized in some way or another. So I did it and started replacing their occurrences with calls to my generic scopes.

Later on, when it came time to test wether my changes worked or not… (f*ck) I found out there was a pretty good reason why a bunch of them were the way they were, i.e. not parametrized.

The first generic scope: or_where

To explain the issue I need to take a little detour. One scope I could not live without anymore was the where with an OR, i.e. the where that makes the OR of its arguments. It’s very strange that Rails implements only the where with an AND. I mean, anyone knows that AND and OR go hand in hand…

module GenericScopes

  module ClassMethods

    def merge_conditions_with_or(*conditions)
      segments = []
      conditions.each do |condition|
        unless condition.blank?
          sql = sanitize_sql(condition)
          segments << sql unless sql.blank?
        end
      end
      "(#{segments.join(') or (')})" unless segments.empty?
    end

  end



  def self.included(model)

    model.extend ClassMethods

    model.class_eval do

      model.named_scope :or_where, lambda { |*conditions|
        split_conditions = []
        conditions.each { |condition|
          if condition.is_a?(Hash)
            condition.each { |key, value|
              split_conditions << {key => value}
            }
          else
            split_conditions << condition
          end
        }
        sql = merge_conditions_with_or(*split_conditions)
        where(sql)
      }

    end

  end

end

About the GenericScopes module, the interesting thing to note is that it will work without modifications with the final solution I’m going to describe here. In fact, its code could be buggy (I didn’t even try to make it work as nicely as the default where) but it works pretty fine for my own needs. (See an example at the end of this post.)

The problem with the default where is that the AND of the arguments is hardcoded into the merge_conditions method. Instead of changing anything into the ActiveRecord code, I opted for copy-and-pasting that method and replacing the AND with an OR. I use lower case SQL so that when I inspect queries I can easily understand if a piece of query was generated by me or by Rails.

The issue I’m trying to explain pops up with the call to sanitize_sql from merge_conditions_with_or. If the method was public it could work directly like this

User.sanitize_sql(:full_name => 'John Doe')
  # => ["`users`.`full_name` = 'John Doe'"]

but, if an exception wasn’t raised, it would be like this

User.or_where(:full_name => 'John Doe') 
  # internally, the sanitize_sql would return 
  # => ["``.`full_name` = 'John Doe'"]

In fact, when the call is applied to User, and when it takes place inside of merge_conditions_with_or (even if called from or_where which is in turn applied to User), the values of self are completely different. While in the former case it’s User (concrete), in the latter self is ActiveRecord::Base (abstract).

Problem

The problem was the way generic scopes were made available to models.

ActiveRecord::Base.send(:include, GenericScopes)

The inclusion of GenericScopes into ActiveRecord::Base was the cause of all troubles with sanitize_sql. It had been working fine for years in production, but for my new scopes I needed to move the inclusion of the module from the abstract ActiveRecord::Base to each and every concrete model, like this:

class User
  include GenericScopes
  #...
end

class Post
  include GenericScopes
  #...
end

#...

Solution

That is quite boring and you must remember to include GenericScopes in all models. Luckily Ruby provides the inherited method. The following code is then a pretty good way to share code among ActiveRecord models. The nice things are that (1) it follows the DRY principle, (2) it is completely automatic, (3) shared code is properly bound to the inheriting class.

module ActiveRecord
  class Base
    class << self
      def inherited_with_generic_scopes(model)
        inherited_without_generic_scopes(model)
        model.send(:include, GenericScopes)
      end
      alias_method_chain :inherited, :generic_scopes
    end
  end
end

being or being_not, that is the question

It’s very common to have many boolean columns in the same table, so it’s useful to have a generic scope for them. Using where and or_where, these are all the combinations:

model.named_scope :being,        lambda { |*columns|    where(columns.map{|x| {:"#{x}" => true}}.reduce(&:merge)) }
model.named_scope :or_being,     lambda { |*columns| or_where(columns.map{|x| {:"#{x}" => true}}.reduce(&:merge)) }
model.named_scope :being_not,    lambda { |*columns|    where(columns.map{|x| {:"#{x}" => false}}.reduce(&:merge)) }
model.named_scope :or_being_not, lambda { |*columns| or_where(columns.map{|x| {:"#{x}" => false}}.reduce(&:merge)) }

And here is an example about how to use them:

Song.or_being('reggae', 'ska', 'ska_punk')

 

© 2017 Notes Log

Theme by Anders NorenUp ↑