Today Icelab Learned

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:

Setting cookies in request specs

When writing request specs and you want to set a cookie, setting it with Capybara doesn’t seem to work, best to set it in the request headers.

Sad panda:

Capybara.current_session.driver.browser.set_cookie("really_useful_cookie=#{cookie_monster.id}")

Happy panda:

get some_path, {}, {"HTTP_COOKIE" => "really_useful_cookie=#{cookie_monster.id}”}

Using viewloader with turbolinks

When using viewloader with turbolinks, you want to make sure that viewloader executes once on the initial page load, once when the visitor reaches another page, and once when the visitor goes back in their browser (or forward for that matter).

To achieve this, get viewloader to execute on the ready, page:load and page:restore events:

$(document).on("ready page:load page:restore", function() {
  viewloader.execute(views);
});

page:restore is fired by turbolinks whenever “A cached body element has been loaded into the DOM”.

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)

Manually add sitemaps to Google’s Webmaster Tools

It’s good practice to add sitemaps to Google’s Webmaster Tools so you can check their health and get notifications about sitemap issues. We often host sitemaps on S3 because they could be large static files that we don’t want running through our Rails stack.

This leads us to the problem: If you have a sitemap that’s hosted externally to your website, you can’t add them through the Google’s Webmaster Tools interface. For some reason Google limits these to paths underneath the official domain attached to an account. There is a solution though — you can sneak them in using their notification URL. All you need to do is:

curl www.google.com/webmasters/tools/ping?sitemap=https%3A%2F%2Fwheeler-centre-heracles.s3.amazonaws.com%2Fsitemaps%2Fsitemap.xml.gz

And ta da, it’ll show up in the Webmaster Tools admin interface.

Pushing all branches

Say you want to push up an existing repository to a new remote on your favourite repository hosting service. You can use the following command if you want to make sure you include all branches:

git push <REMOTE-NAME> --all

This is especially helpful when you want to immediately create some PRs!

To push all tags:

git push <REMOTE-NAME> --tags

Show Open Graph image on first Facebook share

When you share a URL on Facebook for the first time — specifically, the first time anyone has shared it — it won’t include any of the images on the page and our clients will be :cry:. This happens even if you have included a meta tag that references an image:

<meta property="og:image" content="http://foo.com/image.png">

The second time a URL is shared, it’ll work as expected and your chosen og:image will show up in the share preview (along with any other images on the page) — Facebook seem to have shifted this all to a background job or something.

Fear not! There is a solution. If you include the dimensions of your image alongside the og:image meta tag it’ll force Facebook to show the image immediately:

<meta property="og:image" content="http://foo.com/image.png">
<meta property="og:image:width" content="300">
<meta property="og:image:height" content="400">

These dimensions don’t actually have to be accurate either. On Readings we don’t know the dimensions of images as they’re provided by Erudite without any meta-information, so we do a best-guess approach based on the ratio that book covers (or CD covers) tend to be.

Facebook image display when posting a link

When sharing links to Facebook, ████████ were only being given the option to display old images, and couldn’t work out why the new ones weren’t coming through.

As it happens, there are specific metatags required to force Facebook to notice the correct content, explained here.

“Without these tags, the Facebook Crawler will use internal heuristics to make a best guess about the title, description, and preview image for your content.”

To force refresh of a particular item without updating the site itself, you can also use the Facebook debugger.

If you go there, input the URL and click “Fetch new scrape information” until the “right” content shows up at the bottom.

CSS dropdown menus

Creating a CSS-only dropdown menu is easy!

CSS dropdown menus are the bomb

Using nested lists, the basic structure is:

ul.nav
  li.nav__list-item
    a.nav__list-anchor href="#"
      | Hogwarts
  li.nav__list-item.nav__list-item--has-dropdown
    span.nav__list-toggle
      | Students
      i.fa.fa-caret-down
    ul.nav__dropdown
      li.nav__dropdown-list-item
        a.nav__dropdown-list-anchor href="#"
          | Harry Potter
      li.nav__dropdown-list-item
        a.nav__dropdown-list-anchor href="#"
          | Ron Weasley
      li.nav__dropdown-list-item
        a.nav__dropdown-list-anchor href="#"
          | Hermione Granger
  li.nav__list-item.nav__list-item--has-dropdown
    span.nav__list-toggle
      | Teachers
      i.fa.fa-caret-down
    ul.nav__dropdown
      li.nav__dropdown-list-item
        a.nav__dropdown-list-anchor href="#"
          | Minerva McGonagall
      li.nav__dropdown-list-item
        a.nav__dropdown-list-anchor href="#"
          | Severus Snape

And the bare-bones CSS:

.nav__list-item {
  display: inline-block;
  position: relative;
}

  .nav__list-anchor,
  .nav__list-toggle {
    cursor: pointer;
    display: block;
  }

    .nav__dropdown {
      display: none;
      position: absolute;
      z-index: 2;
    }
    .nav__list-item--has-dropdown:hover .nav__dropdown,
    .nav__list-item--has-dropdown:focus .nav__dropdown {
      display: block;
    }

      .nav__dropdown-list-anchor {
        display: block;
      }

Consider mobile

:hover triggered menus don’t feel great on mobile. A nice solution is to use a JavaScript click event to append a modifier class to the the toggle element (.nav__list-anchor--dropdown-is-visible) and use that class to perma-show the menu.