NoMethodError

raise "a blog about rails, javascript, and other stuff"

ActiveAdmin: Redirect to Index on Create/Update

By default, ActiveAdmin will display the :show action on create or update of a resource. Personally, I’d rather redirect to the :index action as long as the resource is valid.

You can overwrite ActiveAdmin’s default behavior using the controller block.

Notice, I’m adding “if resource.valid?” to make sure we only redirect when there are no validation errors. If we didn’t have that extra bit, the actions would always redirect to the :index action regardless.

Simple Rails Form Honeypot

If you have a form on your Rails site, chances are you get a fair amount of spam submissions from automated bots. One solution for preventing spam is by using a captcha field. They usually work very well to prevent spam, but they add a burden to human users. Another, less intrusive solution is to use a honeypot. A honeypot is a field that only bots can see, so you can know a form submission is spam if that field has a value.

There are gems available that will add a honeypot for you, but if you prefer to implement it yourself, it’s very straightforward.

In your form, just add an extra field that you’ll use to detect unauthorized submissions. Make sure to name it something normal so spam bots will populate it. I prefer to use the name “content”. Notice that the field has its tabindex attribute set to -1. This will prevent the field from being selected if a user tabs through the field. I’ve also added some inline hint text in case a user with a screen reader fills out the form.

In your stylesheet, just add some styles to make sure that a human won’t see the field on a desktop or mobile device.

The final step is to add a check in your controller to only process the form if the value of the field is blank. For me, I’m only sending a notification email if the form submission is certified anti-spam.

ActiveAdmin: Custom MetaSearch Filter

Recently, I had the need to create a custom ActiveAdmin filter for a site I was working on. I couldn’t find much information on the subject, but after digging around, it turns out that it’s pretty easy.

Here are my (admittedly contrived) models. You can see that a User belongs to an Account and an Account belongs to a Group. For this example, let’s say I want a filter in ActiveAdmin on the User resource that lists all the Groups.

Here’s my simplified ActiveAdmin resource, with the added filter.

To hook it up to the model, I just need to add a scope and a call to the built-in MetaSearch method search_methods.

ActiveRecord Random Ordering With Pagination

When I created nshvll.org, I wanted the ability to show the user a randomly ordered list of members. I also wanted to paginate the results, and I didn’t know if it was possible to have both. After a little research, I found that you can pass a seed to the mySQL RAND() function and have it return an identical list each time it’s called.

mySQL

Since I want each person to get a uniquely randomized list of members, I set a cookie with a seed which is randomly generated (1-100).

I set the seed in a before_filter in my ApplicationController.

Notice, I only set the cookie if it doesn’t exist. I also set the expiration of the cookie to 15 minutes because I don’t want the user to get the same list of members all the time.

Now, in the MembersController, I can pass that seed to mySQL and get a repeatable sequence of random records.

PostgreSQL

A little while after I built the site, I decided to move it from a personal VPS to Heroku. To do that, I needed to convert the database to PostgreSQL (or pay for a mySQL option). After a little research, I found out that PostgreSQL is a bit trickier than mySQL when it comes to using a seed with the random() function. You have to run a separate select query to set the seed before the query that gets the list of records.

PostgreSQL’s setseed() function requires a number between -1 and 1, so we need to switch to a random float instead of an integer in the ApplicationController

There we go. A repeatable, randomized set of records in mySQL or PostgreSQL.

Track Backbone.js Page Views With Google Analytics

Google Analytics tracks page views based on evaluating the tracking code on page load. Backbone.js makes this difficult because the page never reloads. Fortunately, it’s super easy to add a function to the Backbone router to manually track each route.

Newer Analytics Code

If your tracking code looks like below, then you have the newer Analytics tracking code that uses the ga function instead of the _gaq array.

Add the following code to your router:

Old Analytics Code

If your tracking code looks like this:

Add the following code to your router:

Using PhoneGap FileTransfer to Download Multiple Files to the Device

I’ve been working recently on a PhoneGap app that is designed to operate offline, and receives incremental data updates from an API when the user has an Internet connection. These incremental updates also include new/updated product images. Since the app needs to operate offline, I have to download the files to the device when they are updated or added so they can be shown offline.

PhoneGap has File and FileTransfer plugins that allow you to access the device’s file system and even upload/download files. The problem with these plugin are, almost all of the actions trigger callbacks, so it’s hard to keep the thread of what is happening. The PhoneGap documentation for these plugins are slightly lacking, so it was difficult to figure out how to accomplish what I needed. Through a lot of Googling and trial & error, I was finally able to get the following code working.

Ok, we have the files downloaded, but how to we show them in our app. As far as I could find, you can’t refence images on the device’s filesystem form the PhoneGap app.

Luckily, the PhoneGap File plugin has a way to read files from the filesystem. It includes a method to read the file as a data url which converts it to a base64-encoded string.

My app was written using Backbone with Handlebars templates. So, to display the image, I wrote a Handlebars helper to get the image from the file system and return it as a base64-encoded string. You’ll notice that the helper actually gets an element via jQuery and sets it src attribute. I had to do it this way since all of the File plugin methods are asynchronous and handled with callback.

The helper can be called like so:

Git Essentials - Hack & Ship

If you’re like me, your normal git workflow looks something like this:

  1. Do some work in a topic branch and need to rebase master into the branch.
  2. git checkout master
  3. git pull origin master (or fetch and merge)
  4. git checkout topic-branch
  5. git rebase master

Once you’re done with your topic branch, it might look something like this:

  1. git checkout master
  2. git merge topic-branch
  3. git push origin master

That’s a lot of work, and as a developer we can definitely make that process easier. About 4 years ago, I came across a set of bash scripts that simplified this workflow greatly. The creator, Rein Henrichs, named them Hack && Ship.

These tools worked great, but weren’t flexible enough for me. They assumed that you would always be rebasing and pushing to the master branch. If this wasn’t the case, you had to do everything by hand.

Here are my updated versions of Hack && Ship that allow you to rebase and push to any branch (such as staging or qa):

Hack

Ship

To use them, create two files named hack and ship somewhere in your path (like /usr/local/bin maybe) and make them executable. Then, you can invoke them like so:

hack or hack staging

push or push qa

hack && ship

Converting a Rails Database From mySQL to PostgreSQL

The taps gem allows you to transfer database structure and data easily between db types.

Add the necessary gems to your Gemfile

Make sure you have the gems for both db types you’ll be accessing.

Start the taps server

This will connect to the database that you are copying structure/date from.

authusername and authpassword can be whatever you want. This will be used to authenticate when pulling from this db server.

Pull the data into your new database

Make sure the new database has been created.

Done!