Rieles: conflicto de recursos anidados, cómo abarcar la acción del índice en función de la ruta llamada
-
06-07-2019 - |
Pregunta
Imagine que tiene dos rutas definidas:
map.resources articles
map.resources categories, :has_many => :articles
ambos accesibles por ayudantes / caminos
articles_path # /articles
category_articles_path(1) # /category/1/articles
si visita / articles
, se ejecuta la acción index
de ArticlesController
.
si visita / category / 1 / articles
, la acción index
de ArticlesController
también se ejecuta.
Entonces, ¿cuál es el mejor enfoque para seleccionar condicionalmente solo los artículos de alcance dependiendo de la ruta de llamada?
#if coming from the nested resource route
@articles = Articles.find_by_category_id(params[:category_id])
#else
@articles = Articles.all
Solución
Aquí tiene dos opciones, dependiendo de cuánto esté vinculada su lógica y su punto de vista al alcance. Déjame explicarte más.
La primera opción es determinar el alcance dentro de su controlador, como ya se explicó en las otras respuestas. Por lo general, configuro una variable @scope para obtener algunos beneficios adicionales en mis plantillas.
class Articles
before_filter :determine_scope
def index
@articles = @scope.all
# ...
end
protected
def determine_scope
@scope = if params[:category_id]
Category.find(params[:category_id]).articles
else
Article
end
end
end
El motivo de la variable @scope es que es posible que necesite conocer el alcance de su solicitud fuera de la acción individual. Supongamos que desea mostrar el número de registros en su vista. Debe saber si está filtrando por categoría o no. En este caso, simplemente debe llamar a @ scope.count
o @ scope.my_named_scope.count
en lugar de repetir cada vez que verifique los parámetros de [: category_id]
.
Este enfoque funciona bien si sus puntos de vista, el que tiene categoría y el que no tiene categoría, son bastante similares. Pero, ¿qué sucede cuando la lista filtrada por categoría es completamente diferente en comparación con la que no tiene una categoría? Esto sucede con bastante frecuencia: la sección de su categoría proporciona algunos widgets centrados en la categoría, mientras que la sección de su artículo contiene algunos widgets y filtros relacionados con el artículo. Además, su controlador de artículos tiene algunos antes_filtros especiales que puede usar, pero no tiene que usarlos cuando la lista de artículos pertenece a una categoría.
En este caso, es posible que desee separar las acciones.
map.resources articles
map.resources categories, :collection => { :articles => :get }
articles_path # /articles and ArticlesController#index
category_articles_path(1) # /category/1/articles and CategoriesController#articles
Ahora la lista filtrada por categoría es administrada por CategoriesController
y hereda todos los filtros, diseños, configuraciones del controlador ... mientras que la lista sin filtrar es administrada por ArticlesController
.
Esta suele ser mi opción favorita porque con una acción adicional no tiene que saturar sus vistas y controladores con toneladas de comprobaciones condicionales.
Otros consejos
A menudo me gusta separar esas acciones. Cuando las acciones resultantes son muy similares, puede separar fácilmente los ámbitos dentro del controlador al ver si los parámetros [: category_id] & nbsp; están presentes, etc. (consulte la respuesta de @SimoneCarletti).
Normalmente, la separación de acciones en el controlador mediante rutas personalizadas le brinda la mayor flexibilidad y resultados claros. El siguiente código da como resultado nombres de ayuda de ruta normales, pero las rutas se dirigen a acciones específicas en el controlador.
En routes.rb :
resources categories do
resources articles, :except => [:index] do
get :index, :on => :collection, :action => 'index_articles'
end
end
resources articles, :except => [:index] do
get :index, :on => :collection, :action => 'index_all'
end
Entonces puede tener en ArticlesController.rb
def index_all
@articles = @articles = Articles.all
render :index # or something else
end
def index_categories
@articles = Articles.find_by_category_id(params[:category_id])
render :index # or something else
end
Tener un solo recurso anidado, usar un condicional basado en los parámetros para determinar su alcance sería el enfoque más fácil. Este es probablemente el camino a seguir en su caso.
if params[:category_id]
@articles = Category.find(params[:category_id]).articles
else
@articles = Article.all
end
Sin embargo, dependiendo de qué otros recursos anidados tenga para el modelo, seguir este enfoque puede ser bastante tedioso. En cuyo caso, usar un complemento como resource_controller o make_resourceful hará esto mucho más simple.
class ArticlesController < ResourceController::Base
belongs_to :category
end
Esto realmente hará todo lo que esperarías. Le proporciona todas sus acciones RESTful estándar y configurará automáticamente el alcance de / categories / 1 / articles
.
if params[:category_id].blank?
# all
else
# find by category_id
end
Me gusta considerar la acción independiente de la ruta. No importa cómo lleguen allí, tome una decisión razonable sobre qué hacer.