return to list of articles

Rails 4 and the array type with postgres

A quick explanation of why your Rails arrays might not be updating when you save new values to the array.

The problem

Are you having trouble with new data not persisting to a Rails array column when using Postgres? Well you aren’t alone. I’ve encountered issues with models in Rails 4 where new values would be added to an array column in a method, the instance would save but the value in the database wouldn’t update. After some digging around, it looks like this is caused by ActiveRecord.

If you use destructive or in-place changes to update your array then you’ll have to change the way your new data is persisted. Destructive changes for an array are changes called by methods such as << or push or pop. So despite the fact that you change the contents of an array, the array itself will still be the same so ActiveRecord will ignore your changed values.

An example

So an example of how things can go wrong for you would be if you had something like the following. Let’s say you have a User model and your user can have a list of places he or she has lived in. You can store those in an array in the locations column, like so:

add_column    :users, :locations, :text, array: true, default: '{}'

Then if you would like users to be able to add names of places they have visited then you can have a method like:

# User.rb
def add_location(place)
  update_attributes locations: locations.push(place)
end

If you expect this to work, you’re gonna have a hard time.

Two possible solutions

I can see two possible solutions. You can either tell ActiveRecord that you’ll definitely be changing a value and it will need to update it, or you can save a completely new array in its place.

This first one works because you’ll be saving a new array.

# User.rb
def add_location(place)
  update_attributes locations: locations + [ place ]
end

This second one works because you’re telling AR to track changes by marking it as dirty so that ActiveRecord::Dirty will evaluate locations.changed? to be true and will update the value. I prefer this method.

# User.rb
def add_location(place)
  locations_will_change!
  update_attributes locations: locations.push(place)
end

Bonus Tip

You can use ActiveRecord to query all the people who have visited South Africa or Cape Town using the following command:

User.where("'Cape Town' = ANY (locations)")
User.where("'South Africa' = ANY (locations)")

The above results in the following SQL call

SELECT * FROM users WHERE 'Cape Town' = ALL (locations);
SELECT * FROM users WHERE 'South Africa' = ANY (locations);

Get notified when Pawel releases new posts, guides, or projects