ruby-on-rails - polimorficas - validaciones rails
has_many: a través de una asociación has_and_belongs_to_many (4)
Estoy tratando de hacer lo siguiente en un proyecto de Ruby on Rails:
class FoodItem < ActiveRecord::Base
has_and_belongs_to_many :food_categories
has_many :places, :through => :food_categories
end
class FoodCategory < ActiveRecord::Base
has_and_belongs_to_many :food_items
belongs_to :place
end
class Place < ActiveRecord::Base
has_many :food_categories
has_many :food_items, :through => :food_category
end
Pero llamar al método de instancia some_food_item.places
me da el siguiente error:
ActiveRecord::StatementInvalid: PGError: ERROR: column
food_categories.food_item_id does not exist
LINE 1: ...laces".id = "food_categories".place_id WHERE (("food_cate...
: SELECT "places".* FROM "places" INNER JOIN "food_categories" ON "places".id = "food_categories".place_id WHERE (("food_categories".food_item_id = 1))
Lo cual tiene perfecto sentido: debido a las HABTM en FoodItem y FoodCategory, tengo la tabla de asignación llamada food_categories_food_items
.
¿Qué tengo que hacer para que some_food_item.places
busque correctamente lugares a través de la tabla de asignación en lugar de buscar un food_item_id
en la tabla food_categories
?
Esto es correcto, porque no puede realizar "tiene muchos pasos" en una tabla de unión. En esencia, estás tratando de extender la relación un grado más allá de lo que realmente puedes. HABTM (has_and_belongs_to_many) no es una solución muy robusta para la mayoría de los problemas.
En su caso, recomendaría agregar un modelo llamado FoodCategoryItem y cambiar el nombre de su tabla de combinación para que coincida. También necesitarás volver a agregar el campo de la clave principal. Luego configura tus asociaciones modelo así:
class FoodItem < ActiveRecord::Base
has_many :food_categories, :through => :food_category_items
has_many :places, :through => :food_categories
end
class FoodCategory < ActiveRecord::Base
has_many :food_items, :through => :food_category_items
belongs_to :place
end
class FoodCategoryItems < ActiveRecord::Base
belongs_to :food_item
belongs_to :food_category
end
class Place < ActiveRecord::Base
has_many :food_categories
has_many :food_items, :through => :food_categories
end
Tenga en cuenta que también arreglé un error tipográfico en "Lugar -> has_muy: elementos de comida". Esto debería ocuparse de lo que necesita y darle la ventaja adicional de poder agregar funcionalidad a su modelo de "unión" de FoodCategoryItems en el futuro.
Estoy usando Rails 3.2.13 y Rasmus, su configuración original ahora parece funcionar bien en una HABTM.
Sugiero que los usuarios prueben primero antes de intentar una solución alternativa.
Hace unos meses, escribí un artículo sobre esto . En resumen, has_many
través de una asociación has_and_belongs_to_many
no está permitida por Rails. Sin embargo, puedes simular parcialmente la relación haciendo algo como esto:
class FoodItem < ActiveRecord::Base
has_and_belongs_to_many :food_categories
named_scope :in_place, lambda{ |place|
{
:joins => :food_categories,
:conditions => {:food_categories => {:id => place.food_category_ids}},
:select => "DISTINCT `food_items`.*" # kill duplicates
}
}
end
class FoodCategory < ActiveRecord::Base
has_and_belongs_to_many :food_items
belongs_to :place
end
class Place
has_many :food_categories
def food_items
FoodItem.in_place(self)
end
end
Esto le dará el método some_food_item.places
que busca.
Mi primera versión de la respuesta fue incorrecta, pero esta funciona perfectamente. Hice un par de errores tipográficos la primera vez (el peligro de no crear realmente una aplicación para probar) pero esta vez lo verifiqué. Y se necesita un complemento, pero esto es fácil. primero, instala el plugin:
script/plugin install git://github.com/ianwhite/nested_has_many_through.git
Esto instala la solución provisional de Ian White, y funciona a la perfección. Ahora los modelos, copiados directamente de la aplicación de prueba, los configuro para que funcionen:
class FoodItem < ActiveRecord::Base
has_many :food_category_items
has_many :food_categories, :through => :food_category_items
has_many :places, :through => :food_categories
end
class FoodCategory < ActiveRecord::Base
has_many :food_category_items
has_many :food_items, :through => :food_category_items
belongs_to :place
end
class FoodCategoryItem < ActiveRecord::Base
belongs_to :food_item
belongs_to :food_category
end
class Place < ActiveRecord::Base
has_many :food_categories
has_many :food_category_items, :through => :food_categories
has_many :food_items, :through => :food_category_items
end
Ahora las asociaciones "lejanas" funcionan igual de bien. place_instance.food_items
y food_item.places
funcionan sin problemas, así como las asociaciones más simples involucradas. Solo como referencia, aquí está mi esquema para mostrar dónde van todas las claves foráneas:
create_table "food_categories", :force => true do |t|
t.string "name"
t.integer "place_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "food_category_items", :force => true do |t|
t.string "name"
t.integer "food_item_id"
t.integer "food_category_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "food_items", :force => true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "places", :force => true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
end
¡Espero que esto ayude!
ACTUALIZACIÓN: esta pregunta ha surgido algunas veces recientemente. Escribí un artículo, anidando tu has_many: a través de las relaciones , para explicar en detalle. Incluso tiene una aplicación de ejemplo acompañante en GitHub para descargar y jugar.