如何为在数据库中存储为日期时间的复选框属性添加表单标签?
How to add form tag for a checbox attribute that is stored as a datetime in the database?
假设我有一个名为 advertisements
的 table。我需要为其添加一个字段,以便人们可以将其标记为 active
或 inactive
。我可以添加一个名为 inactive 的 boolean
字段,这是一种直接的方法。
还有另一种方法,我可以创建一个数据类型为 datetime
的字段 inactive
。这不仅告诉我广告是否 active/no,还告诉我广告何时停用。因此,如果该值为 null,则它处于活动状态,如果它是时间戳,则它在过去的那个时间被停用。
但是,问题在于将它添加到表单助手中。
对于复选框实现,Rails 建议如下:
= f.check_box :inactive, { class: 'something' }
这与数据库中的 boolean
数据类型配合得很好。
我如何使用 datetime
值使它工作?
谢谢!
尝试
= f.check_box :inactive, checked: form.object.inactive.present?, { class: 'something' }
Active Record 为模型中的每个属性提供 query methods,它们以属性名称以问号结尾(例如,对于 model.name
属性有 model.name?
)命名当属性为空时为 false,否则为 true。此外,在处理数值时,如果值为零,查询方法将 return false。
AR 查询方法将为您的 inactive?
标志提供 getter,您需要添加 setter 以允许保存对 inactive?
属性的更改。
跟踪停用时间
添加一列deactivated_at
datetime
add_column :advertisements, :deactivated_at, :datetime
形式
<%= form.text_field :deactivated_at, class: "add-datetimepicker" %>
在字段上添加日期时间选择器
假设我们有一个允许参数的方法
def advertisment_params
params.require(:advertisement).permit(:inactive)
end
通过将方法更改为
def advertisment_params
params[:advertisement][:inactive] = params[:advertisement][:inactive] == "0" ? nil : @advertisement.nil? ? DateTime.now.to_s(:db) : @advertisement.inactive
params.require(:advertisement).permit(:inactive)
end
会成功的。
回答我自己的问题。因此,使用 check_box
帮助程序,您可以指定何时应将复选框显示为 checked
,您还可以指定当有人选中该框时要分配的值。
我做到了
= f.check_box :inactive, { class: 'something', checked: f.object.inactive.present? }, f.object.inactive.nil? ? DateTime.now : f.object.inactive
最后一部分 DateTime.now
是用户选中复选框时分配的值。这样您就可以使用复选框实现来保存 DateTime 值。值的存在可帮助您确定在显示表单时是否应选中该复选框。
此方法的唯一注意事项是值的准确性。用户可能会在打开表单几次 mins/hours 后提交表单。如果捕获正确的值对您很重要,您可以使用 JS 来分配准确的值或在控制器中包含逻辑。
以下是我对实现在控制器级别完成的布尔参数到日期时间转换的支持的看法。这解决了 中的问题,其中由于日期时间是在编辑视图中而不是在 PUT/PATCH.
时生成的,时间戳已过时
代码被编写为与 Rails 6. YMMV 在其他版本上一起工作。
首先,您需要 table 上的时间戳列。我们称模型为 Resource
和 table resources
。在迁移中添加日期时间字段 inactive_at
。
def change
add_column :resources, :inactive_at, :datetime
end
我假设在控制器中,资源被实例化并分配给 @resource
。
在视图表单中,您需要在表单助手块中添加以下复选框行。
f.check_box :inactive, checked: @resource.inactive_at?
checked: @resource.inactive_at?
设置复选框的默认值。我们将使用参数 inactive
来承载是否更新 inactive_at
.
的布尔信息
在您的控制器中,您需要以下内容。为简洁起见,如果控制器正在继承父级 class、操作和其他参数的存在,我排除了详细信息。
class ResourceController
private
def resource_params
translate_inactive_param
params.require(:resource).permit(:inactive_at)
end
def translate_inactive_param
return if params[:resource][:inactive_at].present?
return if changing_inactive_at_is_not_requested?
return if touching_inactive_at_is_requested_and_inactive_at_is_already_present?
return if removing_inactive_at_is_requested_and_inactive_at_is_already_blank?
params[:resource][:inactive_at] = cast_to_boolean(inactive_param) ? Time.current : nil
end
def changing_inactive_at_is_not_requested?
inactive_param.nil?
end
def touching_inactive_at_is_requested_and_inactive_is_already_present?
cast_to_boolean(inactive_param) && @resource.inactive_at?
end
def removing_inactive_at_is_requested_and_inactive_at_is_already_blank?
!cast_to_boolean(inactive_param) && @resource.inactive_at.blank?
end
def inactive_param
params.dig(:resource, :inactive)
end
def cast_to_boolean(value)
ActiveRecord::Type::Boolean.new.cast(value)
end
end
我将详细介绍每种方法。
def resource_params
transform_inactive_param
params.require(:resource).permit(:inactive_at)
end
这是strong parameters的方法。
在返回强参数之前,具有布尔值的 inactive
参数将被转换为 inactive_at
的日期时间对象。
对于 inactive
的布尔值,Rail 的表单助手将为 false
输出 "0"
,为 true
输出 "1"
。
对于 typecasting in Rails,以下被认为是错误的:false, 0, "0", :"0", "f", :f, "F", :F, "false", :false, "FALSE", :FALSE, "off", :off, "OFF", :OFF
对于truthy,它是nil
以外的任何值。
对于此示例,我们将假设参数中未定义的 inactive
或分配的 nil
值表示不请求更改 inactive_at
。如果请求从请求负载中省略 inactive
,那么这是一个合理的假设。
def translate_inactive_param
return if params[:resource][:inactive_at].present?
return if changing_published_at_is_not_requested?
return if touching_inactive_at_is_requested_and_inactive_at_is_already_present?
return if removing_inactive_at_is_requested_and_inactive_at_is_already_blank?
params[:resource][:inactive_at] = cast_to_boolean(inactive_param) ? Time.current : nil
end
此方法将 inactive
参数转换为 inactive_at
参数。
这里使用了保护子句模式。我们希望翻译跳过一些条件。
params[:resource][:inactive_at].present?
— 我们将已经存在的 inactive_at
参数优先于 inactive
。如果 resource_params
在控制器中被多次调用,则很有用。
changing_published_at_is_not_requested?
— 如果 @resource.inactive_at
已经存在并且 inactive
参数为真,那么我们不想触及该字段。我假设更新时间戳不是必需的。
removing_inactive_at_is_requested_and_inactive_at_is_already_blank?
— 如果 @resource.inactive_at
已经是空白并且 inactive
参数是假的,那么就不需要转换为 inactive_at
参数。
如果满足其中 none 个条件,则翻译将继续。
三进制用于确定分配给 inactive_at
参数的翻译值是时间戳还是 nil
。转换用于将 inactive_param
转换为布尔值。
def changing_inactive_at_is_not_requested?
inactive_param.nil?
end
通过方法名称自我解释。引入一个方法,通过给它一个描述性的方法名称来清楚地说明它的作用。
def touching_inactive_at_is_requested_and_inactive_is_already_present?
cast_to_boolean(inactive_param) && @resource.inactive_at?
end
通过方法名称自我解释。引入一个方法,通过给它一个描述性的方法名称来清楚地说明它的作用。
def removing_inactive_at_is_requested_and_inactive_at_is_already_blank?
!cast_to_boolean(inactive_param) && @resource.inactive_at.blank?
end
通过方法名称自我解释。引入一个方法,通过给它一个描述性的方法名称来清楚地说明它的作用。
def inactive_param
params.dig(:resource, :inactive)
end
从 params
中访问 inactive
参数的值。由于这被使用了几次,我将其提取到一个方法中。
def cast_to_boolean(value)
ActiveRecord::Type::Boolean.new.cast(value)
end
转换“布尔”值,特别是来自 Rails 表单助手或通过 HTTP 完成的任何请求。 Rails 内置了 classes,可以对各种对象进行类型转换。 ActiveRecord::Type::Boolean
就是我们感兴趣的
编辑:
作为额外的奖励,这是在 RSpec 中进行的测试功能的请求测试。控制器测试可能更合适♂️
我就不去line-by-line解释了,留待reader自己去解读测试吧
RSpec::Matchers.define_negated_matcher(:not_change, :change)
RSpec.describe 'PATCH/PUT /resources/:id/edit', type: :request do
include ActiveSupport::Testing::TimeHelpers
shared_examples 'update `inactive_at` using parameter `inactive`' do
describe '`inactive` parameter' do
let(:resource) { Resource.create(inactive_at: nil) }
let(:params) { { resource: { inactive: nil } } }
subject { -> { resource.reload } }
it { is_expected.to(not_change { resource.inactive_at }) }
context 'when `inactive` key is form value falsey "0"' do
let(:params) { { resource: { inactive: '0' } } }
it { is_expected.to(not_change { resource.inactive_at }) }
end
context 'when `inactive` key is form value truthy "1"' do
let(:params) { { resource: { inactive: '1' } } }
around { |example| freeze_time { example.run } }
it { is_expected.to(change { resource.inactive_at }.to(Time.current)) }
end
context 'when `inactive_at` is present in the resource' do
let(:resource) { Resource.create(inactive_at: 1.year.ago) }
it { is_expected.to(not_change { resource.inactive_at }) }
context 'and when `inactive` key is form value falsey "0"' do
let(:params) { { resource: { inactive: '0' } } }
it { is_expected.to(change { resource.inactive_at }.to(nil)) }
end
context 'and when `inactive` key is form value truthy "1"' do
let(:params) { { resource: { inactive: '1' } } }
it { is_expected.to(not_change { resource.inactive_at }) }
end
end
end
end
describe 'PATCH' do
before do
patch resource_path(resource), params: params
end
include_examples 'update `inactive_at` using parameter `inactive`'
end
describe 'PUT' do
before do
put resource_path(resource), params: params
end
include_examples 'update `inactive_at` using parameter `inactive`'
end
end
假设我有一个名为 advertisements
的 table。我需要为其添加一个字段,以便人们可以将其标记为 active
或 inactive
。我可以添加一个名为 inactive 的 boolean
字段,这是一种直接的方法。
还有另一种方法,我可以创建一个数据类型为 datetime
的字段 inactive
。这不仅告诉我广告是否 active/no,还告诉我广告何时停用。因此,如果该值为 null,则它处于活动状态,如果它是时间戳,则它在过去的那个时间被停用。
但是,问题在于将它添加到表单助手中。 对于复选框实现,Rails 建议如下:
= f.check_box :inactive, { class: 'something' }
这与数据库中的 boolean
数据类型配合得很好。
我如何使用 datetime
值使它工作?
谢谢!
尝试
= f.check_box :inactive, checked: form.object.inactive.present?, { class: 'something' }
Active Record 为模型中的每个属性提供 query methods,它们以属性名称以问号结尾(例如,对于 model.name
属性有 model.name?
)命名当属性为空时为 false,否则为 true。此外,在处理数值时,如果值为零,查询方法将 return false。
AR 查询方法将为您的 inactive?
标志提供 getter,您需要添加 setter 以允许保存对 inactive?
属性的更改。
跟踪停用时间
添加一列deactivated_at
datetime
add_column :advertisements, :deactivated_at, :datetime
形式
<%= form.text_field :deactivated_at, class: "add-datetimepicker" %>
在字段上添加日期时间选择器
假设我们有一个允许参数的方法
def advertisment_params
params.require(:advertisement).permit(:inactive)
end
通过将方法更改为
def advertisment_params
params[:advertisement][:inactive] = params[:advertisement][:inactive] == "0" ? nil : @advertisement.nil? ? DateTime.now.to_s(:db) : @advertisement.inactive
params.require(:advertisement).permit(:inactive)
end
会成功的。
回答我自己的问题。因此,使用 check_box
帮助程序,您可以指定何时应将复选框显示为 checked
,您还可以指定当有人选中该框时要分配的值。
我做到了
= f.check_box :inactive, { class: 'something', checked: f.object.inactive.present? }, f.object.inactive.nil? ? DateTime.now : f.object.inactive
最后一部分 DateTime.now
是用户选中复选框时分配的值。这样您就可以使用复选框实现来保存 DateTime 值。值的存在可帮助您确定在显示表单时是否应选中该复选框。
此方法的唯一注意事项是值的准确性。用户可能会在打开表单几次 mins/hours 后提交表单。如果捕获正确的值对您很重要,您可以使用 JS 来分配准确的值或在控制器中包含逻辑。
以下是我对实现在控制器级别完成的布尔参数到日期时间转换的支持的看法。这解决了
代码被编写为与 Rails 6. YMMV 在其他版本上一起工作。
首先,您需要 table 上的时间戳列。我们称模型为 Resource
和 table resources
。在迁移中添加日期时间字段 inactive_at
。
def change
add_column :resources, :inactive_at, :datetime
end
我假设在控制器中,资源被实例化并分配给 @resource
。
在视图表单中,您需要在表单助手块中添加以下复选框行。
f.check_box :inactive, checked: @resource.inactive_at?
checked: @resource.inactive_at?
设置复选框的默认值。我们将使用参数 inactive
来承载是否更新 inactive_at
.
在您的控制器中,您需要以下内容。为简洁起见,如果控制器正在继承父级 class、操作和其他参数的存在,我排除了详细信息。
class ResourceController
private
def resource_params
translate_inactive_param
params.require(:resource).permit(:inactive_at)
end
def translate_inactive_param
return if params[:resource][:inactive_at].present?
return if changing_inactive_at_is_not_requested?
return if touching_inactive_at_is_requested_and_inactive_at_is_already_present?
return if removing_inactive_at_is_requested_and_inactive_at_is_already_blank?
params[:resource][:inactive_at] = cast_to_boolean(inactive_param) ? Time.current : nil
end
def changing_inactive_at_is_not_requested?
inactive_param.nil?
end
def touching_inactive_at_is_requested_and_inactive_is_already_present?
cast_to_boolean(inactive_param) && @resource.inactive_at?
end
def removing_inactive_at_is_requested_and_inactive_at_is_already_blank?
!cast_to_boolean(inactive_param) && @resource.inactive_at.blank?
end
def inactive_param
params.dig(:resource, :inactive)
end
def cast_to_boolean(value)
ActiveRecord::Type::Boolean.new.cast(value)
end
end
我将详细介绍每种方法。
def resource_params
transform_inactive_param
params.require(:resource).permit(:inactive_at)
end
这是strong parameters的方法。
在返回强参数之前,具有布尔值的 inactive
参数将被转换为 inactive_at
的日期时间对象。
对于 inactive
的布尔值,Rail 的表单助手将为 false
输出 "0"
,为 true
输出 "1"
。
对于 typecasting in Rails,以下被认为是错误的:false, 0, "0", :"0", "f", :f, "F", :F, "false", :false, "FALSE", :FALSE, "off", :off, "OFF", :OFF
对于truthy,它是nil
以外的任何值。
对于此示例,我们将假设参数中未定义的 inactive
或分配的 nil
值表示不请求更改 inactive_at
。如果请求从请求负载中省略 inactive
,那么这是一个合理的假设。
def translate_inactive_param
return if params[:resource][:inactive_at].present?
return if changing_published_at_is_not_requested?
return if touching_inactive_at_is_requested_and_inactive_at_is_already_present?
return if removing_inactive_at_is_requested_and_inactive_at_is_already_blank?
params[:resource][:inactive_at] = cast_to_boolean(inactive_param) ? Time.current : nil
end
此方法将 inactive
参数转换为 inactive_at
参数。
这里使用了保护子句模式。我们希望翻译跳过一些条件。
params[:resource][:inactive_at].present?
— 我们将已经存在的inactive_at
参数优先于inactive
。如果resource_params
在控制器中被多次调用,则很有用。changing_published_at_is_not_requested?
— 如果@resource.inactive_at
已经存在并且inactive
参数为真,那么我们不想触及该字段。我假设更新时间戳不是必需的。removing_inactive_at_is_requested_and_inactive_at_is_already_blank?
— 如果@resource.inactive_at
已经是空白并且inactive
参数是假的,那么就不需要转换为inactive_at
参数。
如果满足其中 none 个条件,则翻译将继续。
三进制用于确定分配给 inactive_at
参数的翻译值是时间戳还是 nil
。转换用于将 inactive_param
转换为布尔值。
def changing_inactive_at_is_not_requested?
inactive_param.nil?
end
通过方法名称自我解释。引入一个方法,通过给它一个描述性的方法名称来清楚地说明它的作用。
def touching_inactive_at_is_requested_and_inactive_is_already_present?
cast_to_boolean(inactive_param) && @resource.inactive_at?
end
通过方法名称自我解释。引入一个方法,通过给它一个描述性的方法名称来清楚地说明它的作用。
def removing_inactive_at_is_requested_and_inactive_at_is_already_blank?
!cast_to_boolean(inactive_param) && @resource.inactive_at.blank?
end
通过方法名称自我解释。引入一个方法,通过给它一个描述性的方法名称来清楚地说明它的作用。
def inactive_param
params.dig(:resource, :inactive)
end
从 params
中访问 inactive
参数的值。由于这被使用了几次,我将其提取到一个方法中。
def cast_to_boolean(value)
ActiveRecord::Type::Boolean.new.cast(value)
end
转换“布尔”值,特别是来自 Rails 表单助手或通过 HTTP 完成的任何请求。 Rails 内置了 classes,可以对各种对象进行类型转换。 ActiveRecord::Type::Boolean
就是我们感兴趣的
编辑:
作为额外的奖励,这是在 RSpec 中进行的测试功能的请求测试。控制器测试可能更合适♂️
我就不去line-by-line解释了,留待reader自己去解读测试吧
RSpec::Matchers.define_negated_matcher(:not_change, :change)
RSpec.describe 'PATCH/PUT /resources/:id/edit', type: :request do
include ActiveSupport::Testing::TimeHelpers
shared_examples 'update `inactive_at` using parameter `inactive`' do
describe '`inactive` parameter' do
let(:resource) { Resource.create(inactive_at: nil) }
let(:params) { { resource: { inactive: nil } } }
subject { -> { resource.reload } }
it { is_expected.to(not_change { resource.inactive_at }) }
context 'when `inactive` key is form value falsey "0"' do
let(:params) { { resource: { inactive: '0' } } }
it { is_expected.to(not_change { resource.inactive_at }) }
end
context 'when `inactive` key is form value truthy "1"' do
let(:params) { { resource: { inactive: '1' } } }
around { |example| freeze_time { example.run } }
it { is_expected.to(change { resource.inactive_at }.to(Time.current)) }
end
context 'when `inactive_at` is present in the resource' do
let(:resource) { Resource.create(inactive_at: 1.year.ago) }
it { is_expected.to(not_change { resource.inactive_at }) }
context 'and when `inactive` key is form value falsey "0"' do
let(:params) { { resource: { inactive: '0' } } }
it { is_expected.to(change { resource.inactive_at }.to(nil)) }
end
context 'and when `inactive` key is form value truthy "1"' do
let(:params) { { resource: { inactive: '1' } } }
it { is_expected.to(not_change { resource.inactive_at }) }
end
end
end
end
describe 'PATCH' do
before do
patch resource_path(resource), params: params
end
include_examples 'update `inactive_at` using parameter `inactive`'
end
describe 'PUT' do
before do
put resource_path(resource), params: params
end
include_examples 'update `inactive_at` using parameter `inactive`'
end
end