Identificación anulada al crear en ActiveRecord

104

¿Hay alguna forma de anular el valor de identificación de un modelo en la creación? Algo como:

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

sería ideal, pero obviamente no funcionará.

Codebeef
fuente
1
Muchas de estas respuestas fallarán de forma intermitente con Rails 4 y dirán que están funcionando. Vea mi respuesta para una explicación.
Rick Smith

Respuestas:

116

id es solo attr_protected, por lo que no puede usar asignación masiva para configurarlo. Sin embargo, al configurarlo manualmente, simplemente funciona:

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

No estoy seguro de cuál fue la motivación original, pero hago esto cuando convierto modelos de ActiveHash a ActiveRecord. ActiveHash le permite usar la misma semántica pertenece_ a en ActiveRecord, pero en lugar de tener una migración y crear una tabla, e incurrir en la sobrecarga de la base de datos en cada llamada, simplemente almacena sus datos en archivos yml. Las claves externas en la base de datos hacen referencia a los identificadores en memoria en el archivo yml.

ActiveHash es ideal para listas de selección y tablas pequeñas que cambian con poca frecuencia y solo lo cambian los desarrolladores. Entonces, cuando se pasa de ActiveHash a ActiveRecord, es más fácil mantener todas las referencias de claves externas iguales.


fuente
@jkndrkn - No sé a qué te refieres. Llegué ActiveRecord::VERSION::STRING == "3.2.11"aquí (con el adaptador sqlite3) y lo anterior funciona para mí.
Felix Rabe
Quizás lo que experimenté solo se expresó con el controlador mysql.
jkndrkn
@jkndrkn funciona para mí usando el controlador MySQL para Rails 3.2.18
lulalala
trabajó para mi. salvó mi vida. usado en rieles 4.0.0 / postgres 9.3.5
allenwlee
Vea mi respuesta sobre cómo hacer esto en Rails 4.
Rick Smith
30

Tratar

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

eso debería darte lo que estás buscando.

PJ Davis
fuente
2
no estoy seguro de por qué te votan negativamente, esto funciona muy bien para mí
semántica
Esto continúa funcionando a partir de activerecord 3.2.11. La respuesta publicada por Jeff Dean el 2 de octubre de 2009 ya no funciona.
jkndrkn
no funciona, solo parece funcionar porque llama a p.save, que probablemente devuelve falso. obtendrá un ActiveRecord :: RecordNotFound si ejecutó p.save!
Alan
29

También podrías usar algo como esto:

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

Aunque como se indica en los documentos , esto evitará la seguridad de asignación masiva.

Samuel Heaney
fuente
Buen hallazgo, @Samuel Heaney, puedo verificar que esto funciona perfectamente con activerecord 3.2.13.
mkralla11
Me funcionó a mi también; No era necesario incluir un campo separado.
shalott
2
Por desgracia, esto ya no funciona en Rails 4, eliminaron las opciones hash
Jorge Sampayo
@JorgeSampayo: en Rails 4 no debería necesitar esto, ya que los atributos protegidos se pueden eliminar en favor de StrongParams.
PinnyM
21

Para rieles 4:

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

Otras respuestas de Rails 4 no funcionaron para mí. Muchos de ellos parecían cambiar al verificar usando la consola Rails, pero cuando verifiqué los valores en la base de datos MySQL, permanecieron sin cambios. Otras respuestas solo funcionaron a veces.

Para MySQL al menos, la asignación de un idnúmero de identificación por debajo del incremento automático no funciona a menos que use update_column. Por ejemplo,

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 cambia createpara usar new+ save, seguirá teniendo este problema. Cambiar manualmente algo idsimilar p.id = 10también produce este problema.

En general, usaría update_columnpara cambiar el idaunque cuesta una consulta de base de datos adicional porque funcionará todo el tiempo. Este es un error que puede no aparecer en su entorno de desarrollo, pero puede corromper silenciosamente su base de datos de producción mientras dice que está funcionando.

Rick Smith
fuente
3
Esta fue también la única forma en que logré que funcionara en una situación de rieles 3.2.22 en la consola. Las respuestas que usan variaciones de 'guardar' no tuvieron ningún efecto.
JosephK
2
Para rieles 4+ utilizo:Post.new.update(id: 10, title: 'Test')
Spencer
6

En realidad, resulta que hacer lo siguiente funciona:

p = Post.new(:id => 10, :title => 'Test')
p.save(false)
Codebeef
fuente
Si bien puede funcionar, también desactiva todas las validaciones, lo que puede no ser lo que pretendía la pregunta.
Jordan Moncharmont
Esto es exactamente lo que necesitaba para los datos iniciales donde las ID importan. Gracias.
JD.
Originalmente voté a favor, pensando que esto funcionaría para datos semilla, como señaló @JD, pero luego lo probé con activerecord 3.2.13 y todavía obtengo el error "No se pueden asignar atributos protegidos". Entonces,
voto negativo
1
Desafortunadamente, esto no funciona en rieles 4, obtienes NoMethodError: método indefinido `[] 'para false: FalseClass
Jorge Sampayo
@JorgeSampayo Esto todavía funciona si pasa validate: false, en lugar de simplemente false. Sin embargo, aún se encuentra con el problema del atributo protegido: hay una forma separada de lo que he descrito en mi respuesta.
PinnyM
6

Como señala Jeff, id se comporta como si estuviera attr_protected. Para evitarlo, debe anular la lista de atributos protegidos predeterminados. Tenga cuidado al hacer esto en cualquier lugar donde la información de atributos pueda provenir del exterior. El campo de identificación está protegido por defecto por una razón.

class Post < ActiveRecord::Base

  private

  def attributes_protected_by_default
    []
  end
end

(Probado con ActiveRecord 2.3.5)

Nic Benders
fuente
6

podemos anular atributos_protegido_por_defecto

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

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

Esto no me parece el tipo de cosas que normalmente desearía hacer, pero funciona bastante bien si necesita completar una tabla con un conjunto fijo de identificadores (por ejemplo, al crear valores predeterminados usando una tarea de rake) y usted desea anular el incremento automático (para que cada vez que ejecute la tarea, la tabla se llene con los mismos ID):

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

Ponga esta función create_with_id en la parte superior de su seeds.rb y luego úsela para hacer su creación de objetos donde se desean identificadores explícitos.

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

y úsalo así

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

En lugar de usar

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

Darren Hicks
fuente
2

Este caso es un problema similar que fue necesario sobrescribirlo idcon una especie de fecha personalizada:

# 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

Y entonces :

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">

Las devoluciones de llamada funcionan bien.

¡Buena suerte!.

CristianOrellanaBak
fuente
Necesitaba crear una idmarca de tiempo basada en Unix. Lo he hecho por dentro before_create. Funciona bien.
WM
0

Para Rails 3, la forma más sencilla de hacer esto es usar newcon el without_protectionrefinamiento y luego save:

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

Para los datos de inicialización, puede tener sentido omitir la validación, lo que puede hacer así:

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

De hecho, hemos agregado un método auxiliar a ActiveRecord :: Base que se declara inmediatamente antes de ejecutar archivos semilla:

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

Y ahora:

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

Para Rails 4, debería utilizar StrongParams en lugar de atributos protegidos. Si este es el caso, simplemente podrá asignar y guardar sin pasar ninguna marca a new:

Post.new(id: 10, title: 'Test').save      # optionally pass `{validate: false}`
PinnyM
fuente
No funciona para mí sin poner atributos en {}la respuesta de Samuel anterior (Rails3).
Christopher Oezbek
@PinnyM Your Rails 4 respuesta no funciona para mí. idsigue siendo 10.
Rick Smith
@RickSmith en el ejemplo dado, idse pasó como 10, así que eso es exactamente lo que debería ser. Si eso no es lo que esperabas, ¿puedes aclarar con un ejemplo?
PinnyM
Lo siento, quería decir que todavía no son 10. Vea mi respuesta para una explicación.
Rick Smith
@RickSmith: interesante, ¿este problema es exclusivo de MySQL? En cualquier caso, el uso general para asignar claves primarias directamente es para datos semilla. Si es así, generalmente NO debe intentar ingresar valores que estén por debajo del umbral de autoincremento, o debe desactivar el autoincremento para ese conjunto de comandos.
PinnyM
0

En Rails 4.2.1 con Postgresql 9.5.3, Post.create(:id => 10, :title => 'Test')funciona siempre que no haya una fila con id = 10.

Nikola
fuente
0

puede insertar id por 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
fuente