web-dev-qa-db-fra.com

Remplacer l'ID lors de la création dans ActiveRecord

Existe-t-il un moyen de remplacer la valeur d'ID d'un modèle lors de la création? Quelque chose comme:

Post.create(:id => 10, :title => 'Test')

serait idéal, mais ne fonctionnera évidemment pas.

103
Codebeef

id est juste attr_protected, c'est pourquoi vous ne pouvez pas utiliser l'affectation en masse pour le définir. Cependant, lors de la configuration manuelle, cela fonctionne:

o = SomeObject.new
o.id = 8888
o.save!
o.reload.id # => 8888

Je ne sais pas quelle était la motivation d'origine, mais je le fais lors de la conversion des modèles ActiveHash en ActiveRecord. ActiveHash vous permet d'utiliser la même sémantique d'appartenance à ActiveRecord, mais au lieu d'avoir une migration et de créer une table, et d'encourir la surcharge de la base de données à chaque appel, vous stockez simplement vos données dans des fichiers yml. Les clés étrangères dans la base de données font référence aux identifiants en mémoire dans le yml.

ActiveHash est idéal pour les listes de sélection et les petites tables qui changent rarement et ne changent que par les développeurs. Ainsi, lorsque vous passez d'ActiveHash à ActiveRecord, il est plus simple de conserver toutes les références de clés étrangères de la même manière.

117
Jeff Dean

Vous pouvez également utiliser quelque chose comme ceci:

Post.create({:id => 10, :title => 'Test'}, :without_protection => true)

Bien que comme indiqué dans le docs , cela contourne la sécurité d'affectation de masse.

29
Samuel Heaney

Essayer

a_post = Post.new do |p| 
  p.id = 10
  p.title = 'Test'
  p.save
end

cela devrait vous donner ce que vous cherchez.

29
PJ Davis

Pour Rails 4:

Post.create(:title => 'Test').update_column(:id, 10)

Autres Rails 4 réponses pas ont fonctionné pour moi. Beaucoup d'entre elles sont apparues pour changer lors de la vérification à l'aide de la console Rails, mais lorsque j'ai vérifié les valeurs dans la base de données MySQL, elles sont restées inchangées. D'autres réponses ne fonctionnaient que parfois.

Pour MySQL au moins, l'attribution d'un id en dessous du numéro d'identification de l'incrémentation automatique ne pas fonctionne sauf si vous utilisez update_column. Par exemple,

p = Post.create(:title => 'Test')
p.id
=> 20 # 20 was the id the auto increment gave it

p2 = Post.create(:id => 40, :title => 'Test')
p2.id
=> 40 # 40 > the next auto increment id (21) so allow it

p3 = Post.create(:id => 10, :title => 'Test')
p3.id
=> 10 # Go check your database, it may say 41.
# Assigning an id to a number below the next auto generated id will not update the db

Si vous modifiez create pour utiliser new + save, vous aurez toujours ce problème. Changer manuellement le id comme p.id = 10 génère également ce problème.

En général, j'utiliserais update_column pour changer le id même si cela coûte une requête de base de données supplémentaire car cela fonctionnera tout le temps. Il s'agit d'une erreur qui peut ne pas apparaître dans votre environnement de développement, mais peut corrompre discrètement votre base de données de production tout en disant qu'elle fonctionne.

20
Rick Smith

Comme le souligne Jeff, id se comporte comme s'il était attr_protected. Pour éviter cela, vous devez remplacer la liste des attributs protégés par défaut. Soyez prudent en faisant cela partout où les informations d'attribut peuvent provenir de l'extérieur. Le champ id est protégé par défaut pour une raison.

class Post < ActiveRecord::Base

  private

  def attributes_protected_by_default
    []
  end
end

(Testé avec ActiveRecord 2.3.5)

6
Nic Benders

En fait, il s'avère que faire les travaux suivants:

p = Post.new(:id => 10, :title => 'Test')
p.save(false)
6
Codebeef

nous pouvons remplacer attributes_protected_by_default

class Example < ActiveRecord::Base
    def self.attributes_protected_by_default
        # default is ["id", "type"]
        ["type"]
    end
end

e = Example.new(:id => 10000)
6
user510319
Post.create!(:title => "Test") { |t| t.id = 10 }

Cela ne me semble pas être le genre de chose que vous voudriez normalement faire, mais cela fonctionne assez bien si vous devez remplir une table avec un ensemble fixe d'ID (par exemple lors de la création de valeurs par défaut à l'aide d'une tâche de râteau) et vous souhaitez remplacer l'incrémentation automatique (de sorte que chaque fois que vous exécutez la tâche, la table soit remplie avec les mêmes identifiants):

post_types.each_with_index do |post_type|
  PostType.create!(:name => post_type) { |t| t.id = i + 1 }
end
5
Sean Cameron

Mettez ça create_with_id fonction en haut de votre seeds.rb, puis utilisez-le pour créer votre objet là où des identifiants explicites sont souhaités.

def create_with_id(clazz, params)
obj = clazz.send(:new, params)
obj.id = params[:id]
obj.save!
    obj
end

et l'utiliser comme ça

create_with_id( Foo, {id:1,name:"My Foo",prop:"My other property"})

à la place d'utiliser

Foo.create({id:1,name:"My Foo",prop:"My other property"})

2
Darren Hicks

Ce cas est un problème similaire qui devait remplacer le id par une sorte de date personnalisée:

# in app/models/calendar_block_group.rb
class CalendarBlockGroup < ActiveRecord::Base
...
 before_validation :parse_id

 def parse_id
    self.id = self.date.strftime('%d%m%Y')
 end
...
end

Et alors :

CalendarBlockGroup.create!(:date => Date.today)
# => #<CalendarBlockGroup id: 27072014, date: "2014-07-27", created_at: "2014-07-27 20:41:49", updated_at: "2014-07-27 20:41:49">

Les rappels fonctionnent bien.

Bonne chance!.

1
CristianOrellanaBak

Dans Rails 4.2.1 avec Postgresql 9.5.3, Post.create(:id => 10, :title => 'Test') fonctionne tant qu'il n'y a pas déjà une ligne avec id = 10.

0
Nikola

Pour Rails 3, la façon la plus simple de le faire est d'utiliser new avec le without_protection raffinement, puis save:

Post.new({:id => 10, :title => 'Test'}, :without_protection => true).save

Pour les données de départ, il peut être judicieux de contourner la validation que vous pouvez faire comme ceci:

Post.new({:id => 10, :title => 'Test'}, :without_protection => true).save(validate: false)

Nous avons en fait ajouté une méthode d'assistance à ActiveRecord :: Base qui est déclarée immédiatement avant l'exécution des fichiers de départ:

class ActiveRecord::Base
  def self.seed_create(attributes)
    new(attributes, without_protection: true).save(validate: false)
  end
end

Et maintenant:

Post.seed_create(:id => 10, :title => 'Test')

Pour Rails 4, vous devez utiliser StrongParams au lieu d'attributs protégés. Si tel est le cas, vous pourrez simplement attribuer et enregistrer sans passer aucun indicateur à new :

Post.new(id: 10, title: 'Test').save      # optionally pass `{validate: false}`
0
PinnyM

vous pouvez insérer id par sql:

  arr = record_line.strip.split(",")
  sql = "insert into records(id, created_at, updated_at, count, type_id, cycle, date) values(#{arr[0]},#{arr[1]},#{arr[2]},#{arr[3]},#{arr[4]},#{arr[5]},#{arr[6]})"
  ActiveRecord::Base.connection.execute sql
0
wxianfeng