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

104

Existe-t-il un moyen de remplacer la valeur d'identifiant 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.

Codebeef
la source
1
Beaucoup de ces réponses échoueront par intermittence avec Rails 4 et indiqueront qu'elles fonctionnent. Voir ma réponse pour une explication.
Rick Smith

Réponses:

116

id est juste attr_protected, c'est pourquoi vous ne pouvez pas utiliser l'affectation en masse pour le définir. Cependant, lors du réglage manuel, cela fonctionne simplement:

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 de modèles ActiveHash en ActiveRecord. ActiveHash vous permet d'utiliser la même sémantique appartient_to dans 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 de la base de données référencent les identifiants en mémoire dans le fichier 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 garder toutes les références de clé étrangère identiques.


la source
@jkndrkn - Je ne sais pas ce que tu veux dire. J'ai ActiveRecord::VERSION::STRING == "3.2.11"ici (avec l'adaptateur sqlite3) et ce qui précède fonctionne pour moi.
Felix Rabe
Peut-être que ce que j'ai vécu ne s'est exprimé qu'avec le pilote mysql.
jkndrkn
@jkndrkn Fonctionne pour moi en utilisant le pilote MySQL pour Rails 3.2.18
lulalala
travaillé pour moi. sauvé ma vie. utilisé sur les rails 4.0.0 / postgres 9.3.5
allenwlee
Voir ma réponse pour savoir comment faire cela sur Rails 4.
Rick Smith
30

Essayer

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

cela devrait vous donner ce que vous recherchez.

PJ Davis
la source
2
Je ne sais pas pourquoi vous êtes rétrogradé, cela fonctionne très bien pour moi
semanticart
Cela continue de fonctionner à partir d'activerecord 3.2.11. La réponse publiée par Jeff Dean le 2 octobre 2009 ne fonctionne plus.
jkndrkn
ne fonctionne pas, semble fonctionner uniquement parce que l'appel à p.save, qui renvoie probablement false. obtiendra un ActiveRecord :: RecordNotFound si vous avez exécuté p.save!
Alan
29

Vous pouvez également utiliser quelque chose comme ceci:

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

Bien que comme indiqué dans la documentation , cela contournera la sécurité des affectations de masse.

Samuel Heaney
la source
Belle trouvaille, @Samuel Heaney, je peux vérifier que cela fonctionne parfaitement avec activerecord 3.2.13.
mkralla11
A travaillé pour moi aussi; n'était pas nécessaire d'inclure un champ séparé.
shalott
2
Hélas, cela ne fonctionne plus sur Rails 4, ils ont supprimé les options de hachage
Jorge Sampayo
@JorgeSampayo - dans Rails 4, vous ne devriez pas en avoir besoin car les attributs protégés peuvent être supprimés au profit de StrongParams.
PinnyM
21

Pour les rails 4:

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

Other Rails 4 réponses n'a pas fonctionné pour moi. Beaucoup d'entre eux semblaient 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, attribuer un idnuméro d'identification à incrémentation automatique ne fonctionne pas 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 changez createpour utiliser new+, savevous aurez toujours ce problème. Changer manuellement les éléments idsimilaires p.id = 10produit également ce problème.

En général, j'utiliserais update_columnpour changer le idmê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 qui peut discrètement corrompre votre base de données de production tout en indiquant qu'elle fonctionne.

Rick Smith
la source
3
C'était aussi la seule façon de le faire fonctionner dans une situation de rails 3.2.22 dans la console. Les réponses utilisant des variantes de «enregistrer» n'ont eu aucun effet.
JosephK
2
Pour les rails 4+ j'utilise:Post.new.update(id: 10, title: 'Test')
Spencer
6

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

p = Post.new(:id => 10, :title => 'Test')
p.save(false)
Codebeef
la source
Bien que cela puisse fonctionner, il désactive également toutes les validations, ce qui peut ne pas être ce que la demande a été conçue.
Jordan Moncharmont
C'est exactement ce dont j'avais besoin pour les données de départ où les identifiants sont importants. Je vous remercie.
JD.
Au départ, j'ai voté positivement, pensant que cela fonctionnerait pour les données de départ, comme @JD l'a souligné, mais ensuite je l'ai essayé avec activerecord 3.2.13 et j'obtiens toujours l'erreur "Can't assign protected attributes". Donc, voté contre :(
mkralla11
1
Malheureusement, cela ne fonctionne pas dans les rails 4, vous obtenez NoMethodError: méthode undefined `[] 'for false: FalseClass
Jorge Sampayo
@JorgeSampayo Cela fonctionne toujours si vous réussissez validate: false, au lieu de simplement false. Cependant, vous rencontrez toujours le problème des attributs protégés - il existe un moyen distinct de contourner ce que j'ai décrit dans ma réponse.
PinnyM
6

Comme Jeff le souligne, 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. Faites attention 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)

Nic Benders
la source
6

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)
user510319
la source
5
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 très bien si vous devez remplir une table avec un ensemble fixe d'identifiants (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 (afin 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
Sean Cameron
la source
2

Placez cette fonction create_with_id en haut de votre seeds.rb, puis utilisez-la 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 utilisez-le 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"})

Darren Hicks
la source
2

Ce cas est un problème similaire qui était nécessaire de remplacer le idavec 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

Puis :

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 très bien.

Bonne chance!.

CristianOrellanaBak
la source
J'avais besoin de créer un idhorodatage basé sur Unix. J'ai fait ça à l'intérieur before_create. Fonctionne très bien.
WM
0

Pour Rails 3, le moyen le plus simple de le faire est d'utiliser newavec le without_protectionraffinement, 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, ce 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 devriez utiliser StrongParams au lieu d'attributs protégés. Si tel est le cas, vous pourrez simplement attribuer et enregistrer sans passer aucun drapeau à new:

Post.new(id: 10, title: 'Test').save      # optionally pass `{validate: false}`
PinnyM
la source
Cela ne fonctionne pas pour moi sans mettre des attributs dans {}la réponse de Samuel ci-dessus (Rails3).
Christopher Oezbek
La réponse @PinnyM Your Rails 4 ne fonctionne pas pour moi. ida toujours 10 ans.
Rick Smith
@RickSmith dans l'exemple donné, a idété passé à 10 - c'est donc exactement ce qu'il devrait être. Si ce n'est pas ce à quoi vous vous attendiez, pouvez-vous clarifier avec un exemple?
PinnyM
Désolé, je voulais dire toujours pas 10. Voir ma réponse pour une explication.
Rick Smith
@RickSmith - intéressant, ce problème est-il unique à MySQL? Dans tous les cas, l'utilisation générale de l'attribution directe de clés primaires concerne les données de départ. Si tel est le cas, vous ne devriez généralement PAS essayer d'entrer des valeurs inférieures au seuil d'auto-incrémentation, ou vous devez désactiver l'auto-incrémentation pour cet ensemble de commandes.
PinnyM du
0

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.

Nikola
la source
0

vous pouvez insérer l'identifiant 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
wxianfeng
la source