开发者

Get ID of Rails Model before saving...?

开发者 https://www.devze.com 2023-01-11 19:49 出处:网络
How do you get the id of a rails model before it is saved? For example, if I create a new model instance, how can I get its ID before it is saved?

How do you get the id of a rails model before it is saved?

For example, if I create a new model instance, how can I get its ID before it is saved?

I know that the id is开发者_JAVA百科 created onsave and according to the database but is there a workaround?


I was looking for this too, and I found an answer:

Let's suppose model name is "Model" and table name is "models"

model.rb

before_save {
    next_id=Model.connection.select_value("Select nextval('models_id_seq')")
}

This will output the value your record will take for id IF it gets saved


Usually when people think they need to do this they actually do not need to do it. Like John says, explain what you are trying to do and someone can probably suggest a way to do it without having to know the id in advance.


This is less a Rails question and more a database question. This is a problem that will present itself in any web application framework, and the solution is the same in all places. You have to use a database transaction.

The basic flow will work like this.

  • Open a transaction
  • Save your model
  • Use the ID assigned by the database
  • If it turns out you actually don't want to keep this model in the database, roll back the transaction.
  • If it turns out you want to keep the model in the database, commit the transaction.

The main thing you will notice from this approach is that there will be gaps in your IDs where you rolled back the transaction.


Using the default Rails convention of an auto-incrementing integer primary key, there's no way to get the ID of a model before it's saved because it's generated by the RDBMS when the new row is inserted in the relevant table.

What problem are you actually trying to solve?


Most of the time when I needed an id can be grouped into a short list. When creating nested associations or connectin of the associations through. Let's assume we have: :user that have :pets through :user_pets association, where we will save their type.

If we have a properly configured "has_many: through Association" we can just User.pets.create(name: "Rex") but this is too simplistic, as we want to creat :pet type in :user_pets.

u = User.create(name: "Cesar")
u.id # => 1 # works fine

p = u.pets.create(name: 'Rex') 
# => rails will create UserPets => {id: 1, user_id: 1, pet_id: 1} for us

# But now we have a problem, how do we find :user_pets of our :pet?
# remember we still need to update the :type, the ugly (wrong) way:
up = p.user_pets.first
up.type = 'dog'
up.save! # working but wrong

# Do you see the problems here? We could use an id
P = Pet.new( name: "Destroyer" )
p.id # will not work, as the pet not saved yet to receive an id
up = UserPet.new( user_id: U.id, pet_id: p.id ) 
# => UserPet {id: 2, user_id: 1, pet_id: nil} # as we expected there is no id.

# What solutions do we have? Use nested creation!
# Good
up = UserPet.new(user_id: u.id, type: "dog")
# even better
up = u.user_pets.new(type: "dog") 
# it's just a shortcut for the code above, 
# it will add :user_id for us, so let's just remember it.

# Now lets add another 'new' from our creatd 'user_pet'
p = up.pets.new(name: "Millan")
user.save!
# => UserPet: {id: 3, user_id: 1, pet_id: 2, type: 'dog'} # => Pet: {id: 2, name: "Sam"}
# everything is working! YEY!

# we can even better, than writing in the beginning "User.create", 
# we can write "User.new" and save it with all the nested elements.

You saw how this created all the ids for us? Let's move to something even more complex! Now we have an additional table :shampoo that exactly as :user_pet, belongs to a :pet and a :user We need to create it without knowing the id of the :user and :pet

u = User.new('Mike')
up = u.user_pets.new(type: "cat") 
p = up.pets.new(name: "Rowe")

# But what are we doing now?
# If we do:
s = u.shampoos.new(name: "Dirty Job") 
# => Shampoo: {user_id: 2, pet_id: nil, name: "..."}
# We get "pet_id: nil", not what we want.

# Or if we do:
s = p.shampoos.new(name: "Schneewittchen") 
# => Shampoo: {user_id: nil, pet_id: 3, name: "..."}
# We get "user_id: nil", in both cases we do not get what we want.

# So we need to get the id of not created record, again.
# For this we can just do as in the first example (order is not important)
s = u.shampoos.new(name: "Mission Impossible") 
# => Shampoo: {id: 3, user_id: 2, pet_id: nil, name: "..."}
s.pet = p # this will give the missing id, to the shampoo on save.
# Finish with save of the object:
u.save! #=> Shampoo: {id: 3, user_id: 2, pet_id: 3, name: '...'} # => Pet: ...
# Done!

I tried to cover most common causes when you need element id, and how to overcome this. I hope it will be useful.


I don't believe there are any workarounds since the id is actually generated by the database itself. The id should not be available until after the object has been saved to the database.


Consider doing what you want right after the instance is created.

after_create do
  print self.id
end


First understand the structure of database.

  • Id is generated using sequence
  • increment done by 1 (specified while creating sequence)
  • Last entry to database will have highest value of id

If you wanted to get id of record which is going to be saved,

Then you can use following:

 1. id = Model.last.id + 1
    model = Model.new(id: id)
    model.save
    # But if data can be delete from that dataabse this will not work correctly.
  
 2. id = Model.connection.select_value("Select nextval('models_id_seq')")
    model = Model.new(id: id)
    model.save
    # Here in this case if you did not specified 'id' while creating new object, record will saved with next id. 
    
    e.g. 
    id
    => 2234
    model = Model.new(id: id) 
    model.save 
    # Record will be created using 'id' as 2234  
    
    model = Model.new()
    model.save
    # Record will be created using next value of 'id' as 2235  
     

Hope this will help you.


I just ran into a similar situation when creating a data importer. I was creating a bunch of records of different types and associating them before saving. When saving, some of the records threw validation errors because they had validate_presence_of a record that was not yet saved.

If you are using postgres, active record increments the id it assigns to a Model by keeping a sequence named models_id_seq (sales_id_seq for Sale etc.) in the database. You can get the next id in this sequence and increment it with the following function.

def next_model_id
    ActiveRecord::Base.connection.execute("SELECT NEXTVAL('models_id_seq')").first["nextval"].to_i
end

However, this solution is not good practice as there is no guarantee that active record will keep id sequences in this way in the future. I would only use this if it was used only once in my project, saved me a lot of work and was well documented in terms of why it should not be used frequently.


I know it's an old question, but might as well throw my answer in case anyone needs to reference it

UserModel

class User < ActiveRecord::Base
before_create :set_default_value

def set_default_value
   self.value ||= "#{User.last.id+1}"
end
0

精彩评论

暂无评论...
验证码 换一张
取 消