如何在 rails 控制器中动态构建redirect_to路径



我有许多模型可以在应用程序的多个区域中进行编辑。我试图避免在更新/创建/销毁控制器方法中使用大量 if/else 语句来确定正确的"成功"重定向路径。我也希望我能把它抽象得足够多,我可以把它放在application_controller中,并在任何需要的地方使用它。

作为一个简化的例子,我有这个ER模型:

class FinancialStatement < ApplicationRecord
belongs_to :company
belongs_to :financial_year
has_many :budgets
has_many :revenue_projections, through: :budgets
end
class Budget < ApplicationRecord
belongs_to :financial_statement
belongs_to :division
has_many :revenue_projections, dependent: :destroy
end
class RevenueProjection < ApplicationRecord
belongs_to :potential_debtor, class_name: "Client", optional: true
belongs_to :budget
has_one :tenant, through: :budget
has_one :division, through: :budget
end

用户可以根据用例和访问级别等,从财务报表或预算下对收入投影对象执行部分/全部 CRUD 操作(我什至可能希望财务报表下的预算相同)

这些是路线的相关部分:

resources :financial_statements do
resources :revenue_projections do
end
end
resources :budgets do
resources :revenue_projections do
end
end

所以我有一个半解决方案,它大部分有效,并且会更清楚地显示我想要发生的事情。这是revenue_projections_controller.rb中的update方法:

def update
respond_to do |format|
if @revenue_projection.update(revenue_projection_params)
format.html { redirect_to cud_redirect_path(@ancestors, @revenue_projection), notice: action_success_message }
format.json { render json: { status: :ok, location: @revenue_projection }}
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: { errors: @revenue_projection.errors, status: :unprocessable_entity }}
end
end
end

@ancestors是从 URL 参数的其他地方设置的父对象数组,在这种情况下只有一代深,但我确实有多达 3 级嵌套的用例(故意但希望不会误导违反关于嵌套路线的传统智慧)。我对cud_redirect_path方法签名感到满意,它的定义如下所示。

def cud_redirect_path(ancestors, object = nil)
path_extension = ancestors.blank? ? '' : (ancestors.map { |anc| anc.class.name.underscore }.join('_') + '_')
if object
send("#{path_extension}#{controller_name}_path", ancestors, object)
else
send("#{path_extension}#{controller_name.pluralize}_path", ancestors)
end
end

if object分支不做我想做的,基本上忽略了要发送的最后一个参数。 我尝试传递(ancestors + [object])它很接近,但在 URL 中输入了"."而不是"/"。我让它工作的唯一方法是手动逐个元素布局数组元素,这意味着根据祖先数组的长度进行分支并违反 DRY 原则。我试图找到一个 ** 等效运算符,该运算符可以处理数组参数,但不能。如果这是Common Lisp,我会使用#'apply。 如果 Rails 具有所有寻路魔力,但不能提供一种好的方式来接受可变长度的父项列表加上一个对象并返回正确的路径,我会感到惊讶,它还没有让我失望! 我有一种唠叨的感觉,我也在某处见过......

提前感谢!

[更新] ChatGPT为我提供了这个:

def cud_redirect_path(ancestors, object = nil)
path_extension = ancestors.blank? ? '' : (ancestors.map { |anc| anc.class.name.underscore }.join('_') + '_')
if object
send("#{path_extension}#{controller_name}_path", *ancestors, object)
else
send("#{path_extension}#{controller_name.pluralize}_path", *ancestors)
end
end

这让我抱有希望,但遗憾的是没有任何不同。

这里不需要重新发明轮子。多态路由帮助程序已经为您处理此问题。

举这个疯狂的例子:

resources :blogs do
resources :categories do
resources :posts # Much nesting, such wow!
end 
end
@ancestors = [Blog.find(1), Category.find(2)]
@record = Post.find(3)

只要您的路由遵循 Rails 约定,您就可以简单地传递一个包含所有记录的数组:

redirect_to [*@ancestors, @record]
# shorthand for 
redirect_to(
polymophic_path([Blog.find(1), Category.find(2), Post.find(3)]
)

这将调用:

blog_category_post_path(blog_id: 1, category_id: 2, id: 3)

并导致路径/blogs/1/categories/2/posts/3.

它通过使用ActiveModel::Naming API来完成所有工作,该 包含从类名派生路径帮助程序名称的方法。

如果要重定向到索引而不是特定记录,可以执行以下操作:

redirect_to [*@ancestors, @record.class]

这将导致路径/blogs/1/categories/2/posts.

因此,为了回答我自己的问题,解决问题的方法定义是:

def cud_redirect_path(ancestors, object = nil)
path_extension = ancestors.blank? ? '' : (ancestors.map { |anc| anc.class.name.underscore }.join('_') + '_')
if object
send("#{path_extension}#{controller_name.singularize}_path", *ancestors, object)
else
send("#{path_extension}#{controller_name}_path", *ancestors)
end
end

请注意,需要在if object分支中单数化controller_name,它已经为else复数化了。

最新更新