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, haciendoUser.active.scoped(:readonly => false)
oUser.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