Get ID of Rails Model before saving…?

后端 未结 10 1196
鱼传尺愫
鱼传尺愫 2020-12-16 13:03

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 t

相关标签:
10条回答
  • 2020-12-16 13:17

    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.

    0 讨论(0)
  • 2020-12-16 13:21

    First understand the structure of database.

    • Id is gerated using sequence.
    • increament 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.

    0 讨论(0)
  • 2020-12-16 13:27

    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.

    0 讨论(0)
  • 2020-12-16 13:29

    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?

    0 讨论(0)
  • 2020-12-16 13:29

    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.

    0 讨论(0)
  • 2020-12-16 13:33

    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.

    0 讨论(0)
提交回复
热议问题