Rails 设计模式:表单对象
形式对象模式
消息传递功能
形式对象模式
有很多设计模式可以帮助你改进 Rails 应用。表单对象就是其中之一。表单对象本质上就是一个普通的 Ruby 对象 (PORO),但我们可以对其进行设计,使其有助于我们在代码中保持 API 的一致性。接下来,我们将深入探讨如何利用表单对象来改进我们的 Rails 应用。
消息传递功能
假设我们的老板要求我们在应用程序中添加一个消息功能。他们告诉我们消息不应该是一个数据库表,所以不需要模型。它只需要一个表单,用户可以在其中选择另一个用户,添加主题和正文,然后发送电子邮件。听起来很简单,我们来添加它。(为了简单起见,假设我们的应用程序已经有一个User包含`<user>` first_name、 `<name> last_name` 和 ` <body> email` 属性的模型,以及一个接受`<user>` 、 `<name> ` 和`<body>`作为关键字参数的DummyMailer方法。)messagetosubjectbody
首先我们来添加消息控制器。
# app/controllers/messages_controller.rb
class MessagesController < ApplicationController
def show; end
def create
DummyMailer.message(message_params).deliver_later
redirect_to messages_path, notice: "Your message was sent!"
end
private
def message_params
params.permit(:to, :subject, :body)
end
end
然后我们就可以添加视图/表单了。
# app/views/messages/show.html.erb
<h1>New Message</h1>
<h3><%= notice %></h3>
<%= form_with url: messages_path do |form| %>
<div class="field">
<%= form.label :to %>
<%= form.select :to, User.all.pluck(:email), include_blank: "Select a recipient" %>
</div>
<div class="field">
<%= form.label :subject %>
<%= form.text_field :subject %>
</div>
<div class="field">
<%= form.label :body %>
<%= form.text_area :body %>
</div>
<div class="actions">
<%= form.submit "Create Message" %>
</div>
<% end %>
在路由文件中添加内容后,resource :messages, only: %i[show create]我们可以启动服务器并查看我们的新表单(请原谅样式不够美观)。
如果我们选择一个用户,添加主题和正文。
我们会发现事情最终会进展得很顺利。
差一点就成功了。
直到我们把表单拿给老板看。她做的第一件事就是尝试提交一个没有任何信息的表单,当屏幕上显示“您的消息已发送!”时,她皱起了眉头。她要求我们在缺少任何信息时显示错误信息。好吧,这稍微复杂了一点,但仍然可以实现。让我们更新一下控制器。
例如:
class MessagesController < ApplicationController
def show; end
def create
if message_params[:to].present? && message_params[:subject].present? && message_params[:body].present?
DummyMailer.message(message_params).deliver_later
redirect_to messages_path, notice: "Your message was sent!"
else
flash.now[:notice] = "Please include all fields"
render :show
end
end
private
def message_params
params.permit(:to, :subject, :body)
end
end
更多问题……
这样做虽然可行,但我们的老板几乎立刻发现了另一个问题。如果我们包含某些字段,这些字段会在表单重新渲染时被清空。老板还要求我们限制表单的长度subject,高亮显示出错的字段,并在提交无效表单时列出问题。哎呀!这比控制器应该包含的逻辑要多得多,但如果没有模型,我们应该把它放在哪里呢?嗯,放在某种模型里。你看,模型不必继承自 ActiveRecord,也不必由数据库支持。我们可以添加一个 PORO,包含相关ActiveModel::Model代码,然后就可以开始使用了。
形状对象来帮忙啦!
# app/models/message.rb
class Message
include ActiveModel::Model
attr_accessor :to, :subject, :body
validates :to, :subject, :body, presence: true
validates :subject, length: { maximum: 255 }
end
你会注意到我把它放在了这个models目录下。这纯粹是个人偏好。它完全可以放在其他forms目录下app,甚至更远的地方models。把“模型”(这可以说是模型)放在你的目录下完全没问题models。它们不一定都需要数据库支持。
有了新的表单对象,让我们来更新表单。
<h1>New Message</h1>
<h3><%= notice %></h3>
<%= form_with model: @message, url: messages_path do |form| %>
<div class="field">
<%= form.label :to %>
<%= form.select :to, User.all.pluck(:email), include_blank: "Select a recipient" %>
</div>
<div class="field">
<%= form.label :subject %>
<%= form.text_field :subject %>
</div>
<div class="field">
<%= form.label :body %>
<%= form.text_area :body %>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
请注意,我们model: @message在form_with调用中添加了参数。这有助于我们实现 Rails 中所有预期的表单行为。此外,由于传入的模型会自动填充该标签,因此我们可以从提交操作中移除特定的“创建消息”标签。不过,要使其真正生效,我们需要对控制器进行一些更新。
class MessagesController < ApplicationController
def show
@message = Message.new
end
def create
@message = Message.new(message_params)
if @message.valid?
DummyMailer.message(message_params).deliver_later
redirect_to messages_path, notice: "Your message was sent!"
else
flash.now[:notice] = @message.errors.full_messages.join(", ")
render :show
end
end
private
def message_params
params.require(:message).permit(:to, :subject, :body)
end
end
不错。这看起来更像我们在 Rails 应用中期望看到的控制器了。我们正确地利用了强参数,并让“模型”处理所有的验证逻辑。无需其他任何更改,我们的表单现在就能按照老板的要求正常工作了:
附加题:制作一款意想不到的控制器。
我们的控制器中有一小部分可能不太寻常。它对valid?`vs`的调用方式save与我们在“正常”Rails 控制器中看到的有所不同。另外,我也不太喜欢在控制器中调用邮件发送器(但这可能只是我个人的看法)。
class MessagesController < ApplicationController
def show
@message = Message.new
end
def create
@message = Message.new(message_params)
if @message.save
redirect_to messages_path, notice: "Your message was sent!"
else
flash.now[:notice] = @message.errors.full_messages.join(", ")
render :show
end
end
private
def message_params
params.require(:message).permit(:to, :subject, :body)
end
end
如果现在尝试这样做,你会收到一个“NoMethodError”错误,因为虽然ActiveModel::Model它提供了大量的模型逻辑,但并没有给出任何save消息。不过不用担心,只需save在模型中添加一个方法就能解决这个问题,而且还能为邮件发送调用提供一个合适的位置(如果你需要的话)。
class Message
include ActiveModel::Model
attr_accessor :to, :subject, :body
validates :to, :subject, :body, presence: true
validates :subject, length: { maximum: 255 }
def save
return false unless valid?
DummyMailer.message(to: to, subject: subject, body: body).deliver_later
end
end
如果我们现在再试一次,一切都会顺利的。🎉
虽然这只是一个相当简单的例子,但我希望它能充分展现表单对象的强大之处。让控制器以预期的方式呈现、运行和行为,对于保持应用程序的稳定性和易于升级/扩展至关重要,也是一项巨大的进步。此外,我们现在拥有了更加熟悉的表单设计。验证过程采用的是久经考验且广为人知的方法,无需重新编写逻辑。现在,对“消息”功能的任何额外更改都变得异常简单,其体验与向数据库支持的模型添加功能并无二致。
文章来源:https://dev.to/drbragg/rails-design-patterns-form-object-4d47




