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])
Другие вопросы по тегам