Ruby on Rails
HowToUseNonDatabaseProvidedIDs

I’d been struggling with this one for a while: I needed a class which was keyed on something non-sequential, non-guessable and non-numeric. In my case, uuids. ActiveRecord doesn’t give you a great deal of help to do this, it generally assumes that you will use a column called ‘id’ that is some sort of integer and that the database will always give you a value for it.

If you’re not doing it this way, you need to work around a few things:


  def before_create
    self.id = uuid
  end

Q: I too use Postgres, and uuid as a primary key. Dumb question on the above: how do you generate a uuid in Ruby ? I’m migrating from .Net where uuid is a system defined class.


class Fixtures
  def self.reset_sequences(connection, table_names)
  end
end

TufTy

Hi TufTy,
Well I did try this but I am a newbe and must have got something wrong. Here is my Model:


class Job < ActiveRecord::Base
   def before_create
      self.id = job_id
   end

   def self.reset_sequences(connection, table_names)
   end
end


But when I try to do a save ActiveRecord keeps refering to a missing ID field. I really just want to use PK’s that don’t use autoincrement. Better yet that don’t use autoincrement and don’t have to be integer. I want to assign the value for the PK myself when I create a new record.

Here is the table:
CREATE TABLE [dbo].[jobs] (

[job_id] [int] NOT NULL ,

[job_desc] [varchar] (50) NULL ,

[min_lvl] [tinyint] NULL ,

[max_lvl] [tinyint] NULL

) ON [PRIMARY]

GO

—————
Tuffy and others:

I’ve been struggling
on userping the Rails autoincrement and hide primary key phillosopy as in real world applications it just isn’t an across the board “one way for all ID’s”. So, here’s how far I’ve gotten which does give you the ability to self-assign in any manner you like the ID for new records.

Here’s what I did (running against the Oracle demo schema for Scott/Tiger and Emp, Dept tables):

models/emp.rb (you won’t need the _before_type_cast if you’re using character ID’s – here I want to display the numeric ID and Rails wants to hide it):


def empno_before_type_cast
return read_attribute(:empno) if read_attribute(:empno)
return Integer(self.empno_next)
end
def empno_next
return ActiveRecord::Base.connection.select_value(‘select max(empno)+1 from emp’)
end

views\emp\new.rhtml (not much different here):


New emp

<= start_form_tag :action => ‘create’ %>
<
= render :partial => ‘form’ >
<
= submit_tag “Create” >
<
= end_form_tag %>

<%= link_to ‘Back’, :action => ‘list’ %>

and in my views\emp\edit.rhtml:


Editing emp

<= start_form_tag :action => ‘update’, :id => @emp.empno %>
<
= render :partial => ‘form’ >
<
= submit_tag ‘Update’ >
<
= end_form_tag %>

<= link_to ‘Show’, :action => ‘show’, :id => @emp %> |
<
= link_to ‘Back’, :action => ‘list’ %>

The thing that makes this work is the conditional in the empno_before_type_cast. You could just as easily overload the attribute itself in the model (for me the emp.rb) like


def empno
return read_attribute(:empno) if read_attribute(:empno)
return Integer(self.empno_next)
end

Can’t be right, but it worked

AKA: Horribal hack of doom

I had a lot of problems with this and postgres, especially because just remapping the primary key to some other column value wasn’t quite enough to avoid getting errors like:

ActiveRecord::StatementInvalid (PGError: ERROR: \
relation "usr_general_rights_login_seq" does not exist

I ended up setting the hooks, as mentioned above, and then defining a different column name for the primary key otherwise. It was an existing column, but essentially not in use. Primarily, I don’t think that anything cares about the values of that column so it was a good candidate to play with. I was finding if I set it to the correct value that I wanted to treat as a primary key that it would start inserting NULL’s where I wanted the my uid’s to be, as it was data I was defining as part of the passed attributes. Perhaps I just found some strange loop hole. Certainly, if anyone has a better solution that works without needing to make one of your columns into a NULL storage unit. I’d love to hear about it <leah@heinous.org>.

Here’s the example from the model. ‘login’ is the closest thing I have to a unique ID in this case, and the ‘changed_from’ column is just a random varchar field for an updating client to ID itself with.


  set_primary_key "changed_from"

def before_create self.id = login end def before_update self.id = login end


—————
Now, while this worked sometimes, sometimes, for whatever reason, it did not, with postgres still compaining about sequence relations that don’t exist. So here’s an even worse hack:

Create lib/pghack.rb


module ActiveRecord
  class Base
    class << self
      def sequence_name
        "#{table_name}_#{primary_key}_seq"
      end
    end
  end

  module ConnectionAdapters
    class PostgreSQLAdapter
      private

      def last_insert_id( table, column = id )
        klass = Object.const_get( Inflector.classify( table ) )
        sequence_name = klass.sequence_name
        0
      end
    end
  end
end

Then add require 'pghack' to config/environment.rb.

Then the model looks gets this code:


class UsrGeneralRight < ActiveRecord::Base

  set_primary_key "login"

  def self.sequence_name
    "login"
  end


Again, if anyone has a better way that deals with non-numeric, non-incrementing, postgres primary keys with non-standard names, that would be great for a link here.
—————
Note to the self-described “newbie” – It sounds like you’re trying to do something slightly different than what this page describes. You’re trying to remap the “id” field to another column. That is accomplished with the “set_primary_key” method, which will instruct ruby to use an alternate column for the object identifier. In your case, try this:


class Job < ActiveRecord::Base
   set_primary_key "job_id"

   ... snip ...
   end
end

Note to Tufty. This is exactly what I’m looking for. I’ll try it in MySQL tonight, and let you know how it works. Thanks!