Monday, December 2, 2013

Rails, Ajax Requests, and Playing Nicely with respond_to do |format|

So this morning I'm working on making a highly interactive dialog for +Rentables. The dialog is initially loaded via ajax using the very familiar respond_to block where render_ajax_dialog is a helper specific to my project.
respond_to do |format|
  format.html {}
  format.js { render_ajax_dialog }
end
Now when the user clicks certain buttons on the dialog, I want to refresh the dialog from the server (i.e. Ajax) without reloading the underlying page and without the updating controller having to know anything about the dialog - basically the blind interacting with the blind.
Pop quiz: How do the blind interact in the stateless hyper text transfer protocol world?
My solution is to pass a parameter to the updating controller (:redisplay) with the URL to the action which renders the dialog. It seems a bit messy, right? Maybe someone reading this has a better idea.
class DialogLoadingController < ApplicationController
  def show
    # load some foo

    respond_to do |format|
      format.html
      format.js { render_ajax_dialog }
    end
  end
end


class UpdatingThingsController < ApplicationController
  def update
    # update some foo

    respond_to do |format|
      format.html { redirect_to pop_return_to_or(foo_path) }
      format.js { 
        if (redisplay = params[:redisplay])
          show_dialog_url = ensure_format_js(redisplay)
          render :js => "$.ajax({url: '#{show_dialog_url}', cache: false});"
        end
      }
    end
  end


  private

  # In order for +respond_to+ to pick JS we need to ensure the URL
  # includes '.js'. The first call will not but subsequent calls will.
  #   ensure_format_js('/path/to/foo?are_you_awesome=true')
  #   # => /path/to/foo.js?are_you_awesome=true
  #
  #   ensure_format_js('/path/to/foo.js?are_you_awesome=true')
  #   # => /path/to/foo.js?are_you_awesome=true
  #
  # @param [String] url the redisplay URL
  # @return [String] Url with format .js specified
  def ensure_format_js(url)
    return nil unless url

    # split off query string, if any
    url = url.split '?'

    # check if JS specified
    if url.first.match(/.*\.js$/)
      # JS specified, re-join URL and query
      url.join('?')
    else
      # add JS and re-join
      url.first + '.js?' + url.last
    end
  end
end

Wednesday, November 20, 2013

Using Ruby to Delete Unused Files in a Rails Project

I have amassed a rather large set of icons for a RoR project I'm working. Initially I just checked all these files into the repository and everything was great. Gradually the list of icons (most of them not used) got longer and longer. Now I'm to the point where running rake assets:precompile in my production environment takes forever. It's time to defriend all those unused icons.

The first step is to get a list of all the icons I actually use. Lucky for me, every icon is conveniently defined in a CSS file.

def referenced_icons(subdir)
  path_to_css = File.join(Dir.pwd, 'app/assets/stylesheets/all/icons.css.scss')

  # Open the CSS file and scan for lines referencing PNG 
  # files and get just the filename
  used_icons = File.open(path_to_css)  do |f| 
    f.grep(/png/).map{|x| x[Regexp.new("(?<=#{subdir}\/).*.png")]}
  end

  # Beat the list into a smaller package
  used_icons.compact.uniq.sort
end

Great! I can get a list of all the PNG files I actually use. Now who am I going to defriend.

def not_my_friends(subdir)
  used_icons = referenced_icons(subdir)
  path_to_pngs = File.join(Dir.pwd, "app/assets/images/icons/#{subdir}/*.png")
  
  # Reject files not in the used_icons list
  Dir.glob(path_to_pngs).reject do |f|
    b = Pathname(f).basename.to_s
    used_icons.include?(b)
  end
end

And now for the defriending:

not_my_friends('some_dir').each{|x| File.delete(x)}

It feels good to lighten the load (by about 2400 unused icons!). Now back to work!