I have an Oracle DB schema which includes a "users" table. This table has two non-null foreign keys to an editor and creator which are also users.
The schema dump looks like this:
create_table "users", :force => true do |t|
t.integer "creator_id", :precision => 38, :scale => 0, :null => false
t.integer "editor_id", :precision => 38, :scale => 0, :null => false
end
add_foreign_key "users", "users", :column => "creator_id", :name => "r_user_creatorid", :dependent => :nullify
add_foreign_key "users", "users", :column => "editor_id", :name => "r_user_editorid", :dependent => :nullify
My User model looks like the following:
class User < ActiveRecord::Base
belongs_to :creator, :class_name => "User"
belongs_to :editor, :class_name => "User"
validates_presence_of :creator, :editor
end
The problem comes when I try to save the first User. No other users exist yet, but I cannot have a null editor_id or creator_id. If I try to set the editor and creator to itself, I get a stack overflow.
In theory, it makes sense that all users (except the first) have a creator and editor. Is there any way to accomplish this without temporarily removing the non-null constraint?
So the problem is, there has to be a user at the top of the hierarchy, a user for whom there is no manager (editor in your example). That is why the classic solution to this sort of structure is to allow null values. You acknowledge this in your closing paragraph:
The kicker is, if the first user doesn't have a CREATOR or an EDITOR then there is no "temporary": you have to ditch the mandatory constraint. If you do this, the problem with the recursive foreign key constraint will disappear.
The alternative is to introduce what Aristotle called a Prime Mover, a User whose Creator is itself. Given this table:
it's pretty simple to create such a user:
So why isn't this the canonical solution. Well it leads to a slightly wacky data model which can create havoc with hierarchical queries once we add a few more users.
Basically the database doesn't like USERID being its own editor. However, there is a workaround, which is the
NOCYCLE
keyword (introduced with 10g). This tells the database to ignore circular references in the hierarchy:Here it doesn't matter because the data is still correctly hierarchical. But what happens if we do this:
We lose a relationship ( 1 -> 7). We can use the CONNECT_BY_ISNOCYCLE pseudo-column to see which row is cycling.
Oracle has lots of additional functionality to make it easier to work with hierarchical data in pure SQL. It is all in the documentation. Find out more.
I would have thought you'd remove the NOT NULL constraint (i.e. allow the first user to have NULL for creator and editor).
You can then implement constraints to make sure that all subsequent entries are not null, e.g.: