Ruby Gotcha: Chained Assignment

Posted by code_monkey_steve on Dec 30, 2015 Dec 30

I ran into a tricky bug that involved an obscure bit of Ruby behavior, involving the chained assignment idiom and overloaded assignment operators.

Chained Assignment Considered Harmful

“Chained Assigned” refers to the idiom of using the value of an assignment expression in another assignment expression, e.g.:

a = b = 42

This seems simple enough, and it works fine in this case, but it’s an idiom that doesn’t always behave like you might expect. That is, you might expect it to be equivalent to:

b = 42
a = b   # WRONG

But it’s actually:

b = 42
a = 42  # correct

Overloaded assignment operators may surprise you

Consider a Person class that, for some contrived reason, stores people’s names in uppercase:

class Person
  attr_reader :name
  def name=(name)
    @name = name.upcase
  end
end
person = Person.new

name = person.name = "Bob"

You might expect both variables (name and person.name) to have the same value (i.e. "BOB"), but after the assignment, name will be "Bob" and person.name will be "BOB".
That’s because …

Ruby ignores the return value of overloaded assignment operators

At least in that case the the behavior is consistent, if not obvious, but what about when we use conditional assignment for default values:

name = (person.name ||= "Unknown")

The first time this expression runs, name will be set to the default ("Unknown"), but subsequent times it will be the the uppercase value in person.name (i.e. "UNKNOWN").

How this can create subtle bugs

So far we’ve only looked at simple assignments of scalar values, but using Array or Hash values is where things can go very wrong. Consider a blog Post that has an array of tags. We want
to make sure that each tag is a String, so the assignment operator maps the list and casts each value:

class Post
  attr_reader :tags
  def tags=(tags)
    @tags = tags.map { |tag| tag.to_s }
  end
end
post = Post.new

tags = (post.tags ||= [])
tags += ['ruby', 'gotchas']

In this case, the conditional assignment means that tags will be set to the empty Array passed in the assignment, which is different than the Array (also empty) that’s returned by the
map call in the assignment operator. Adding the tag strings to the array does nothing, because it’s not the array stored in the Post. Once post.tags is set, though, the conditional
assignment does nothing, tags references the same Array as post.tags, and things work as expected.

If this seems like an extreme edge-case, you may have a point, but it’s also an actual bug that I ran into in a popular database library. Based on my new understanding on Ruby assignment,
I’m inclined to avoid chained assignment all together, and just move the default assignment to its own line.

comments | Tags: ruby

HTTP Caching Like an Idiot: Redux

Posted by code_monkey_steve on Dec 26, 2015 Dec 26

Apparently Steam didn’t read my last blog post, as they’ve discovered their own way to Use HTTP Caching Like an
Idiot
. Come on, people, what’s the point of me making all these mistakes if you won’t learn
from them?

comments | Tags: HTTP and bugs

How to Use HTTP Caching Like an Idiot

Posted by code_monkey_steve on May 12, 2013 May 12

I’ve found that it’s pretty easy to run your own custom blog for with zero hosting cost, and even survive some significant traffic spikes, with liberal usage of HTTP Caching in Rails. So it’s somewhat ironic that when I come to update this blog this morning (after giving the last post a good year to really take root in the Collective Unconscious), I noticed that my caching fetish had shot me in the foot.

You see1, for logged-in users (i.e. me) the blog pages have an extra panel in the sidebar for creating new posts. Since all of the blog pages are cached (with last_modified set to the update time of the most recent post), that admin panel was getting cached along with the rest of the page, and served to all public visitors (both of them). D’oh!

The quick solution was pretty simple:

  fresh_when(...)  unless current_user

That will at least keep from polluting the publicly-cached page with user- (and, especially, admin-) specific HTML. If that seems like such an obvious thing to do that only an idiot wouldn’t think to do it, then you’re probably reading the wrong blog.

For the rest of us, I’m contemplating more complicated (but safer!) solutions, to keep us from spamming the internet with our private data. The first step might just be a mechanism where the partial that renders users-specific HTML could just cancel the HTTP caching. A more scalable solution would involve Fragment Caching and Memcached. But part of me wants to go Full Paranoid with some sort of Frankensteinian melding of SafeBuffers and CanCan, so no string ever makes it to the response body unless it’s Certified Cache Safe.

You know, for idiots!

1 That’s a retroactively-foreshadowing meta-pun. You’re welcome.

comments

Small Footprint MongoDB

Posted by code_monkey_steve on Mar 25, 2012 Mar 25

MongoDB comes configured out of the box for maximum performance and reliability on production databases. But it can be a bit of a disk hog, and if you’re using a development environment with an SSD like me (which I highly recommend), disk space might be scarce. After doing a little research, I found configuration settings that significantly reduce MongoDB’s disk usage.

Edit your MongoDB configuration file (/etc/mongod.conf) and add some/all of the following:

smallfiles = true

Uses smaller data file sizes — starting at 16MB instead of 128MB — and create fewer files initially. This can save almost 200MB on small collections (each!).

oplogsize = 100 (MB)

If you’re using an oplog for replication (or just for update notifications), you can set the size smaller than the default of “5% of all disk space”.

nojournal = true

MongoDB 2.0 introduced journaling, which is great for production environments, but not very useful in development. You can disable it and save several GB.

comments

Excluding a bad RPM package

Posted by code_monkey_steve on May 12, 2011 May 12

I’m a big fan of KDE, as both a user and a developer, and Akregator is my RSS feed reader of choice. I’m also a big fan of RSS feeds, using them for almost all my regular daily information consumption.

So imagine my notable lack of delight when, after doing a regular YUM update, I discovered that the latest version of Akregator has a serious bug that makes it almost unusable. And I didn’t even want the new version anyway.

Ah, but since I’m using RPM and YUM, the fix for this sort of thing is actually pretty simple, although it took me a few minutes of reading man pages to work it out, so I thought I should share the fruits of my labor. Here’s how you exclude a bad RPM package:

First, find the last good version of the appropriate package:


$ rpm -qf `which akregator`
kdepim-4.4.11.1-2.fc14.x86_64

$ sudo yum list kdepim --showduplicates
...
Installed Packages
kdepim.x86_64        7:4.4.11.1-2.fc14
Available Packages
kdepim.x86_64        6:4.4.6-2.fc14
kdepim.x86_64        7:4.4.11.1-2.fc14

In this case, the desired version is 4.4.6 (the “6:” is the epoch, the “-2” is the release, and the “fc14” is the architecture).

Next, downgrade the package. If there are any dependency errors, you’ll also need to downgrade those packages too.


$ sudo yum downgrade kdepim-4.4.6
...
Error: Package: 7:kdepim-libs-4.4.11.1-2.fc14.x86_64
...

$ sudo yum downgrade kdepim-4.4.6 kdepim-libs
...
Removed:
  kdepim.x86_64 7:4.4.11.1-2.fc14                                                   kdepim-libs.x86_64 7:4.4.11.1-2.fc14

Installed:
  kdepim.x86_64 6:4.4.6-2.fc14                                                      kdepim-libs.x86_64 6:4.4.6-2.fc14

Complete!

And finally, edit /etc/yum.conf and add the offending package version to an exclude line:


[main]
...
exclude=kdepim-4.4.11.1 kdepim-libs-4.4.11.1

Take that, kdepims-4.4.11. plonk

comments | Tags: rpm and tips