[TIP] Working with columns that end with "?", like "male?"

I was about to write a really long question asking how to do this, then
I figured it out. I figured I’d post it in case anyone else runs into
it.

Lets say you have the following hypothetical table associated with the
model Attendee:

create_table :attendees do |t|
t.column :name, :string
t.column :first_time?, :string, :limit => 3, :default => “no”
t.column :first_place?, :boolean, :default => false
end

I altered between using a string and a boolean for the sake of the
example. Chances are you’d use one or the other.

Now in Erb:

a = Attendee.new
=> #<Attendee:0x493421c @attributes={“first_place?”=>false,
“name”=>nil, “first_time?”=>“no”}, @new_record=true>

By convention, ActiveRecord automatically creates a “#{field_name}?”
method for each field in your table which returns true or false
depending on if the field is valued. So if we entered

a.methods

We’d see among the results “name” and “name?”, correlating to the name
column we created.

a.name?
=> false

a.name = “CDB”
=> “CDB”

a.name?
=> true

However, ActiveRecord does not create these methods if your field ends
in with a “?”! In the list of methods mentioned above we would see only
“first_place?” and “first_time?”, not “first_place??” nor “first_time??”
as I expected. It turns out that ActiveRecord skips this step because it
sees there is already a method with this name.

a.first_place?
=> false

a.first_time?
=> “no”

The first example is misleading as it returns a boolean just as our
name? method does. This is because the field itself is a Boolean field.
In fact, there really is no distinction between the name? method that
ActiveRecord generates and the first_place? method that correlates to
our field.

a.first_place?.class
=> TrueClass

a.first_place?.class.ancestors
=> [TrueClass, Object, Base64::Deprecated, Base64, Kernel]

a.name?.class
=> TrueClass

a.name?.class.ancestors
=> [TrueClass, Object, Base64::Deprecated, Base64, Kernel]

The tricky part here is that ActiveRecord, for some reason, won’t let
you set a new value to the first_time? and first_place? fields as you
would normally do.

a.first_place? = true
SyntaxError: compile error
(irb):69: syntax error, unexpected ‘=’, expecting $end
a.first_place? = true
^
from (irb):69
from :0

a.first_time? = “yes”
SyntaxError: compile error
(irb):70: syntax error, unexpected ‘=’, expecting $end
a.first_time? = “yes”
^
from (irb):70
from :0

So how do we do it? Using an alternate accessor syntax:

a[“first_time?”]
=> “no”

a[“first_time?”] = “yes”
=> “yes”

a[“first_place?”]
=> true

a[“first_place?”] = false
=> false

I hope that saves someone else a bit of sanity. It had me scratching my
head for a while. I’m not sure if this is a “bug” or a “feature”, but I
plan on reporting it just in case.

~ Chris

Chris B. wrote the following on 04.10.2007 23:24 :

[…]

a.first_place? = true

SyntaxError: compile error
(irb):69: syntax error, unexpected ‘=’, expecting $end
a.first_place? = true
^
from (irb):69
from :0

This tries to send “first_place?=” to the a object.

My guess is that it is Ruby that prevents the ? from being part of the
method name unless it is the last character.

Nothing ActiveRecord can do about it…

Lionel

On 10/4/07, Lionel B. [email protected] wrote:

    from :0

This tries to send “first_place?=” to the a object.

My guess is that it is Ruby that prevents the ? from being part of the
method name unless it is the last character.

Nothing ActiveRecord can do about it…

I think the real question is why on earth would you want to have those
column names in the first place? If you have to work with a legacy
db, sure, but I’d never ever ever do that in a migration myself.

And if I did have a legacy db with that schema, I’d just write some
sensible methods:

def male=(val)
write_attribute :male?, val
end

def male
read_attribute :male?
end

def male?
!!male
end

Normal assignment, raw attribute, and predicate…the Rails Way.

Pat

Lionel B. wrote:

My guess is that it is Ruby that prevents the ? from being part of the
method name unless it is the last character.

Nothing ActiveRecord can do about it…

Good point, Lionel.

Pat M. wrote:

I think the real question is why on earth would you want to have those
column names in the first place? If you have to work with a legacy
db, sure, but I’d never ever ever do that in a migration myself.

I didn’t write this to encourage anyone to do so, but in case anyone did
run into it. I work with an “enterprise helpdesk application” (read:
bloated piece of crap built on ASP) and the database has a mess of
fields with special characters like that. So, if you can’t get RoR to
work with your fields in the dot syntax, you can go at it the
model[“quoted_field_name”] way.

And if I did have a legacy db with that schema, I’d just write some
sensible methods:

def male=(val)
write_attribute :male?, val
end

def male
read_attribute :male?
end

def male?
!!male
end

Yeah, that makes sense. I didn’t actually make it to the model code when
I found this. I was working Erb trying to get some associations to work
(see my numerous previous topics) and worked it out there before calling
it quits for the day. You’re right though, you’re better off just
building better accessor methods if those are the field names that
you’re stuck with.

Again. I’m not advocating using special field names, however:

  1. This does follow the Ruby convention of least surprise
    (first_place? answers a question and returns a boolean), so it seemed
    natural to try to extend that to the DB (I know better now)

  2. ActiveRecord didn’t complain about creating a column named
    :first_place?, so I would expect it to also handle it properly when
    accessing it later. Or, throw an error or warning when creating the
    field saying that YMMV when using that syntax.

Thanks for weighing in.