【问题标题】:How to add form tag for a checbox attribute that is stored as a datetime in the database?如何为在数据库中存储为日期时间的复选框属性添加表单标签?
【发布时间】:2020-04-24 14:53:40
【问题描述】:

假设我有一个名为advertisements 的表。我需要为其添加一个字段,以便人们可以将其标记为activeinactive。我可以添加一个名为 inactive 的 boolean 字段,这是一种直接的方法。

还有另一种方法,我可以创建一个数据类型为datetime 的字段inactive。这不仅告诉我广告是否处于活动状态,还告诉我它何时被停用。因此,如果值为 null,则它处于活动状态,如果它是时间戳,则它在过去的那个时间被停用。

然而,问题在于将它添加到表单助手中。 对于复选框实现,Rails 建议如下:

= f.check_box :inactive, { class: 'something' }

这适用于 db 中 boolean 的数据类型。 如何使用 datetime 值进行这项工作?

谢谢!

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-5 form-helpers


    【解决方案1】:

    假设我们有一个允许参数的方法

    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
    

    会成功的。

    【讨论】:

      【解决方案2】:

      回答我自己的问题。因此,使用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 值。值的存在帮助您确定在显示表单时是否应选中复选框。

      这种方法唯一需要注意的是值的准确性。用户可能会在打开表单的几分钟/小时后提交表单。如果获取正确的值对您很重要,您可以使用 JS 来分配确切的值或在控制器中设置逻辑。

      【讨论】:

        【解决方案3】:

        试试

        = f.check_box :inactive, checked: form.object.inactive.present?,  { class: 'something' }
        

        【讨论】:

        • 请不要只发布代码作为答案,还要解释您的代码的作用以及它如何解决问题的问题。带有解释的答案通常质量更高,更有可能吸引投票。
        • 谢谢。你的回答帮了我一半。它有助于逻辑显示选中/未选中的复选框。当您选中该框时,它仍然需要解决设置值的逻辑。
        【解决方案4】:

        跟踪停用时间

        添加一列deactivated_atdatetime

        add_column :advertisements, :deactivated_at, :datetime
        

        形式

        <%= form.text_field :deactivated_at, class: "add-datetimepicker" %>
        

        在字段上添加日期时间选择器

        bootstrap-datetimepicker
        datetimepicker

        【讨论】:

          【解决方案5】:

          这是我对在控制器级别实现对布尔参数到日期时间转换的支持的看法。这解决了 accepted answer 中的问题,由于日期时间是在编辑视图中而不是在 PUT/PATCH 时生成的,因此时间戳过时。

          编写代码以与 Rails 6 一起使用。其他版本上的 YMMV。

          首先,您需要表格中的时间戳列。我们称模型为Resource,表为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 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 参数。

          如果这些条件都不满足,则继续翻译。

          三元组用于确定分配给inactive_at 参数的转换值是时间戳还是nil。 Casting 用于将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 内置了对各种对象进行类型转换的类。 ActiveRecord::Type::Boolean 是我们感兴趣的那个。

          编辑:

          作为额外的奖励,这里有一个在 RSpec 中进行的请求测试,用于测试功能。控制器测试可能更合适??‍♂️

          我不会逐行解释它,所以我将把它留给读者来解释测试。

          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
          

          【讨论】:

            【解决方案6】:

            Active Record 为模型中的每个属性提供query methods,它们以以问号结尾的属性名称命名(例如,对于model.name 属性,有model.name?),当属性为空且为真时返回假除此以外。此外,在处理数值时,如果值为零,查询方法将返回 false。

            AR 查询方法将为您的 inactive? 标志提供一个 getter,您需要添加 setter 以允许保存对 inactive? 属性的更改。

            【讨论】:

              猜你喜欢
              • 2015-11-06
              • 1970-01-01
              • 2019-04-01
              • 1970-01-01
              • 2014-12-03
              • 2022-10-04
              • 2011-09-22
              • 2011-09-26
              相关资源
              最近更新 更多