has_many through es una pesadilla

En los primero tiempos rails tenia una idea acerca de las many to many: se ponia un “has_and_belongs_to_many” en ambas tablas y automaticamente te iba creando la de emparejamientos. El problema venia cuando querias etiquetar de alguna manera esos emparejamientos. Digamos tener un peso para cada pareja, o una etiqueta de posicion, o cosas asi. La solucion fue decir que si necesitabas algo mas complejo crearas explicitamente el modelo de la tabla de emparejamiento y cruzaras a traves de has_many through. Con eso abandonaban a su suerte el has_and_belongs dejandolo practicamente deprecado, pero no arreglaban de inmediato la cuestion de como te apañabas para añadir las etiquetas. Ya se sabe, convencion sobre configuracion, e improvisacion sobre convencion.

En gran parte de nuestro codigo, que venia de rail3, hemos acabado usando la tabla de parejas como un array, y olvidandonos del through:

has_many :item_solutions, -> {order "position ASC" }

Que no es mal sistema, sobre todo ahora que hay Enums, pero si no tienes todos los slots rellenados el order no te sirve de mucho y tienes que andar recargando los corchetes, usando acceso directo al association owner:

has_many :item_solutions  do 
    def [](pos)
          itemsol= find_by(position:pos) #find_or_create_by no se puede en este caso
          if itemsol.nil?
                    item=@association.owner.project.items.find_by(text:"")
                    itemsol=ItemSolution.create(:solution_id => @association.owner.id, :item_id => item.id, :position => pos)
          end
          return itemsol
    end
end

La solucion mas Railista parece que seria utilizar los scopes en la tabla de enlaces y pivotar con ellos, e incluso se puede hacer en el scope del through gracias a que el where tiene que mirar la tabla del join.  Pero mientras esto puede valer para dividir una tabla en tres o cuatro categorias, no esta demasiado claro como hacerlo de forma dinamica para el caso en que la etiqueta o el integer que queremos meter en el enlace no se conoce de antemano.

El mayor problema es inventarse el create. Para recoger el peso se pueden intentar bastantes trucos, volver a cruzar la tabla, meter un includes (y/o references) en la condicion (en el scope que se define al crear el has_many), cosas asi. La lambda que se suele usar para el scope permite incluso recibir como parametro el objeto original del que ha partido la query, y ahi se podria meter alguna variable que dijera a que position o label se quiere acceder (por cierto que en realidad si sabemos que la posicion ya nos da un elemento unico, es mas un has_one que un has_many ¿no?). De hecho dado que tenemos que definir tanto la join table como el through tenemos pues dos posibilidades para meter una condicion. Pero todo ello si funciona luego el create, claro.

Con scopes, podria uno primero intentar un scope con parameter en el join model, algo asi como scope :indice, (i) -> {where(position : i)}. Pero ¿cómo pasamos ese parametro al through?

Otro camino pausible seria jugar con el accepts_nested_attributes_for, pero eso me da la impresion de que aunque se defina en el modelo es algo mas para el controlador, y vielve a ser como el array, ignorar el through.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.