Archive for the ‘Ruby on Rails’ Category

Loosely defined link_to may cause problems when overriding url_helper

Tuesday, August 26th, 2008

OR “Beware of pick pockets and loose link_to’s”

I hope most rails dev’s out there take advantage of RESTful routes and the url-generating helpers so readily available these days and actually enjoy them. But for some of you who are lucky enough to inherit a legacy app (you know, 1 year olds) or for whatever reason encounter loosely defined link_to’s [exemplified below], you can easily update the code to avoid some gotchas.

This is what I consider a loosely defined link_to:

link_to 'Destroy', some_record, :confirm => ‘Are you sure?’, :method => :delete

It lacks parentheses, option grouping, and most importantly, expects the helpers to generate a specific kind of link (in this case a destroy action) from just the record itself. Now, rails is pretty smart these days and will probably generate the correct link without a hiccup if your app isn’t employing a custom RAILS_RELATIVE_URL_ROOT, or setting skip_relative_url_root => false.

Well, my latest app is. And this is how that loosely defined link_to renders:

ArgumentError in Controller#action
wrong number of arguments (0 for 1)

The stack trace will provide evidence that some thing’s a mess in url_for related to the default_url_options, defined in application.rb (or elsewhere). Rails’ helpers cannot correctly produce a url due to lazy linking.

So, make your code more readable and keep the helpers happy and define your link_to’s like this:

link_to ( 'Destroy', record_path(some_record), :confirm => ‘Are you sure?’, :method => :delete )

Refresh that error page and voila! Happy links again! Stricter coders may even group the options with curlies, and that doesn’t hurt either.

Introducing environmentalist: an intuitive, environment-focused config structure for your rails applications

Monday, August 4th, 2008

A couple of months ago, I wrote an article (Better setup for environments in rails) discussing the standard set of changes we make to the config structure of each of our rails apps.

The primary motivation for me to make these changes stemmed from the need to have several deployable environments. The standard set of rails environments (development, test, production) simply just don’t cut it for me. It’s important for us to be able to deploy to staging, demo and even production-test environments. When including server configurations (e.g. a Passenger config snippet, or a mongrel_cluster config snippet), I’ve often had to use unique configurations for each deployable environment. Consequently, my config/ directory quickly became polluted with files such as: apache_prod.conf, apache_staging.conf, apache_demo.conf. Furthermore, it also requires special care when deployment comes around.

(more…)

Merging Adobe PDF’s and generating a table of contents on the fly using ruby

Monday, July 21st, 2008

So let’s say you have some random PDFs and what you want is one PDF that includes all of the original PDF files and a table of contents listing all of the files and the proper page numbers. Well in Ruby it is not too hard to put this together. There are a wealth of plugins, gems, and other ruby software available for manipulating and creating PDFs (a thorough list can be found here - http://wiki.rubyonrails.org/rails/pages/HowtoGeneratePDFs). To get this project up and running we are going to use two PDF::Writer (http://rubyforge.org/projects/ruby-pdf/) and PDFTK (http://www.accesspdf.com/pdftk/) - though if you want to get fancier and also include text, html, or xml documents you can use PDF::Htmldoc (http://htmldoc.rubyforge.org/) which requires Htmldoc to be installed. Before I do get started though, I also have give thanks to George Anderson over at Benevolent Code who wrote a lot of similar code on the project which provided me with some great examples.

(more…)

Advanced Model Based searches in rails

Monday, July 21st, 2008

After watching a railscast episode on advanced searching I thought I would give it a try. So I came up with a slightly modified version that would handle my search.

Model

class ExportSearch
 
  def timecards
    find_cards
  end
 
  def users(u)
    @u = u
  end
 
  def projects(p)
    @p = p
  end
 
  def tasks(t)
    @t = t
  end
 
  def dates(date1, date2)
    @d1 = date1
    @d2 = date2
  end
 
  def clients(c)
    @c = c
  end
 
private
 
  def find_cards
    TimeCard.find(:all, :conditions => conditions, :include => {:task => :project}, :order => :date)
  end
 
  def projects_conditions
    ["tasks.project_id IN (?)", @p] unless @p.blank?
  end
 
  def client_conditions
    ["projects.client_id IN (?)", @c] unless @c.blank?
  end
 
  def date_conditions
    ["date BETWEEN ? AND ?", @d1, @d2] unless (@d1.blank? || @d2.blank?)
  end
 
  def task_conditions
    ["task_id IN (?)", @t] unless @t.blank?
  end
 
  def users_conditions
    ["user_id IN (?)", @u] unless @u.blank?
  end
 
  def conditions
    [conditions_clauses.join(' AND '), *conditions_options]
  end
 
  def conditions_clauses
    conditions_parts.map { |condition| condition.first }
  end
 
  def conditions_options
    conditions_parts.map { |condition| condition[1..-1] }.flatten
  end
 
  def conditions_parts
    private_methods(false).grep(/_conditions$/).map { |m| send(m) }.compact
  end
end

Controller

    search = ExportSearch.new
    search.users(params[:export][:users].join(',')) unless params[:export][:users].blank?
    search.tasks(params[:export][:tasks].join(',')) unless params[:export][:tasks].blank?
    search.projects(params[:export][:projects].join(',')) unless params[:export][:projects].blank?
    search.dates(start_date, end_date)
 
    @time_cards = search.timecards

Problems with restful_authentication Plugin and Internet Explorer Cookies

Friday, July 11th, 2008

I just ran into a fairly obscure bug. Bit me pretty good and stole an hour from me on an otherwise quiet Friday afternoon.

How the Problem Manifested Itself: Using restful_authentication, I could log in fine using Firefox and Opera, but not Internet Explorer or Safari. I figured, it’s just an HTML POST, nothing special, so what could be going wrong? I started to tail my logfile, and the session#create action was working properly. It was redirecting to a protected page, signifying that the login was successful. However, there was a second redirect occurring immediately after, sending me back to the login page. Here’s a tail of the logfile:

(more…)

Merging a :has_many relationship into one instance

Thursday, July 10th, 2008

So the problem is that I have an ActiveRecord model that has a :has_many relationship to another model (we’ll call this one object), but when I am in the view context I didn’t want to have to loop through the object each time to determine which data was being displayed. Object has many attributes (approximately 30) and many are often null for a given instance. So I decided to add a method to my model to loop through all of objects and determine which data should be included. Pretty much the rule was that if there was no data for a particular attribute temporarily save it to a copy of the object and then return that. This is what I came up with.

  def object
    tmp = objects.first
    objects.each {|o| tmp.attributes.each {|key, value| tmp[key] = o[key] if value.blank? && key != 'id'}}
    tmp.freeze
  end

However there was a flaw here. Every time I would view the page all of the data in the objects was getting overwritten with one copy of it. After banging my head on the desk it was realized that tmp[key] = o[key] was actually writing the changes to the database permanently rewriting all of the objects (which still seems counter intuitive to me, because it seems like only the first record should have been the one changing). But the solution was pretty simple. The working method is as follows.

  def object
    tmp = Object.new
    objects.each {|o| tmp.attributes.each {|key, value| tmp[key] = o[key] if value.blank? && key != 'id'}}
    tmp.freeze
  end

Reading and replacing text in Word DocX and Excel XlsX documents using Ruby

Wednesday, July 9th, 2008

So as you may know. The new Word and Excel formats are similar to open office document formats in that they are just zips of multiple xml documents (well mostly xml documents). So what we wanted to do for our project (the WebDav one mentioned in my last post) is to set up a simple templating system that would do variable replacement in Word/Excel documents. And it turned out to be a piece of cake. I am just going to go through the DocX version of template model, but the only difference between them is the folder structure so there is not too much to change to get this working for both.

(more…)

Microsoft WebDav opens document as Read-Only when using RailsDav

Tuesday, July 1st, 2008

I had been working on a project in which we wanted to utilize WebDAV (namely for editing Word & Excel Documents that were saved in our application). In order to do this we decided to use a plugin from liverail.net that can be found here. It was pretty easy to hook up after a little direction from a guy over at Benryan Inc [apologies I cannot find a link for them], but there was a major issue. When opening a document through the ActiveX controller for editing it was opening in Read-Only mode.

After a few starts and stops, many hours of reading through the webdav documentation, and browsing through the http traffic using Fiddler - it was determined that locking was the issue.

(more…)

Rails 2.1 broke my mysql foreign keys!

Tuesday, June 24th, 2008

Rails 2.1 introduced in the MySQL Adapter “smart integer columns.” The idea was to use the :limit option to determine whether a smallint, int, or bigint should be used. This is something that the Postgres adapter had already previously implemented. The relevant code in activerecord/lib/active_record/connection_adapters/mysql_adapter.rb is:

  # Maps logical Rails types to MySQL-specific data types.
  def type_to_sql(type, limit = nil, precision = nil, scale = nil)
    return super unless type.to_s == 'integer'

    case limit
    when 0..3
      "smallint(#{limit})"
    when 4..8
      "int(#{limit})"
    when 9..20
      "bigint(#{limit})"
    else
      'int(11)'
    end
  end

Mirko Froehlich suggests monkey patching this function. Timothy Jones blogged about it.

To monkey-patch this, just drop a file (fix_mysql_adapter.rb) into your initializers/ directory, as such:

(more…)

Don’t Abuse the Session

Monday, June 23rd, 2008

Never, ever, ever, ever, ever store an ActiveRecord model in the session. Just store the id and load it into an instance variable from the database on every request. Why? A couple reasons…

First, you’re susceptible to staleness. Consider this. User A logs in, and you store their user object in the session. Administrator X logs in and deactivates User A’s account. User A can still muck around your site because you’re reading the user data from the session, which has stale data.

Second, the default in Rails these days is to store your session data in cookies (honestly, I don’t know why…..it only clutters up your requests, forcing the session to be passed back and forth on _every_ request, and opening up the possibility that the encryption key could be brute-forced……this is a rant for another day). You just don’t want to be storing whole ActiveRecord objects in the session. They’re big and clunky. The extra database call to reload the object in a before_filter on every request is practically trivial, and you’ll keep the “tubes” less clogged.

This practice is certainly not rails-specific, and should be adopted no matter the server-side technology.