This project is archived and is in readonly mode.

#3473 ✓stale
pmontrasio

Cannot assign values to non numeric primary key attributes in Model#new and #create

Reported by pmontrasio | November 9th, 2009 @ 12:15 PM

This is a model that defines a string field as primary key:

class Game < ActiveRecord::Base
set_primary_key "name" end

and this is the migration that creates the table and a couple of records.

class CreateGames < ActiveRecord::Migration
def self.up

create_table (:games, :id => false, :primary_key => "name")  do |t|
  t.string :name
end

Game.create(:name => "go")
Game.create(:name => "chess")

end ... end

Unfortunately the name attribute never gets assigned and is NULL inside the database.

Apparently there is no way to assign primary keys values into the new and create methods of the models. This is ok and desirable for the standard id auto incremented primary key but is bad when using non numeric primary keys. The workaround exists and is obvious but doesn't look nice:

x = Model.new(attributes_hash)
x.non_numeric_key = "value"
x.save

It is less DRY than a Model.create single liner.

I looked inside the ActiveRecord sources and found the culprit. It's the unless check inside the loop in this method:

  def attributes_from_column_definition
    self.class.columns.inject({}) do |attributes, column|
      attributes[column.name] = column.default unless column.name == self.class.primary_key
      attributes
    end
  end

I'm sure there are good reasons to have the check there. One I can think about is that AR is assuming that the primary key is managed by the database itself as an auto incremented field. On the other side rails gives us a way to define non numeric primary keys which cannot be auto incremented in a straightforward way so this looks like a sort of bug to me.

I implemented a workaround that doesn't remove the check because there might be a lot of code that assumes it's there. I want to be able to tell AR that I'm taking the responsibility to manage the values of the primary key. I added a new method to AR to do so and the model now is:

class Game < ActiveRecord::Base
set_primary_key "name" automatic_primary_key false # the new method end

Here's the patch to active_record/base.rb.
I had to define the automatic_primary_key method and modify the code of the initialize method, which is also called by create.

--- /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb 2009-11-09 12:22:12.000000000 +0100 +++ base.rb 2009-11-09 12:21:37.000000000 +0100 @@ -1219,6 +1219,18 @@

   end
   alias :primary_key= :set_primary_key
  • # PATCH
  • # Accept a value for the primary key in the Model#new and Model#create
  • # methods
  • def set_automatic_primary_key(value = false)
  •  define_attr_method :automatic_primary_key, value
    
  • end
  • alias :automatic_primary_key= :set_automatic_primary_key
  • def automatic_primary_key(value)
  •  @automatic_primary_key = value
    
  • end
  • # END OF PATCH + # Sets the name of the inheritance column to use to the given value, # or (if the value # is nil or false) to the value returned by the # given block. @@ -2432,6 +2444,12 @@ # hence you can't have attributes that aren't part of the table columns. def initialize(attributes = nil)
     @attributes = attributes_from_column_definition
    
  •  # PATCH
    
  •  unless @automatic_primary_key
    
  •    pk = self.class.primary_key
    
  •    @attributes.merge!({ pk => attributes[pk.to_sym] })
    
  •  end
    
  •  # END OF PATCH
     @attributes_cache = {}
     @new_record = true
     ensure_proper_type
    

I'm not familiar with the inner workings of AR so I really don't know if I'm breaking some other parts of rails but it seems to work. I'm also sure that there are better ways to do it and I'll be more than glad to be advised and learn.

Paolo

Comments and changes to this ticket

Create your profile

Help contribute to this project by taking a few moments to create your personal profile. Create your profile »

<h2 style="font-size: 14px">Tickets have moved to Github</h2>

The new ticket tracker is available at <a href="https://github.com/rails/rails/issues">https://github.com/rails/rails/issues</a>

Attachments

Pages