2

当我查看 Rails 控制器的示例时,我通常会看到如下内容:

class WidgetController < ActionController::Base

  def new
    @widget = Widget.new
  end

  def create
    @widget = Widget.new(params[:id])
    if @widget.save
      redirect_to @widget
    else
      render 'new'
    end
  end
end

这可行,但有几个问题:

路线

如果我将小部件添加到我的 routes.rb 文件中:

Example::Application.routes.draw do
  resources :widgets
end

GET /widgets/newwill route tonewPOST /widgetswill route to create

如果用户在新的小部件页面上输入错误信息并提交,他们的浏览器将显示带有 的 URL /widgets,但会呈现新模板。如果用户为页面添加书签并稍后返回或刷新页面,则将调用索引操作而不是新操作,这不是用户所期望的。如果没有索引操作或用户无权查看它,则响应将是 404。

代码重复

作为一个人为的例子,假设我的新方法中有一些棘手的逻辑:

def new
  @widget = Widget.new
  do_something_tricky()
end

使用当前的方法,我会在newand中复制该逻辑create。我可以调用newfrom create,但是我必须修改new以检查是否@widget已定义:

def new
  @widget ||= Widget.new
  do_something_tricky()
end

另外,这感觉不对,因为它降低了控制器操作的正交性。

该怎么办?

那么解决这个问题的Rails 方法是什么?我应该重定向到new而不是呈现新模板吗?我应该打电话new到里面create吗?我应该忍受它吗?有没有更好的办法?

4

5 回答 5

1

一般来说,我认为Rails解决问题的方法是将棘手的方法放在模型上或作为辅助方法,因此控制器保持“瘦”,您不必确保为两者添加自定义行为#new#create

编辑:为了进一步阅读,我推荐“Rails AntiPatterns”一书,因为它们经历了很多这些常见的设计问题并提供了潜在的解决方案。

于 2013-03-04T01:29:10.880 回答
1

我不认为这是“轨道方式”中的问题,并且没有内置功能可以在不弄脏您的手的情况下允许这样做。用户在为刚刚提交并出现错误的表单添加书签时期望什么?用户不知道更好,他们不应该为失败的表单添加书签。

我认为重定向到new_widget_path是最干净的解决方案。但是,您应该保留错误并将它们显示在表单上。为此,我建议您将参数保留在会话中(我希望它小于序列化的 Widget 对象)。

def new
  @widget = widget_from_session || Widget.new 
end

def widget_from_session
  Widget.new(session.delete(:widget_params)) if session[:widget_params].present?
end
private :widget_from_session

# Before the redirect
session[:widget_params] =  params

代码是不言自明的,Widget.new只会在widget_from_session返回 nil 时调用,这是 session[:widget_params] 存在的时候。调用delete哈希将返回删除的值并将其从原始哈希中删除。

更新选项 2 使用 ajax 提交表单怎么样?您的控制器可以受益于:

  respond_to :html, :json

  ...

  def create
    @widget = Widget.new params[:widget]
    @widget
    respond_with @widget, location: nil
  end

根据响应代码(由 Rails 设置:201 Created 或 422 Unprocessable Entity),您可以显示错误(验证失败时在响应正文中可用)或将用户重定向到 @widget

这就是 StackOverflow 的做法:https ://stackoverflow.com/questions/ask 。他们异步提交表单。

于 2013-03-12T02:38:13.553 回答
1

您放入do_something_tricky()自己的方法并在 create 操作中调用它(但仅在您呈现新模板时,即验证失败时)。

至于书签问题,我不知道防止这种情况的好方法,而是修改路由并将创建操作设置为新操作,但使用POST

get '/users/new' => 'users#new'
post '/users/new' => 'users#create'

更新:使用resources

resources :platos, except: :create do
  post '/new' => 'plates#create', on: :collection, as: :create
end

然后你可以create_platos_path在你的表格中使用

于 2013-03-04T01:24:20.413 回答
0

您不需要在两个 action 中编写相同的函数,而是使用 before_filter 。

如果您想在错误提交后拥有“widget_new_url”,那么在您的表单中添加新小部件路径的 url,例如 :url => widget_new_path 。

Rails 从 Form 获取 url。

于 2013-03-08T14:35:58.640 回答
0

我以前遇到过这个问题,所以我改用编辑操作。

这是我的代码。

路线:

resources :wines do
  collection do
    get :create_wine, as: :create_wine
  end
end

控制器:

def create_wine
  @wine = Wine.find_uncomplete_or_create_without_validation(current_user)
  redirect_to edit_wine_path(@wine)
end

def edit
  @wine = Wine.find(params[:id])
end

def update
  @wine = Wine.find(params[:id])
  if @wine.update_attributes(params[:wine])
    redirect_to @wine, notice: "#{@wine.name} updated"
  else
    render :edit
  end
end

模型:

def self.find_uncomplete_or_create_without_validation(user)
  wine = user.wines.uncomplete.first || self.create_without_validation(user)
end

def self.create_without_validation(user)
  wine = user.wines.build
  wine.save(validate: false)
  wine
end

看法:

= simple_form_for @wine, html: { class: 'form-horizontal' } do |f|
  = f.input :complete, as: :hidden, input_html: { value: 'true' }

我所做的是使用 get 操作创建一个新操作“create_wine”。

  1. 如果用户请求“create_wine”,它将在没有验证的情况下创建一个新酒,并重定向到编辑操作,其中包含属性更新表单和 compele 隐藏字段。
  2. 如果用户之前创建过但放弃保存酒,它将返回最后一个未完成的酒。

这意味着无论是否使用保存,url 都将与/wines/:id 相同。

不太适合 RESTful 设计,但可以解决我的问题。如果有更好的解决方案请告诉我。

于 2013-03-11T07:38:36.783 回答