Rails 4 [Лучшие практики] Вложенные ресурсы и мелкие: правда
Следующий пост основан на Rails 4.
Я на самом деле ищу хорошие передовые практики для нескольких вложенных ресурсов (более 1), и вариант мелкой: true.
Сначала на моих маршрутах произошло следующее:
resources :projects do
resources :collections
end
Маршруты связаны:
project_collections GET /projects/:project_id/collections(.:format) collections#index
POST /projects/:project_id/collections(.:format) collections#create
new_project_collection GET /projects/:project_id/collections/new(.:format) collections#new
edit_project_collection GET /projects/:project_id/collections/:id/edit(.:format) collections#edit
project_collection GET /projects/:project_id/collections/:id(.:format) collections#show
PATCH /projects/:project_id/collections/:id(.:format) collections#update
PUT /projects/:project_id/collections/:id(.:format) collections#update
DELETE /projects/:project_id/collections/:id(.:format) collections#destroy
projects GET /projects(.:format) projects#index
POST /projects(.:format) projects#create
new_project GET /projects/new(.:format) projects#new
edit_project GET /projects/:id/edit(.:format) projects#edit
project GET /projects/:id(.:format) projects#show
PATCH /projects/:id(.:format) projects#update
PUT /projects/:id(.:format) projects#update
DELETE /projects/:id(.:format) projects#destroy
Я прочитал в документации об ограничении вложенных ресурсов:
Ресурсы никогда не должны быть вложены глубже, чем на 1 уровень.
Источник: http://guides.rubyonrails.org/routing.html Ok. Затем, как сказано в документации, я буду использовать "мелкие" в моих маршрутах.
shallow do
resources :projects do
resources :collections
end
end
Маршруты связаны:
project_collections GET /projects/:project_id/collections(.:format) collections#index
POST /projects/:project_id/collections(.:format) collections#create
new_project_collection GET /projects/:project_id/collections/new(.:format) collections#new
edit_collection GET /collections/:id/edit(.:format) collections#edit
collection GET /collections/:id(.:format) collections#show
PATCH /collections/:id(.:format) collections#update
PUT /collections/:id(.:format) collections#update
DELETE /collections/:id(.:format) collections#destroy
projects GET /projects(.:format) projects#index
POST /projects(.:format) projects#create
new_project GET /projects/new(.:format) projects#new
edit_project GET /projects/:id/edit(.:format) projects#edit
project GET /projects/:id(.:format) projects#show
PATCH /projects/:id(.:format) projects#update
PUT /projects/:id(.:format) projects#update
DELETE /projects/:id(.:format) projects#destroy
Основное различие, которое я вижу, - это "показ" коллекций, этот:
collection GET /collections/:id(.:format) collections#show
Так что, если я прав, ссылка на действие шоу для коллекции:
<%= link_to 'Show", collection_path(collection)%>
и должен вернуть что-то вроде этого: " http://example.com/collections/1"
НО! 2 вещи:
- Это не работает. Я получаю вместо " http://example.com/projects/1". WTF?
- Даже если это работает, это на самом деле довольно плохо, потому что я теряю основную часть REST, которая говорит: "Коллекция - это потомок проекта, тогда URL должен быть" localhost / project / 1 / collection / 1 ".
Я не понимаю, в чем состоит интерес мелководья, если он теряет большое преимущество действий Отдыха. Какой интерес? И какой интерес потерять действие "Шоу"? Я уже разместил это на SO, но единственный комментарий, который я получил, это "Это что-то нормальное". WTF? В чем заключается нормальное поведение "удалить" действие из остальных API?
Я воспроизвел проблему на нейтральном проекте, чтобы быть уверенным, что я не делал что-то не так, и такая же проблема произошла. Так что, да, помощникам может быть удобно использовать поверхностный доступ, но в остальном это совсем НЕ удобно, вы теряете интерес "одна коллекция вложена в один проект, так что это отражается в URL".
Я не знаю, есть ли другой способ сделать это, это правда, что мелкие допускают большую гибкость в отношении помощников, но неверно, что это соответствует требованиям покоя. Таким образом, есть ли шанс заставить "помощников" работать (довольно здорово иметь "nested3_path(collection)" вместо "nested1_nested2_nested3([nested1.nested2.nested3, nested1.nested2, nested1])" и сохранять " URL часть "и продолжать иметь"nested1/123/nested2/456/nested3/789?
Спасибо!
5 ответов
Я не верю, что Rails предлагает какой-либо встроенный способ, чтобы URL-адреса использовали полную иерархию (например, /projects/1/collections/2
), но также есть помощники по ярлыкам (например, collection_path
вместо project_collection_path
).
Если вы действительно хотите сделать это, вы можете развернуть свой собственный помощник, как показано ниже:
def collection_path(collection)
# every collection record should have a reference to its parent project
project_collection_path(collection.project, collection)
end
Но это было бы довольно обременительно делать вручную для каждого ресурса.
Я думаю, что идея использования shallow
Маршруты лучше всего обозначать документацией:
Один из способов избежать глубокого вложения (как рекомендовано выше) состоит в том, чтобы генерировать действия коллекции, ограниченные родительским элементом, чтобы получить представление об иерархии, но не вкладывать действия члена. Другими словами, строить только маршруты с минимальным количеством информации, чтобы однозначно идентифицировать ресурс
источник: http://guides.rubyonrails.org/routing.html
Таким образом, хотя это может не соответствовать REST (как вы говорите), вы не теряете никакой информации, поскольку каждый ресурс может быть уникально идентифицирован, и вы можете вернуться к иерархии, если ваши ассоциации настроены правильно.
Так как есть id
для Collection
излишне вкладывать маршрут в проект, за исключением index
а также create
действия.
Есть правило относительно URL, где должен быть только один URL для GET (с 200) данного ресурса, если есть другие URL, вы должны перенаправить на него. Таким образом, у вас может быть маршрут /projects/:id/collections/:collection_id
который перенаправляет на /collections/:collection_id
,
В вашем случае коллекция привязана к проекту, но это не обязательно верно для всех отношений. Когда у вас есть :collection_id
вам не нужно ссылаться на контекст Project
чтобы получить к нему доступ.
Хотя это может усложнить ситуацию, если вам нужно это только для некоторых моделей, было бы неплохо проверить Inherited Resources (IR). Он поддерживает вложение ресурсов, относится к разным полиморфным типам и может автоматически генерировать методы поиска более короткого пути и URL-адреса, которые вы ищете. Причина, по которой вы больше не слышите об IR, заключается в том, что его первоначальный автор и некоторые другие разработчики несколько отказались от него из-за сложностей, возникающих при попытке расширить ваши контроллеры. Тем не менее, у него все еще есть сообщество, и мы постарались расширить его немного больше и сосредоточиться на простоте расширения контроллеров с Irie.
"Лучшая практика" в Rails зависит от того, с кем вы общаетесь.
Rails традиционно был нацелен в основном на базовый CRUD для (не вложенных) ресурсов. Да, он позволяет извлекать и обновлять вложенные ресурсы, но предполагается, что это происходит не так часто.
Однако в сообществе Rails появляется подход ActiveModel:: Serializers/ json-api. При этом, как правило, происходит не более одного уровня вложенности ресурсов, а вложенный ресурс представляет собой либо список ссылок, либо небольшую версию дочерних ресурсов с боковой загрузкой, которую вы затем можете запросить на этом ресурсе, чтобы получить больше данных. Это также было принято Ember/ Ember Data.
Есть также рев и ряд других проектов, которые направлены на реализацию чего-то ближе к их пониманию чего-то близкого к первоначальному видению Роя Филдинга о REST.
Я думаю, это зависит от того, какой у вас дизайн и что вам нужно. Если эффективность является целью, то дополнительное время для разработки, чтобы быть явным и вкладывать больше, может окупиться. В настоящее время мы используем, например, AngularJS и Irie. Но каждому свое.
Как последнее замечание, обязательно избегайте n+1 поисков через использование includes(...)
(или аналогичные) в ваших запросах, в противном случае все это вложение может снизить производительность.
Уровни
Понятие о том, что вы должны использовать только 1 уровень в своих вложенных ресурсах, действительно применимо только к дизайну системы:
Соответствующим помощником маршрута будет publisher_magazine_photo_url, требующий указания объектов на всех трех уровнях. Действительно, эта ситуация достаточно запутанная, и популярная статья Джамиса Бака предлагает практическое правило для хорошего дизайна Rails:
Я считаю, что Rails все еще может обрабатывать несколько уровней, хотя это не рекомендуется с точки зрения удобства использования
Мелкий
Несмотря на то, что я видел мелко использованный ранее, я никогда не использовал его сам
Из-за того, что я смотрю на документацию, кажется, что мелководье имеет довольно неясную цель (на самом деле я не знаю, почему это так). Проблема в том, что вы публично не передаете post_id
параметр вашего контроллера, оставляя вам загружать collection
без важного параметра
Я бы предположил (и это всего лишь предположение), что цель состоит в том, чтобы передать параметр, который вам нужен, за кулисами, так что вы остаетесь с публичным "мелким" маршрутом:
#config/routes.rb
resources :projects do
resources :collections, shallow: true
end
Я полагаю, вы получите URL-помощник, как это:
collection_path(project.id, collection.id)
Это выйдет как domain.com/collection/2
Из этого ответа кажется, что мелкие маршруты несколько противоречат соглашению Rails, IMO.
Я думаю, вам не понадобится явный помощник пути для показа маршрута. Помощник link_to должен иметь возможность выводить его из метода to_param объекта.
#your helper becomes
link_to "show", collection
Если вы используете хелпер по-своему, как и выше, вам, вероятно, потребуется передать вложенный идентификатор родительского ресурса хелперу.
link_to "show", collection_path([project, collection])