JasonDaly.name

PHP, Ruby, Symfony, Rails, Doctrine, MooTools. Web Development.

Posts tagged with "tips"

October 12, 2011

Decorating Models from the View Layer with Draper

Draper is a great gem allowing for the Decorator pattern to easily be applied to ActiveRecord models.

When integrating Draper into an existing application, a decorator or collection of decorators must be retrieved/built instead of ActiveRecord models in order to use the functionality of your decorators (the decorators include all functionality of the ActiveRecord model they’re decorating). Draper’s README explains three methods for doing this from within the controller:

  • Call .new and pass in the object to be wrapped

    ArticleDecorator.new(Article.find(params[:id]))
    
  • Call .decorate and pass in an object or collection of objects to be wrapped:

    ArticleDecorator.decorate(Article.first) # Returns one instance of ArticleDecorator
    ArticleDecorator.decorate(Article.all)   # Returns an array of ArticleDecorator instances
    
  • Call .find to do automatically do a lookup on the decorates class:

    ArticleDecorator.find(1)
    

I’d like to decorate ActiveRecord models from the view directly, leaving the controllers unchanged (directly fetching the record(s) through ActiveRecord). I use the following decorate method in app/helpers/application_helper.rb.

def decorate(records)
  collection = [records] if !objects.is_a? Array
  return records unless collection.first.class.name.match(/Decorator$/).nil?
  klass = (collection.first.class.name + 'Decorator').constantize

  decorated_objects = []
  collection.each do |object|
    decorated_records.push(klass.decorate(object))
  end

  return decorated_records if records.is_a? Array
  decorated_records.first
end

Now I can simply wrap any record or collection with decorate in any view to leverage the decorator functionality.

12 notes Tags: ruby draper gem code rails ruby on rails tips decorator helpers

May 19, 2011

Using intern to Reference Routes Dynamically

In config/routes.rb if routes such as the following exist:

match "users/customers" => "users#customers", :as => :users_customers, :via => :get
match "users/employees" => "users#employees", :as => :users_employees, :via => :get 

match "users/:id/toggle-status" => "users#toggle_status", :as => :users_toggle_status, :via => :put

the views associated with the :users_customers and :users_employees actions above (in this case each displaying a list-view of users of the specified type) likely both have a link referencing the :users_toggle_status route.

Within the toggle_status action the route to redirect back to can be determined by looking at the type of user who’s status is being toggled. A sample version of this action is below.

def toggle_status
    user = User.find(params[:id])
    user.toggle_status!

    if user.type == 'employee'
      redirect_to :users_employees and return
    end

    redirect_to :users_customers and return
end

The condition above and multiple redirect_to’s is cumbersome and unnecessary. Instead, we can use Ruby’s intern method for strings.

def toggle_status
    user = User.find(params[:id])
    user.toggle_status!

    redirect_to ('users_%s' % user.type).intern and return
end

11 notes Tags: ruby rails ruby on rails RoR intern string tips code

April 26, 2011

Reverse Pagination Count with Kaminari

When displaying a threaded conversation with messages paginated by Kaminari, I had the unique requirement to display messages in DESC order by their creation date. This meant that if there were 12 messages with 5 per page, the breakdown would be

  • Page 1: messages 12 - 8
  • Page 2: messages 7 - 3
  • Page 3: messages 2 - 1

To actually display the message number next to each message, it had to be calculated using the methods added to a paginated model by Kaminari (see the :message_number value below)

<div class="comments">
  <% @messages.each_with_index do |message, index| %>
    <%= render :partial => 'message', :locals => {:message => message, :message_number => @messages.total_count - (@messages.current_page - 1) * @messages.limit_value - index} %>
  <% end %>
</div>

For now this is the only place the above calculation is relevant. Should I need it in multiple places I would move the above calculation to a helper.

3 notes Tags: rails ruby on rails RoR code tips ruby kaminari

April 25, 2011

Parsing and Pluralizing Model Names in Rails

Recently working with Kaminari, I wanted to have text display next to the pagination links like

283 Items - Page 3/15

I wanted the Items text to be dynamic according to the model the paginated collection was representing. Looking over my model names I found converting each to a human readable version would work great in all cases I needed a paginator. Simply using pluralize (I use singular form for my models) will not work in all cases.

  • User would become Users - great!
  • Page would become Pages - great!
  • ConversationMessage would become ConversationMessages - yuck!

For the ConversationMessage model, I really only wanted to display the items in the collection to the user as Messages, not ConversationMessages or even Conversation Messages. I found this was common for my other models on the many side of a one-to-many relationship too (ie. for my UserComplaint model, I wanted the pagination text to say simply Complaints).

The following works beautifully for all models I currently have in place

@collection.first.class.name.underscore.humanize.pluralize.titleize[/\s?([^\s]+)$/, 1]

With models having their default names (in the plural form), the .pluralize call can be removed.

4 notes Tags: ruby ruby on rails rails RoR code tips Kaminari pagination paginator

August 24, 2010

Amazon S3 + Nightly Database Backups

Amazon S3 is a cost-effective, redundant, and straight-forward solution for off-site data backups. Using the highly recommended aws command line tool by Tim Kay and a simple shell script running nightly, creating an automatic remote database backup is easy.

After installing aws, I created a shell script as shown below.

#!/bin/bash
export AWS_ACCESS_KEY_ID= # your aws access key id
export AWS_SECRET_ACCESS_KEY= # your secret access key

DBUSER= # the database username
DBPASS= # the database password
DBNAME= # the name of the database
BUCKET= # the root bucket to store the backup

FILE=$DBNAME.bkp.$(date +_%b_%d_%y.%s).sql.gz
mysqldump -u $DBUSER -p$DBPASS $DBNAME | gzip -9 > /tmp/$FILE
aws put $BUCKET/bkp/sql/$FILE /tmp/$FILE --private
rm -f /tmp/$FILE # cleanup

A nightly (at 1AM) cron is then setup for the user running the above script

0 1 * * * /home/app/s3dbbkp 2>&1

This is clearly a very simple implementation, but for my current needs (and likely others) it is perfect.

Tags: shell bash cli aws amazon s3 database backup code tips

August 21, 2010

Testing for Input Focus

Though part of the HTML5 Specification, the activeElement attribute is now supported by all major browsers and offers the ability to determine whether an element currently has the user’s focus very easily.

Previously making the determination for which input element currently had focus was done using multiple event handlers applied to all input elements. (Note: The selector below does not include form inputs not based on the <input> tag such as a <select> list)

var current_focus; // Maintains a reference to the currently-focused input
$(document.body).getElements('input').each(function(f){
    $(f).addEvents({
        'focus': function(){
            current_focus = $(this);
        },
        'blur': function(){
            current_focus = false;
        }
    });
});

//... and later

if (current_focus == $('element-in-question')) {
    // The element in question currently has the user's focus
}

Now that most major browsers support activeElement, the test for an input having focus can be done without any event handlers.

if ($(document.activeElement) == $('element-in-question')) {
    // The element in question currently has the user's focus
}

Ideally your application’s code will include the old version as a fallback along with the above solution to accommodate all browsers.

Tags: Mootools javascript tips code mdc

August 17, 2010

Caching Static Assets with Cache-Busting Support in Symfony 1.4

(Note: This technique can be applied anywhere. This article is specifically related to a Symfony 1.4 implementation)

As recommended in the Google Page Speed best practices, static resources should be cached locally in the browser. This means setting a far-future expiration date on filetypes such as .css and .js content. This first step is achieved rather simply by adding something like below to the application’s .htaccess file or virtualhost’s httpd.conf include.

<IfModule mod_expires.c>
  Header set cache-control: public
  ExpiresActive on
  ExpiresDefault                          "access plus 1 month"

 # Other ExpiresBy... declarations here...

 # css and javascript
  ExpiresByType text/css                  "access plus 1 month"
  ExpiresByType application/javascript    "access plus 1 month"
  ExpiresByType text/javascript           "access plus 1 month"
</IfModule>

FileETag None

Note the last line above removes ETags, since according to Yahoo ETags are not needed for static content with far-future expiries set1.

The changes above will cause CSS and Javascript files to be cached locally in the browser for 1 month from the first access time, causing future requests to be made without checking against the server for newer versions of these static resources. Though the caching desired is now in place, it’s not perfect since if these static resources are modified in any way, the cached local browser version of these files will not be invalidated.

Invalidating the Cache

Currently in the <head> of my application, default stylesheets are added and then all included stylesheets (including those added by the specific action being requested) have their <link ... /> tags constructed and appended to the DOM using the following code

  use_stylesheet('main.css');
  use_stylesheet('handheld.css', '', array('media' => 'handheld'));

  include_autoversioned('stylesheets');

The include_autoversioned() function is where the work is done which invalidates the browsers’ cache. This function is a wrapper of Symfony’s include_stylesheets() and include_javascripts(). I have added a Template.php file to my application’s lib/helpers/ directory which contains the function as shown below.

/**
 * Depending on the type of include requested (stylesheets or javascripts), for
 * each file already added to the response object, for each file, i.e. /css/base.css,
 * replaces it with a string containing the file's mtime, i.e. /css/base.1221534296.css.
 *  
 * @see http://stackoverflow.com/questions/118884/what-is-an-elegant-way-to-force-browsers-to-reload-cached-css-js-files
 *  
 * @param $file  The file to be loaded.  Must be an absolute path (i.e.
 *               starting with slash)
 *               
 * @throws InvalidArgumentException when invalid type is requested
 *                
 * @return void (outputs string response directly)               
 */
function include_autoversioned($type = 'stylesheets'){
  if (!in_array(strtolower($type), array('stylesheets', 'javascripts'))) {
    throw new InvalidArgumentException(sprintf("\$type can only be 'stylesheets' or 'javascripts': '%s' was passed", $type));
  }

  $function = sprintf('get_%s', $type);
  $code = $function();
  unset($function);

  $code = preg_replace_callback('/(href|src)\=\"([^\"]+)\"/', function($matches){    
    if (strpos($matches[2], '/') !== 0 || !file_exists(sfConfig::get('sf_web_dir') . DIRECTORY_SEPARATOR . $matches[2])) {
      $path = $matches[2];
    } else {
      $mtime = filemtime(sfConfig::get('sf_web_dir') . DIRECTORY_SEPARATOR . $matches[2]);
      $path = preg_replace('{\\.([^./]+)$}', ".$mtime.\$1", $matches[2]);
    }

    return str_replace($matches[2], $path, $matches[0]);
  }, $code);

  echo $code;
}

(Note: The above code requires >= PHP5.3 due to the use of an anonymous function)

Also, again to the application’s .htaccess file or virtualhost’s httpd.conf include, the following must be added above the other RewriteRules that are in place for Symfony applications by default2.

# Cache-busing js and css files
RewriteRule ^(.*)\.[\d]{10}\.(css|js)$ $1.$2 [L]

To show what this does, let’s look at the <link ... /> tags generated using Symfony’s get_stylesheets in the <head>

<link rel="stylesheet" type="text/css" media="screen" href="/css/main.css" />
<link rel="stylesheet" type="text/css" media="handheld" href="/css/handheld.css" />

and compare this to the output generated by the new include_autoversioned('stylesheets')

<link rel="stylesheet" type="text/css" media="screen" href="/css/main.1282090705.css" />
<link rel="stylesheet" type="text/css" media="handheld" href="/css/handheld.1282076131.css" />

By modifying the filenames of these static resources by injecting a unix timestamp for the last-modified time of each file, whenever a change is made to one of these files, however small, it will cause the filename of that resource to change, effectively invalidating the local browser’s copy of that file. This guarantees that a browser will maintain a copy of a static resource for as long as can be reasonably expected, while always guaranteeing they immediately receive the latest changes to those resources as soon as they’re published.


  1. The .htaccess section was in part taken from http://github.com/paulirish/html5-boilerplate 

  2. Though modified considerably, the cache invalidation was inspired by this stackoverflow answer 

Tags: cache code php symfony symfony 1.4 tips php5.3

June 16, 2010

Symfony + sfController::getPresentationFor() + sfWebRequest::getRequestFormat()

There may be times when part or all of a text/plaintext version of an action’s template needs to be rendered for use from within another action who’s display format is set as text/html. For example, if there is a text/plaintext version of an order receipt that appears within an application, and that same version of the order receipt needs to be mailed to a user from within a separate action, we need to tell the order receipt to render in text/plaintext otherwise it will attempt to render in the format of the current action; in this case text/html. This is assuming the email is being sent as text/plaintext itself and the rendering action’s format is text/html.

Symfony offers a sfWebRequest::getPresentationFor() method which provides the functionality to render an action’s view from within another action, even if the calling action resides in a separate module. The trick is specifying the format for the other action while maintaining the requested format for the current action. The getPresentationFor() method signature is limiting in it’s support for specifying a format, so the only option appears to be as follows.

// In the current action
$format = sfContext::getInstance()->getRequest()->getRequestFormat();
sfContext::getInstance()->getRequest()->setRequestFormat('txt');
$otherActionContent = sfContext::getInstance()->getController()->getPresentationFor('moduleName', 'otherActionName');
sfContext::getInstance()->getRequest()->setRequestFormat($format);
unset($format);

The above code

  1. Temporarily retrieves the current action’s request format
  2. Sets the request format to the format required for our other action to render in
  3. Retrieves the properly rendered contents of the other action
  4. Resets the request format for the current action
  5. Unsets the temporary format storage

Tags: php symfony getPresentationFor sfwebrequest tips code

June 10, 2010

PayPal Sandbox + Website Payments Pro Integration

Setting up a new Website Payments Pro account for integration with an application I’m working on, it required a sandbox account be setup through http://developer.paypal.com/. Though the Test Accounts page is very simple to work with, it was a little tricky sorting out exactly how to integrate a Website Payments Pro account with a business test account that will actually process payments successfully.

I finally came across this help document. I can only assume this process seems immensly over-complicated to me because I am new to this type of integration with PayPal’s sandbox. I understand the necessity for all of the configuration, but am a bit unclear why developers are required to manually enter so much dummy account information with no guidance provided throughout the process directly through the signup forms.

Update: After hours of sifting through the mess that is PayPal’s developer network/documentation/sandbox site, I have opted to use Authorize.net. Getting test credentials was simple, and with this simple and concise class I was making test payment through my application within 45 minutes.

1 note Tags: paypal integration api help tips

June 7, 2010

Symfony’s format_date(), l10n Support, and Custom Formatting

Symfony comes with some great i18n and l10n support. The DateHelper comes with functionality for templates to provide locale-specific date formats. There are a bunch of default formats that can be easily used as

format_date(strtotime('now'), 'P', 'en_US'); // Outputs 'Monday 7 June 2010'
format_date(strtotime('now'), 'P', 'fr_FR'); // Outputs 'Lundi 7 Juin 2010'

Some of the pre-defined patterns are constructed using special token patterns. These tokens can be passed to the 2nd parameter of the format_date() function above to generate custom formatted locale-specific date/time stamps.

format_date(strtotime('now'), 'EEE, MMMM dd, yyyy hh:mm a'); // Outputs 'Mon, June 07, 2010 12:40AM'

It doesn’t appear detailed information is available in any of the main documentation books or in the source code regarding the tokens available for date/time formatting, but I did come across a wiki page in the symfony trac.

1 note Tags: php symfony tips format_date l10n code

March 24, 2010

MooTools toElement() - Using a Class Instance to Retrieve the Related DOM Element

In MooTools the $() is used in part to ensure the element passed to $() is extended by MooTools. Clientcide points out a great tip with the native Class toElement() function.

Aside from the benefits from the use of this from outside of a class passing an instance variable to $(), I like to use toElement() in combination with $(this) from within my Class objects simply for brevity.

var someClass = new Class({

  Implement: [Options],

  options: {
    domReference: null
  },

  initialize: function(options){
    this.setOptions(options);

    // Here, $(this) will reference this.options.domReference :)
    $(this).setStyle('background-color', '#F00');
  }

  toElement: function(){
    return this.options.domReference;
  }
});

var someInstance = new someClass({
  domReference: $('some-dom-element-id')
});

Obviously the example is a little bit ridiculous just to style an element’s background red, but the point is for me it’s much cleaner having the option to write

$(this).setStyle('background-color', '#F00');

than

this.options.domReference.setStyle('background-color', '#F00');

Tags: mootools javascript code tips

March 19, 2010

Opening Folder from Terminal in Gnome

The majority of the file management I do at work is through the terminal, however sometimes it is necessary to view/work with files through Nautilus. I’ve added the following command to my ~/.bashrc, causing openme to launch the current working directory in Nautilus.

alias openme='nautilus --browser . 2> /dev/null'

Tags: gnome linux bash tips bashrc nautilus

February 26, 2010

Parsing Email Addresses From a Log File

Recently I needed to parse all unique email addresses from a very large plaintext log file. A simple grep would not suffice because there were many cases where multiple email addresses appeared on the same line.

Realizing there are many solutions to this problem, here is the one I came across1 which I found most suitable for my needs.

perl -wne'while(/[\w\.\-]+@[\w\.\-]+\w+/g){print "$&\n"}' path/to/log_file | sort -u > parsed_unique_sorted_emails.txt

2 notes Tags: perl bash email parsing tips

May 29, 2009

Bash Shortcuts Using the Exclamation Point

The shell provides multiple shortcuts via the usage of the exclamation point when trying to execute recently performed commands. Err the Blog’s Bash Cheat Sheet provides the following tips and more.

If you need to run the most recently executed command again

!!

If the last command run requires super user privileges

sudo !!

Run the most recently executed command beginning with foo

!foo

Retrieve the last ‘word’ of the most recently executed command. In the example below, !$ would have the value my_database.

mysqldump -u root -p my_database
!$ # Will contain the value 'my_database'

Similar to the example above, this shortcut contains all ‘words’ except the first in the most recently run command. Using the same mysqldump command above, !* would have the value -u root -p my_database.

mysqldump -u root -p my_database
!* # Will contain the value '-u root -p my_database'

Note that the above commands can have :p appended, printing the commands or ‘words’ that the exclamation point replacement would use instead of running them as part of a new command in shell. The example below would print the most recently executed command beginning with foo

!foo:p

1 note Tags: bash linux shell tips