redirect_to and redirect

There are multiple ways to redirect URLs to a different URL in Rails, and they differ in small but important nuances.

Imagine you want to redirect the following url https://www.example.com/old_location?foo=bar to https://www.example.com/new_location?foo=bar.

Variant A

You can use ActionController::Redirecting#redirect_to in a controller action

class SomeController < ActionController::Base
  def old_location
    redirect_to(new_location_url(params.permit(:foo))) 
  end
end

This will:

  • It will redirect with a 302 st...

Josh McArthur: Fancy Postgres indexes with ActiveRecord

I recently wanted to add a model for address information but also wanted to add a unique index to those fields that is case-insensitive.
The model looked like this:

create_table :shop_locations do |t|
  t.string :street
  t.string :house_number
  t.string :zip_code
  t.string :city
  t.belongs_to :shop
end

But how to solve the uniqueness problem?

Another day, another undocumented Rails feature!

This time, it’s that ActiveRecord::Base.connection.add_index supports an undocumented option to pass a string argument as the v...

Setting expiry dates for images, JavaScript and CSS

When deploying Rails applications you might have noticed that JS and CSS are not cached by all browsers.

In order to force Apache to add expiry dates to its response, add the attached .htaccess to the public directory. This will add a header such as Expires: Thu, 07 Oct 2010 07:21:45 GMT to the httpd response.

Configuring Apache

Check that you have mod_expires enabled. You need it for the attached .htaccess to work:

sudo a2enmod expires

Configuring Nginx

You can add this:

ActiveRecord::Relation#merge overwrites existing conditions on the same column

In Ruby on Rails ActiveRecord::Relation#merge overwrites existing conditions on the same column. This may cause the relation to select more records than expected:

authorized_users = User.where(id: [1, 2])
filtered_users   = User.where(id: [2, 3])
authorized_users.merge(filtered_users).to_sql
# => SELECT * FROM users WHERE id IN (2, 3)

The merged relation select the users (2, 3), although we are only allowed to see (1, 2). The merged result should be (2).

This card explores various workarounds to combine two scopes so t...

Capybara: Testing file downloads

Download buttons can be difficult to test, especially with Selenium. Depending on browser, user settings and response headers, one of three things can happen:

  • The browser shows a "Save as..." dialog. Since it is a modal dialog, we can no longer communicate with the browser through Selenium.
  • The browser automatically downloads the file without prompting the user. For the test it looks like nothing has happened.
  • The browser shows a binary document in its own window, like a PDF. Capybara/Selenium freaks out because there is no HTML docum...

Unpoly: Loading large libraries on-demand

When your JavaScript bundle is so massive that you cannot load it all up front, I would recommend to load large libraries from the compilers that need it.

Compilers are also a good place to track whether the library has been loaded before. Note that including same <script> tag more than once will cause the browser to fetch and execute the script more than once. This can lead to memory leaks or cause duplicate event handlers being registered.

In our work we mostly load all JavaScript up front, since our bundles are small enough. We recent...

Sanitize user-generated filenames and only send files inside a given directory

If in your application your users pass along params that result in filenames, like invoices/generated?number=123. This could be your (very careless) controller method:

def generated
  send_file File.join(Rails.root, 'shared', 'invoices', params[:number])
end

This allows your users not only to access those files but also any files your application can read, like this:

invoices/generated?number=../../../../../etc/passwd
# => send_file '/etc/passwd'

You do not want this. In most cases you should prefer a show met...

Don't assign time values to date attributes

Do not pass times to date attributes. Always convert times to dates when your application uses time zones.

Background

A time-zoned Time attribute on a Rails record is converted to UTC using to_s(:db) to be stored, and converted back into the correct time zone when the record is loaded from the database. So when you are not on UTC, time objects will be converted as follows.

>> Time.current
=> Fri, 15 Mar 2013 11:56:03 CET +01:00
>> Time.current.to_s(:db)
=> "2013-03-15 10:56:03" # This is now UTC

Problem

That will...

Ruby: A small summary of what return, break and next means for blocks

Summary

  • Use return to return from a method. return accepts a value that will be the return value of the method call.
  • Use break to quit from a block and from the method that yielded to the block. break accepts a value that supplies the result of the expression it is “breaking” out of.
  • Use next to skip the rest of the current iteration. next accepts an argument that will be the result of that block iteration.

The following method will serve as an example in the details below:

def example
  puts yield
  puts ...

How to use Active Job to decouple your background processing from a gem

In a web application you sometimes have tasks that can not be processed during a request but need to go to the background.
There are several gems that help to you do that, like Sidekiq or Resque.

With newer Rails you can also use ActiveJob as interface for a background processing library. See here for a list of supported queueing adapters.
For ...

How to discard a surrounding Bundler environment

tl;dr: Ruby's Bundler environment is passed on to system calls, which may not be what you may want as it changes gem and binary lookup. Use Bundler.with_original_env to restore the environment's state before Bundler was launched. Do this whenever you want to execute shell commands inside other bundles.

Example outline

Consider this setup:

my_project/Gemfile     # says: gem 'rails', '~> 3.0.0'
my_project/foo/Gemfile # says: gem 'rails', '~> 3.2.0'

And, just to confirm this, these are the installed Rails versions for each ...

Prefer using Dir.mktmpdir when dealing with temporary directories in Ruby

Ruby's standard library includes a class for creating temporary directories. Similar to Tempfile it creates a unique directory name.

Note:

  • You need to use a block or take care of the cleanup manually
  • You can create a prefix and suffix e.g. Dir.mktmpdir(['foo', 'bar']) => /tmp/foo20220912-14561-3g93n1bar
  • You can choose a different base directory than Dir.tmpdir e.g. `Dir.mktmpdir('foo', Rails.root.join('tmp')) => /home/user/rails_example/tmp/foo20220912-14...

Find an ActiveRecord by any column (useful for Cucumber steps)

The attached patch lets you find a record by a string or number in any column:

User.find_by_anything('carla')
User.find_by_anything('email@domain.de')
User.find_by_anything(10023)

There's also a bang variant that raises ActiveRecord::NotFound if no record matches the given value:

User.find_by_anything!('carla')

Boolean and binary columns are excluded from the search because that would be crazy.

I recommend copying the attachment to features/support/find_by_anything.rb, since it is most useful in Cucumber step def...

Jasmine: Fixing common errors during initialization

Due to the way we setup Jasmine tests in our projects, you may run into various errors when Jasmine boots.

Setting jasmineRequire on undefined

Jasmine 4 may fail with an error like this:

Uncaught TypeError: Cannot set properties of undefined (setting 'jasmineRequire')

This is due to issues in Jasmine's [environment detection](https://github.com/jasmine/jasmine/blob/502cb24bb89212917a3c943b593fd918ffc481cb/lib/jasmine-core/...

Returning an empty ActiveRecord scope

Returning an empty scope can come in handy, e.g. as a default object. In Rails 4 you can achieve this by calling none on your ActiveRecord model.

    MyModel.none # returns an empty ActiveRecord::Relation object

For older Rails versions you can use the attached initializer to get a none scope.

Shortcuts for getting ids for an ActiveRecord scope

You can use .ids on an ActiveRecord scope to pluck all the ids of the relation:

# Modern Rails
User.where("users.name LIKE 'Foo Bar'").ids

# Rails 3.2+ equivalent
User.where("users.name LIKE 'Foo Bar'").pluck(:id)

# Edge rider equivalent for Rails 2+
User.where("users.name LIKE 'Foo Bar'").collect_ids

Faking and testing the network with WebMock

An alternative to this technique is using VCR. VCR allows you to record and replay real HTTP responses, saving you the effort to stub out request/response cycles in close details. If your tests do require close inspection of requests and responses, Webmock is still the way.


WebMock is an alternative to FakeWeb when testing code that uses the network. You sh...

Clean up application servers when deploying

Our development process makes us deploy very often. As the number of releases grows, junk clogs up the hard drive of our application servers:

  • Old release code
  • Old tmp folders with compiled view templates etc.
  • Precompiled assets (Javascripts, images...) that no longer exist. When using the asset pipeline, Capistrano will symlink the public/assets directory to shared/assets. This is cool since we can still serve previous assets after a new release, in the window where browser caches might still have references to old assets. But i...

ActiveRecord: Creating many records works faster in a transaction

When you need to insert many records into the same table, performance may become an issue.

What you can do to save time is to open a transaction and save multiple records within that transaction:

transaction do
  500.times { Model.create! }
end

Although you will still trigger 500 INSERT statements, they will complete considerably faster.

When I tried it out with a simple model and 500 iterations, the loop completed in 1.5 seconds vs. 6 seconds without a transaction.

Alternative

Another fast way to insert many ...

Controlling how your website appears on social media feeds

When a user shares your content, a snippet with title, image, link and description appears in her timeline. By default social networks will use the window title, the first image, the current URL and some random text snippet for this purpose. This is often not what you want.

Luckily Facebook, Twitter, etc. lets you control how your content appears in the activity streams. They even have agreed on a common format to do this: OpenGraph <meta> tags that go into your HTML's <head>:

<meta property="og:url" content="http://start.m...

Databases don't order rows unless you tell them so

There is no such thing as a "default order" of rows in database tables.

For instance, when you paginate a result set: When using LIMIT, it is important to use an ORDER BY clause that constrains the result rows into a unique order. Otherwise you will get an unpredictable subset of the query's rows. You might be asking for the tenth through twentieth rows, but tenth through twentieth in what ordering? The ordering is unknown, unless you specified ORDER BY.

In Rails, if you use Record.first or Record.last, it will default to orderin...

In MySQL, a zero number equals any string

In MySQL comparing zero to a string 0 = "any string" is always true!

So when you want to compare a string with a value of an integer column, you have to cast your integer value into a string like follows:

SELECT * from posts WHERE CAST(posts.comments_count AS CHAR) = '200' 

Of course this is usually not what you want to use for selecting your data as this might cause some expensive database operations. No indexes can be used and a full table scan will always be triggered.

If possible, cast the compared value in your application to...

SAML Single Logout (SLO)

There are two ways a logout in SAML can happen: Service Provider (SP) initiated and Identity Provider (IDP) initiated logout. I'll explain how to implement both flows with devise_saml_authenticatable.

Note

SAML also supports a SOAP and an Artifact binding to do this. This guide only refers to POST and Redirect bindings. devise_saml_authenticatable does not support SOAP and Artifact bindings.

SP initiated logout (using the Redirect Binding)

When the user clicks on Logout within the app, the app can trigger...

Creating a self-signed certificate for local HTTPS development

Your development server is usually running on an insecure HTTP connection which is perfectly fine for development.

If you need your local dev server to be accessible via HTTPS for some reason, you need both a certificate and its key. For a local hostname, you need to create those yourself.
This card explains how to do that and how to make your browser trust the certificate so it does not show warnings for your own certificate.

Easy: self-signed certificate

To just create a certificate for localhost, you can use the following command....