2011-07-12 6 views
5

Estoy buscando una solución al siguiente problema: Tengo una entidad ActiveRecord respaldada por una vista de base de datos actualizable (en DB2 a través del activerecord-jdbc -adapter gema). Esta vista contiene una columna que se calcula a partir de otras columnas y es 'de solo lectura': no ​​puede establecer esa columna de ninguna manera válida. Cuando se crea un nuevo registro para esta entidad, se debe establecer ese campo no. Sin embargo, de forma predeterminada, ActiveRecord lo establece con el 'predeterminado' (NULL), que es rechazado por la base de datos.Ignorar columna 'solo lectura' en creaciones y actualizaciones en Ruby ActiveRecord

attr_readonly no es una solución, porque eso solo excluye una columna de las actualizaciones y no de las creaciones.

attr_ignore, como implementado por la gema 'lincoln', tampoco es una solución, porque entonces el campo se ignora por completo. Sin embargo, la columna aún debe leerse y ser accesible. En realidad, incluso se usa como parte de una relación.

Hay maneras para evitar que pueda establecer un determinado atributo de una entidad ActiveRecord, pero eso no significa por lo general evitan ese atributo de ser incluido en crear o instrucciones de actualización

¿alguien sabe si hay una manera de ActiveRecord para especificar una columna como 'nunca configurar este campo'?

Actualización, en respuesta a Arsen7: He intentado utilizar el gancho after_initialize para eliminar el atributo de una entidad recién creada, por lo que no está incluido en el SQL creado. El problema con esto es que el atributo se elimina por completo y ya no está disponible, prácticamente idéntico a la situación 'igonre_attr' descrita anteriormente. Debido al almacenamiento en caché, no es trivial para moverse y requeriría una lógica adicional para forzar una recarga de entidades de estas tablas específicas. Eso probablemente se puede lograr anulando create para agregar una 'recarga', además de usar after_initialize.

(Como ha señalado Arsen7, se me olvidó mencionar que estoy en ActiveRecord 3.0.9)

Mi solución

Desde mis entidades ya heredan de una subclase de ActiveRecord::Base, no tengo optó por agregar ganchos before_create y after_create. En el gancho before_create, elimino las columnas 'calculadas' del @attributes de la instancia. En el gancho after_create, los vuelvo a agregar y leo los valores de las columnas 'calculadas' de la base de datos para establecerlos en los valores que recibieron.

La adición de tales ganchos es casi idéntica a la creación superior, por lo que considero que la respuesta de Arsen7 es correcta.

Respuesta

2

Me temo que ActiveRecord no está preparado para el caso de uso que necesita. (Por cierto: ¿qué versión de AR está utilizando?)

Pero creo que puede aplicar dos soluciones posibles.

El primero es sobrescribir el método 'crear' de su modelo, ejecutando otro SQL, preparado manualmente en el peor de los casos. Supongo que la función real que tendrá que sobrescribirse no será la 'creación' en sí misma, sino que mirando las fuentes se puede encontrar la.

La otra solución, y creo que más elegante, sería crear un disparador en la base de datos.Estoy más en el mundo de PostgreSQL, donde usaría un 'CREATE RULE', pero mirando la documentación de DB2 veo que en DB2 hay 'INSTEAD OF' triggers. Espero que esto pueda ser útil.

+0

Si está por volver a escribir 'crear', no sugiero retirar el atributo del objeto, sólo sugieren crear (y ejecutar) el SQL sin la columna especificada. Pero ¿qué hay de los factores desencadenantes? Parece la solución más elegante, su consulta será reescrita por la base de datos, allí puede eliminar esa columna de solo lectura de la consulta final. – Arsen7

+0

Me gusta la idea de los factores desencadenantes, pero la desventaja es que el campo calculado debe actualizarse cuando cambia uno de los campos 'fuente'. Esencialmente, el campo calculado hace lo mismo que múltiples disparadores en una cantidad de otros campos. Como tal, es algo más simple que múltiples factores desencadenantes. En cuanto a modificar 'crear' (y tal vez 'actualizar'): prefiero que lo anterior modifique el método que crea el SQL, porque este último está más profundamente en los tazones de ActiveRecord. Los cambios de nivel más altos son generalmente más fáciles de entender. Sin embargo, todavía tengo que hacerlo y puedo equivocarme al respecto :) – Confusion

2

que han logrado el mismo resultado reemplazando ActiveRecord :: Base # arel_attributes en mi modelo:

Class Model < ActiveRecord::Base 
    @@skip_attrs = [:attr1, :attr2]  

    def arel_attributes_values(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys) 
    skip_attrs = @@skip_attrs.map { |attr| [self.class.arel_table[attr] } 
    attrs = super(include_primary_key, include_readonly_attributes, attribute_names) 
    attrs.delete_if {|key, value| skip_attrs.include?(key) }   
    end 
end 

Los atributos de la matriz @@ skip_attrs serán ignorados por ActiveRecord en ambos estados de inserción y actualización, como se ambos confían en arel_attributes_values ​​para devolver la lista de atributos del modelo.

Una solución mejor sería: un parche en ActiveRecord :: Base # arel_attributes junto con una macro 'attr_ignore' similar a 'attr_readonly'.

aplausos

+0

Consideré esta opción y acepto que también resuelve el problema. La principal reserva que tengo sobre esto es que no me sorprendería si ese método se cambia en una versión futura de ActiveRecord, lo que podría conducir a un error difícil de encontrar. – Confusion

+0

Sí, tienes razón. A mediano plazo es mejor enviar una solicitud a los autores de AR para que consideren tal característica. El de aquí arriba es solo un 'parche', no más que eso. – evital

0

Sé que esto es muy antiguo, pero he estado luchando con esta misma cuestión. Tengo una base de datos con un disparador que calcula un valor de índice basado en el valor máximo dentro de una clave. Yo también quiero evitar cualquier posibilidad de establecer el valor en AR, ya que podría descartar el índice aplicado a medida que se insertan las filas.

CREATE TRIGGER incr_col_idx 
AFTER INSERT ON fl_format_columns 
FOR EACH ROW 
BEGIN UPDATE fl_format_columns 
SET idx = (SELECT coalesce(max(idx),-1) + 1 
      FROM fl_format_columns 
      WHERE fl_file_format_id = new.fl_file_format_id) 
WHERE fl_file_format_id = new.fl_file_format_id AND name = new.name; 
END; 

He intentado una variedad de cosas, pero siempre volví a anular el setter directamente.

# @raise ArgumentError when an attempt is made to set a value that is calculated in db 
def idx=(o) 
    raise ArgumentError,'the value of idx is set by the db. attempts to set value is not allowed.' unless o.nil? 
end 

Para ello será necesario agarrar la excepción en lugar de interrogar a la matriz errores, pero eso es lo que terminó con. No pasar la siguiente inspección:

context 'column index' do 
    it 'should prevent idx from being set' do 
    expect{FL_Format_Column.create(fl_file_format_id:-1,name:'test idx',idx:0)}.to raise_error(ArgumentError) 
    end 
    it 'should calculate idx relative to zero' do 
    x = FL_Format_Column.create(fl_file_format_id:-1,name:'test_idx_nil') 
    expect(x.errors[:idx].any?).to be false 
    expect(FL_Format_Column.last.idx).to be > -1 
    end 
end 
Cuestiones relacionadas