我正在创建一个包含两组嵌套模型的 Rails 应用程序:
目标 -> 阶段 -> 模板
联系人 -> 跟踪器 -> 电子邮件
在我的目标的显示页面上,我可以列出与该目标相关的所有跟踪器并显示标题等信息,但我不知道如何链接到跟踪器。
我已经尝试了 link_to 的许多变体,但似乎没有任何效果,所以我真的很感激一些帮助。非常感谢!
配置Routes.rb:
Rails.application.routes.draw do
get 'pages/inbox'
get 'pages/trackers'
devise_for :users
resources :contacts do
resources :trackers do
resources :emails
end
end
resources :goals do
resources :stages do
resources :templates
end
end
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
# Defines the root path route ("/")
root "contacts#index"
end
我的目标控制器:
def show
@trackers = Tracker.all
@tracker = Tracker.all
end
我的目标模型:
class Goal < ApplicationRecord
has_many :stages , dependent: :destroy
has_many :trackers
end
我的追踪器型号:
class Tracker < ApplicationRecord
belongs_to :contact
belongs_to :goal
belongs_to :stage
has_many :emails , dependent: :destroy
end
这是 Goal 的 Show.html.erb 页面,其中包含错误的链接到 Tracker 的代码:
<% for tracker in @goal.trackers %>
<hr></hr>
<%= link_to goal_tracker_path(@goal, tracker) do %> #Doesn't work
<p><%= tracker.title %> | <%= tracker.contact.first_name %></p>
<% end %>
<% end %>
tracker
资源没有嵌套到goal
,而是嵌套到contacts
。路径goal_tracker_path (eg: /goals/1/trackers/1)
不存在。只有contact_tracker_path
存在。如果你运行rails routes
.,你可以看到这个
有几种方法可以解决这个问题。
/trackers/id
。的链接看起来像link_to 'Title', tracker
goal_tracker_path
和 contact_tracker_path
都将可用,尽管两者都将使用相同的 TrackersController
。如果需要,您总是可以编写另一个控制器并在路由中指定它。路线看起来像resources :contacts do
resources :trackers do
resources :emails
end
end
resources :goals do
resources :trackers
resources :stages do
resources :templates
end
end
contact_tracker_path
。最好总是看一下
rails routes
以确保路径存在。
跟踪器嵌套在合约下方。因此,你需要将跟踪器所属的合约也传递给
link_to
方法:
<% @goal.trackers.each do |tracker| %>
<hr />
<%= link_to goal_tracker_path(tracker.contact, tracker) do %>
<p><%= tracker.title %> | <%= tracker.contact.first_name %></p>
<% end %>
<% end %>
或者你可能想考虑不要在你的路由中使用太多的嵌套。官方 Rails 指南建议使用 Shallow Nesting 而不是嵌套路由。
您实际上没有在
trackers
中嵌套goals
资源的主要问题。
为此,您需要:
resources :contacts do
resources :trackers
# ...
end
resources :goals do
resources :trackers
# ...
end
这里
/contacts/1/trackers
和/goals/1/trackers
都会去TrackersContoller#index
方法。一种常见的处理方式是我所说的“参数嗅探法”:
class TrackersContoller
# GET /contacts/1/trackers
# GET /goals/1/trackers
# GET /trackers
def index
if params[:contact_id].present?
@trackers = Contact.find(params[:contact_id]).trackers
elsif params[:goal_id].present?
@trackers = Goal.find(params[:contact_id]).trackers
else
@trackers = Tracker.all
end
end
# ...
end
就单一职责原则而言,这真是太臭了,因为控制器现在正在处理 RESTful 资源的三种不同变体。
我通常更喜欢将资源的嵌套表示路由到它们自己的控制器:
resources :trackers # TrackerController
resources :contacts do
# routes to Contacts::TrackerContoller
resources :trackers, only: [:new, :create, :index],
module: :contacts
# ...
end
resources :goals do
# routes to Goals::TrackerContoller
resources :trackers, only: [:new, :create, :index],
module: :contacts
# ...
end
module Contacts
# Creates and lists trackers belonging to a Contact
class TrackersContoller < ApplicationController
# GET /contacts/1/trackers
def index
Contact.find(params[:contact_id]).trackers
end
end
end
module Goals
# Creates and lists trackers belonging to a Goal
class TrackersContoller < ApplicationController
# GET /goals/1/trackers
def index
Goal.find(params[:goal_id]).trackers
end
end
end
同样,在你疯狂地构建一个俄罗斯套娃方案之前,你可能应该阅读 Jamis Buck 的关于为什么深度嵌套是一件坏事的经典文章:
经验法则:资源的嵌套永远不应超过 1 层 深的。集合可能需要由其父级确定范围,但特定的 成员总是可以通过 id 直接访问,不需要 范围(除非 id 不是唯一的,出于某种原因)。
代码可能已经过时了,但基本原则与 15 年前一样重要。这也是 Rails 指南推荐的。