Rails 一个模型的多个更新表单的最佳实践
Rails best practices with multiple update forms for one model
我有一个关于模型更新的一般性问题,我想它与一个更大的问题有关,即如何以尽可能 'Rails-y' 的方式组织模型和控制器操作。对于给定模型 Profile
(及其关联),我有多个更新表单。例如,一种形式可能用于更新 first_name
、last_name
等基本信息,另一种形式可能用于更新 age
、jobs
等内容。在我的例子中,有这样的其中 8 种不同的形式,它们比我给出的例子要复杂一些。我想知道处理此设置的不同方式之间的权衡。过去我尝试过 3 种不同的方式:
1) 具有自定义控制器操作(在 Profiles 控制器中)来处理这些不同的更新表单中的每一个。例如
#views
<%= simple_form_for @profile, url: profile_update_name_path(@profile), method: :patch, remote: true do |f| %>
# the form fields
<% end %>
<%= simple_form_for @profile, url: profile_update_basics_path(@profile), method: :patch, remote: true do |f| %>
# other form fields
<% end %>
#profiles controller
def update_name
if @profile.update
# do some stuff
end
end
def update_basics
if @profile.update
# do some different stuff
end
end
2) 传递一个额外的参数作为表单 url 的一部分,以区分对表单的响应。例如
#views
<%= simple_form_for @profile, url: profile_path(update_form: “name-form”), method: :patch, remote: true do |f| %>
# the form fields
<% end %>
<%= simple_form_for @profile, url: profile_path(update_form: “basics-form”), method: :patch, remote: true do |f| %>
# the form fields
<% end %>
#profiles controller
def update
if params[:update_form] == "name-form"
if @profile.update
# do some stuff
else
# handle errors
end
elsif params[:update_form] == "basics-form"
if @profile.update
# do some different stuff
else
# handle different errors
end
end
end
3) 将模型分解为单独的更小的 classes,它们都通过 has_one、belongs_to 关系以某种方式连接到父模型 Profile 模型。例如
#profile.rb
has_one :name_information, dependent: :destroy
has_one :basic_information, dependent: :destroy
has_many :jobs, through: :basic_information
#name_information.rb
# has attributes: first_name, last_name
belongs_to :profile, touch: true
#basic_information.rb
# has attributes: age
belongs_to :profile, touch: true
has_many :jobs, dependent: :destroy
accepts_nested_attributes_for :jobs, allow_destroy: true
#views
# each form now points to the update action for it's own controller rather than using the profiles_controller
<%= simple_form_for [@profile, @name_information], url: profile_name_information_path(@profile, @name_information), method: :patch, remote: true do |f| %>
# the form fields
<% end %>
<%= simple_form_for [@profile, @name_information], url: profile_name_information_path(@profile, @name_information), method: :patch, remote: true do |f| %>
# the form fields
<% end %>
我使用所有这些技术都取得了一些成功,但老实说,我不确定它们中的任何一个都是很好的练习。有没有人对处理这种设置的最佳“Rails”方式有什么想法?将事物分解成更小的 classes 的第三种选择对我来说似乎是最好的,但它对于较大的应用程序也不太有吸引力,因为更改这些基本模型将对整个应用程序产生重大影响。这也让我想知道加载一堆更小的关联对象的效率,这些对象很容易成为一个 class.
的一部分
我猜对此有很多不同的看法。有些人可能会说将所有东西都放在一个模型和一个控制器中并完成它。但是,我认为您在考虑未来时说的是对的:
The third option of breaking things up into smaller classes seems like it might be best to me, but it is also less appealing for a larger application where changing these base models will have major repercussions throughout the entire app.
我还有一个问题你可以问自己:
- 这些表单会有不同的验证集吗?
- 如果遇到问题,哪种方法更容易调试?
如果他们这样做了,而您只有一个模型,您可能会发现自己在与一大组条件验证作斗争。可能不清楚哪些验证 运行 何时。
就个人而言,结合 1 和 3 的方法似乎不错。有开箱即用的好处。
- 每个表单都有自己的路由和允许的参数。如果您需要知道正在填写哪些表格,这将很有帮助。您还可以禁止 first_name 在基本信息表上更新。
- 拥有多个控制器操作似乎在 3 年后更容易理解。缺点是如果你不小心,可能很容易复制很多东西。
- 当您有多个控制器操作时,调试会更容易。如果有人遇到问题,您或许可以毫不费力地清楚地识别出他们正在使用的表单。
可能的解决方案
有一个模式我喜欢,它与您在第三个想法中描述的相似。您将保留 Profile
模型,但为每个表单使用一个表单对象。表单对象将存储特定于每个表单的验证。每个控制器操作将使用不同的表单对象来处理请求。
基本上,Profile
模型只能通过其中一个表单对象进行更改。
这是一个例子:
class Profile < ActiveRecord::Base
end
class BasicInformation
include ActiveModel::Model
attr_accessor :age
validates :age, presence: true
end
def ProfilesController < ApplicationController
before_action do
@profile = Profile.find(params[:id])
end
def update_name
name_params = params.require(:profile).permit(:first_name, :last_name)
if NameInformation.new(name_params).valid?
update_profile(name_params)
end
end
def update_basic_information
basic_information_params = params.require(:profile).permit(:age)
if BasicInformation.new(basic_information_params).valid?
update_profile(name_params)
end
end
private
def update_profile(params)
@profile.update(params)
end
end
我不确定我是否会使用这种确切的方式,但希望主要思想是清楚的。这也不是做事的唯一方法。就像我说的,人们对此会有不同的看法。这实际上取决于您需要处理的复杂程度。
将所有内容都放在一个模型和一个控制器中可能对您来说非常适合。
如果您希望阅读更多关于表单对象的内容,多年来我已经看到了很多处理表单对象的不同方法。这是一个列表:
我有一个关于模型更新的一般性问题,我想它与一个更大的问题有关,即如何以尽可能 'Rails-y' 的方式组织模型和控制器操作。对于给定模型 Profile
(及其关联),我有多个更新表单。例如,一种形式可能用于更新 first_name
、last_name
等基本信息,另一种形式可能用于更新 age
、jobs
等内容。在我的例子中,有这样的其中 8 种不同的形式,它们比我给出的例子要复杂一些。我想知道处理此设置的不同方式之间的权衡。过去我尝试过 3 种不同的方式:
1) 具有自定义控制器操作(在 Profiles 控制器中)来处理这些不同的更新表单中的每一个。例如
#views
<%= simple_form_for @profile, url: profile_update_name_path(@profile), method: :patch, remote: true do |f| %>
# the form fields
<% end %>
<%= simple_form_for @profile, url: profile_update_basics_path(@profile), method: :patch, remote: true do |f| %>
# other form fields
<% end %>
#profiles controller
def update_name
if @profile.update
# do some stuff
end
end
def update_basics
if @profile.update
# do some different stuff
end
end
2) 传递一个额外的参数作为表单 url 的一部分,以区分对表单的响应。例如
#views
<%= simple_form_for @profile, url: profile_path(update_form: “name-form”), method: :patch, remote: true do |f| %>
# the form fields
<% end %>
<%= simple_form_for @profile, url: profile_path(update_form: “basics-form”), method: :patch, remote: true do |f| %>
# the form fields
<% end %>
#profiles controller
def update
if params[:update_form] == "name-form"
if @profile.update
# do some stuff
else
# handle errors
end
elsif params[:update_form] == "basics-form"
if @profile.update
# do some different stuff
else
# handle different errors
end
end
end
3) 将模型分解为单独的更小的 classes,它们都通过 has_one、belongs_to 关系以某种方式连接到父模型 Profile 模型。例如
#profile.rb
has_one :name_information, dependent: :destroy
has_one :basic_information, dependent: :destroy
has_many :jobs, through: :basic_information
#name_information.rb
# has attributes: first_name, last_name
belongs_to :profile, touch: true
#basic_information.rb
# has attributes: age
belongs_to :profile, touch: true
has_many :jobs, dependent: :destroy
accepts_nested_attributes_for :jobs, allow_destroy: true
#views
# each form now points to the update action for it's own controller rather than using the profiles_controller
<%= simple_form_for [@profile, @name_information], url: profile_name_information_path(@profile, @name_information), method: :patch, remote: true do |f| %>
# the form fields
<% end %>
<%= simple_form_for [@profile, @name_information], url: profile_name_information_path(@profile, @name_information), method: :patch, remote: true do |f| %>
# the form fields
<% end %>
我使用所有这些技术都取得了一些成功,但老实说,我不确定它们中的任何一个都是很好的练习。有没有人对处理这种设置的最佳“Rails”方式有什么想法?将事物分解成更小的 classes 的第三种选择对我来说似乎是最好的,但它对于较大的应用程序也不太有吸引力,因为更改这些基本模型将对整个应用程序产生重大影响。这也让我想知道加载一堆更小的关联对象的效率,这些对象很容易成为一个 class.
的一部分我猜对此有很多不同的看法。有些人可能会说将所有东西都放在一个模型和一个控制器中并完成它。但是,我认为您在考虑未来时说的是对的:
The third option of breaking things up into smaller classes seems like it might be best to me, but it is also less appealing for a larger application where changing these base models will have major repercussions throughout the entire app.
我还有一个问题你可以问自己:
- 这些表单会有不同的验证集吗?
- 如果遇到问题,哪种方法更容易调试?
如果他们这样做了,而您只有一个模型,您可能会发现自己在与一大组条件验证作斗争。可能不清楚哪些验证 运行 何时。
就个人而言,结合 1 和 3 的方法似乎不错。有开箱即用的好处。
- 每个表单都有自己的路由和允许的参数。如果您需要知道正在填写哪些表格,这将很有帮助。您还可以禁止 first_name 在基本信息表上更新。
- 拥有多个控制器操作似乎在 3 年后更容易理解。缺点是如果你不小心,可能很容易复制很多东西。
- 当您有多个控制器操作时,调试会更容易。如果有人遇到问题,您或许可以毫不费力地清楚地识别出他们正在使用的表单。
可能的解决方案
有一个模式我喜欢,它与您在第三个想法中描述的相似。您将保留 Profile
模型,但为每个表单使用一个表单对象。表单对象将存储特定于每个表单的验证。每个控制器操作将使用不同的表单对象来处理请求。
基本上,Profile
模型只能通过其中一个表单对象进行更改。
这是一个例子:
class Profile < ActiveRecord::Base
end
class BasicInformation
include ActiveModel::Model
attr_accessor :age
validates :age, presence: true
end
def ProfilesController < ApplicationController
before_action do
@profile = Profile.find(params[:id])
end
def update_name
name_params = params.require(:profile).permit(:first_name, :last_name)
if NameInformation.new(name_params).valid?
update_profile(name_params)
end
end
def update_basic_information
basic_information_params = params.require(:profile).permit(:age)
if BasicInformation.new(basic_information_params).valid?
update_profile(name_params)
end
end
private
def update_profile(params)
@profile.update(params)
end
end
我不确定我是否会使用这种确切的方式,但希望主要思想是清楚的。这也不是做事的唯一方法。就像我说的,人们对此会有不同的看法。这实际上取决于您需要处理的复杂程度。
将所有内容都放在一个模型和一个控制器中可能对您来说非常适合。
如果您希望阅读更多关于表单对象的内容,多年来我已经看到了很多处理表单对象的不同方法。这是一个列表: