Today Icelab Learned
 about rails

Using Arel to compare attributes of a record

I was just trying to find a way to run a query and compare the updated_at and created_at attributes of a record and came across this technique:

users = User.arel_table
User.where(users[:created_at].eq(users[:updated_at]))

It seems like .gt and .lt also work. While User.where("created_at = updated_at") would also work in SQL, we can use Arel to be database agnostic.

Arel docs

Manually incrementing count columns in rails

Adding a counter cache column to a model is a common optimisation we make in order to avoid unnecessary queries when trying to aggregate data associated with that particular model. Rails provides us with a number of ways to maintain the counter cache column’s value. The first is to follow the rails convention and add counter_cache: true to a belongs_to association and ensure we have a correctly named *_count column.

The other way to do it, is manually. In this case rails provides us with a few convenience methods to increment a given column.

The first is ActiveRecord::Base#increment!(attribute, by).

increment! is defined as:

def increment!(attribute, by = 1)
  increment(attribute, by).update_attribute(attribute, self[attribute])
end

and increment is defined as:

def increment(attribute, by = 1)
  self[attribute] ||= 0
  self[attribute] += by
  self
end

Which means that we’re first fetching the current attribute’s value, incrementing it then passing it on to update_attribute before it can be saved. This method leads to a non-atomic database operation, that is to say that at one point, the count is different in memory than it is in the database (which can lead to race conditions).

The second is ActiveRecord::Base#increment_counter(column_name, record_id)

increment_counter is defined as:

def increment_counter(counter_name, id)
  update_counters(id, counter_name => 1)
end

which executes SQL like:

UPDATE "table_name"
  SET "counter_name" = "counter_name" + 1
  WHERE id = 1

This means that we now have an atomic operation and the counter cache value is the same across the system.

Docs:

PostgreSQL as a message bus

PostgreSQL has its own listen/notify mechanism. It might be useful for cases when we have to manage messaging between different application processes, but would prefer to not use extra dependencies such as Redis.

process.rb

CHANNEL       = "slack_bot"
RESET_CHANNEL = "pg_restart"

ActiveRecord::Base.connection_pool.with_connection do |connection|
  conn = connection.instance_variable_get(:@connection)
  begin
    conn.async_exec "LISTEN #{RESET_CHANNEL}"
    conn.async_exec "LISTEN #{CHANNEL}"
    catch(:break_loop) do
      loop do
        conn.wait_for_notify do |channel, pid, payload|
          p [channel, payload]
          throw :break_loop if channel == RESET_CHANNEL
        end
      end
    end
  rescue => error
    p [:error, error]
  ensure
    conn.async_exec "UNLISTEN *"
  end
end

another-process.rb

User.connection.execute %Q(NOTIFY "slack_bot", params)

Displaying booleans in Active Admin

In Active Admin if you want to display a boolean property that doesn’t directly map to a database column you can use status_tag to display the value in a friendly way:

column :featured do |thing|
  thing.featured? ? status_tag("yes", :ok) : status_tag("no")
end

There’s a bit more info in the Active Admin docs which shows you how to add classes too!