update through rails query left includes has_one has_and_belongs_to_many ruby-on-rails activerecord

ruby-on-rails - through - rails sql query



La forma correcta de evitar ActiveRecord:: ReadOnlyRecord? (4)

Creo que la mejor solución es usar .join como ya lo hiciste y hacer un find por separado ()

Una diferencia crucial de uso: include es que usa la combinación externa while: ¡join usa una combinación interna! Entonces, usar: include puede resolver el problema de solo lectura, ¡pero el resultado puede ser incorrecto!

Actualmente estoy usando Rails 2.3.9. Entiendo que al especificar la opción :joins en una consulta sin un explícito :select hace automáticamente que cualquier registro devuelto sea de solo lectura. Tengo una situación en la que me gustaría actualizar los registros y, aunque he leído sobre diferentes formas de abordarlo, me preguntaba cuál es la forma preferida o "adecuada".

Específicamente, mi situación es que tengo el siguiente modelo de User con un alcance con nombre active que realiza un JOIN con la tabla de subscriptions :

class User < ActiveRecord::Base has_one :subscription named_scope :active, :conditions => { :subscriptions => { :status => ''active'' } }, :joins => :subscription end

Cuando llamo User.active.all , los registros del usuario que se devuelven son todos de solo lectura, así que si, por ejemplo, ¡llamo update_attributes! en un usuario, se generará ActiveRecord::ReadOnlyRecord .

A través de la lectura de varias fuentes, parece una forma popular de evitar esto agregando :readonly => false a la consulta. Sin embargo, me preguntaba lo siguiente:

  • ¿Esto es seguro? Entiendo que la razón por la que Rails lo configura para que sea de lectura, en primer lugar, es porque, de acuerdo con la documentación de Rails , "tendrán atributos que no se corresponden con las columnas de la tabla". Sin embargo, la consulta SQL que se genera a partir de esta llamada usa SELECT `users`.* todos modos, lo que parece ser seguro, entonces , ¿qué es lo que trata de evitar Rails en primer lugar? Parecería que Rails debería protegerse contra el caso cuando :select está realmente explícitamente especificado, que es el inverso del comportamiento real, entonces ¿no entiendo correctamente el propósito de establecer automáticamente el indicador de solo lectura en :joins ?
  • ¿Esto parece un truco? No parece correcto que la definición de un alcance nombrado deba preocuparse por establecer explícitamente :readonly => false . También tengo miedo de los efectos secundarios si el alcance del nombre está encadenado con otros ámbitos nombrados. Si trato de especificarlo fuera del alcance (por ejemplo, haciendo User.active.scoped(:readonly => false) o User.scoped(:readonly => false).active ), parece que no funciona.

Otra forma que he leído para solucionar esto es cambiar el :joins a an :include . Entiendo mejor el comportamiento de esto, pero ¿hay alguna desventaja en esto (aparte de la lectura innecesaria de todas las columnas en la tabla de subscriptions )?

Por último, también pude recuperar la consulta utilizando los ID de registro llamando a User.find_all_by_id(User.active.map(&:id)) , pero considero que se trata más de una solución alternativa que de una posible solución, ya que genera una consulta SQL adicional.

¿Hay alguna otra solución posible? ¿Cuál sería la solución preferida en esta situación? He leído la respuesta dada en la pregunta previa de StackOverflow sobre esto , pero no parece dar una guía específica de lo que se consideraría correcto.

¡Gracias por adelantado!


Creo que sería habitual y aceptable en este caso usar :include lugar de :join . Creo que :join solo se usa en circunstancias especiales poco frecuentes, mientras que :include es bastante común.

Si no va a actualizar a todos los usuarios activos, probablemente sea conveniente agregar un alcance nombrado adicional o una condición de búsqueda para restringir aún más los usuarios que está cargando, de modo que no esté cargando innecesarios usuarios y suscripciones innecesariamente. . Por ejemplo...

User.active.some_further_limiting_scope(:with_an_argument) #or User.active.find(:all, :conditions => {:etc => ''etc''})

Si decide que todavía desea usar el :join , y solo actualizará un pequeño porcentaje de los usuarios cargados, entonces probablemente sea mejor volver a cargar solo el usuario que desea actualizar justo antes de hacerlo. Como...

readonly_users = User.active # insert some other code that picks out a particular user to update User.find(readonly_users[@index].id).update_attributes(:etc => ''etc'')

Si realmente necesita cargar todos los usuarios activos, y desea seguir con :join , y es probable que esté actualizando la mayoría o la totalidad de los usuarios, entonces su idea de volver a cargarlos con una serie de ID es probablemente su mejor opción. .

#no need to do find_all_by_id in this case. A simple find() is sufficient. writable_users_without_subscriptions = User.find(Users.active.map(&:id))

Espero que eso ayude. Tengo curiosidad por saber qué opción elegir, o si encontraste otra solución más adecuada para tu situación.


En relación con su subpregunta: ¿no entiendo correctamente el propósito de establecer automáticamente la bandera de solo lectura en: uniones?

Creo que la respuesta es: con una consulta de uniones, obtendrá un solo registro con los atributos de la tabla Usuario + Suscripción. Si trató de actualizar uno de los atributos (digamos "subscription_num") en la tabla de Suscripción en lugar de la tabla de Usuario, la declaración de actualización a la tabla de Usuario no podrá encontrar el número de suscripción y se bloqueará. Por lo tanto, los ámbitos de unión son de solo lectura por defecto para evitar que eso suceda.

Referencia: 1) http://blog.ethanvizitei.com/2009/05/joins-and-namedscopes-in-activerecord.html


Me encontré con este mismo problema y no me sentía cómodo usando :readonly => false

Como resultado hice un selecto explícito a saber :select => ''users.*'' Y sentí que parecía menos de un hack.

Podría considerar hacer lo siguiente:

class User < ActiveRecord::Base has_one :subscription named_scope :active, :select => ''users.*'', :conditions => { :subscriptions => { :status => ''active'' } }, :joins => :subscription end