Topic: "Illegal" field names

I'm developing an application that has to connect to a legacy database. I have no control over the structure of this database, but the Rails application has to read and write to certain tables in that database.

So I've setup the two connections, and everything works fine. Except the one table kept on giving me an error when I tried to save to it. Either a new record, or an existing record. It kept on complaining about

"ArgumentError: wrong number of arguments (1 for 0)"

in the callback function of ActiveRecord. This happened whenever the Model attempted to validate the record.

So I finally figured out that the table in the Legacy database has a field called "callback" (It's a telephony app). So when Rails created the ORM structure it overrides it's own "callback" method with a new one mapped to the field called "callback".

The only way I could think of around the problem, was not to map that field. Luckily I don't need to use that field for my app. So I put the following into my Model:

module ActiveRecord
  module ConnectionAdapters
    MysqlAdapter.class_eval do
      def columns(table_name, name = nil)#:nodoc:
        sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
        columns = []
        execute(sql, name).each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") unless field[0] == 'callback'}
        columns
      end
    end
  end
end

This code comes out of the MySQL adapter for ActiveRecord. I added this bit:
unless field[0] == 'callback'

This does the trick very well, but I feel it's a bit of a dirty fix to the problem. It certainly makes upgrading to a new version of Rails (or ActiveRecord) problematic, as I'll have to make sure to check that specific method to see if it needs updating.

Is there a better way to let ActiveRecord not map a certain field in a table? Or even better, give a field a method name that's different from the field name?

Re: "Illegal" field names

You could set up a mysql view which is basically just that table, but with the callback column renamed to something more friendly.  Then tell the relevant model to use the view instead of the table. 

Make sure you put the create view code in a migration (if you do go down this route).

###########################################
#If i've helped you then please recommend me at Working With Rails:
#http://www.workingwithrails.com/person/ … i-williams

Re: "Illegal" field names

someone posted a nice fix for this somehwere in the forum, where he monkeypatched an AR method that was checking weither an attribute accessor was already set or something.

essentially it keeps rails from creating the getter and setter methods for a field, so you can do
@instance.attributes[:callback]  = " blabla"
but not
@instance.callback  = " blabla"

got rid of the error.

unfortunatelly i can'T find it right now sad

Re: "Illegal" field names

Max Williams wrote:

You could set up a mysql view which is basically just that table, but with the callback column renamed to something more friendly.  Then tell the relevant model to use the view instead of the table. 

Make sure you put the create view code in a migration (if you do go down this route).

That is a good idea. However, I don't have any rights on that database. It's not even on the same server as my application's main database. I only have limited rights to certain tables there.

Re: "Illegal" field names

found it:

class MyTable < ActiveRecord::Base
   class << self
     def instance_method_already_implemented?(method_name)
       return true if method_name == 'callback'
       super
     end
   end
end

you can still access the attribute like this:
<% = @instance[:callback] %>
#or
@instance[:callback] = "bla"
#but this won't work:
<%= @instance.callback %>
@instance.callback = "bla"

not THAT clean either, but much more likely to stay arounf for future versions... and it's right in yur model instead of the adapter...

i would create custom accessors in the model for reading and writing this attribute so you can still use mass assignment in forms etc.

class MyTable < ActiveRecord::Base

def call_back=(text)
  self[:callback] = text
end
def call_back
  self[:callback]
end