【问题标题】:rails text_field / text_ara empty string vs nilrails text_field / text_ara 空字符串 vs nil
【发布时间】:2016-05-13 12:04:24
【问题描述】:

我什至不确定我是否有问题,但我只是不喜欢我的 text_fields 和 text_areas 以empty string 而不是nil 的形式保存在数据库中。

我正在使用空对象模式并且刚刚意识到如果我创建了一个配置文件但不填写位置字段然后调用<%= @user.profile.location.upcase %> 然后它不会爆炸因为位置是一个字符串即使它是空。

这是 Rails 的惯例吗?如果是这样,那为什么?这有点奇怪,因为假设配置文件上有一个 number_of_pets attr 然后我尝试调用类似

<% if user.profile.number_of_pets.odd? %>
  <%= @user.profile.first_name %> has odd number of pets
<% end %>

然后它爆炸了,因为我不能打电话给nil.odd?

和往常一样,所以如果没有填写,它将保存为空字符串

<%= form_for @profile, url: user_profile_path do |f| %>
  <%= f.label :city %>
  <%= f.text_field :location, class: 'form-control' %>
  ......

【问题讨论】:

  • 你可以为此编写验证
  • Amol,感谢您的回答,但这不是我的问题。我想知道约定是什么以及为什么。
  • 这也一直困扰着我。对于更可预测的数据(空白为NULL),我总是使用nilify_blanks gem。我知道这并不能回答你的问题,但这更像是一种同意。

标签: ruby-on-rails database null null-object-pattern


【解决方案1】:

最简单的解决方法是使用像“strip_attributes”这样的 gem:https://github.com/rmm5t/strip_attributes

可以通过在模型中添加 before_save 回调来完成自定义解决方法,该回调将任何空白值设置回 nil。

以您的模型为例:

before_save :my_nil_maker_method_lol

[...]

def my_nil_maker_method_lol
  if self.whatever_attribute.blank?
    self.whatever_attribute=nil
  end
end

更新:

保持空白字段不被保存可以通过上述几种方法完成,甚至可以在控制器中的空白参数到达数据库之前删除它们。

在维护数据库完整性时,Rails 的方式是始终将此逻辑保留在模型中。它使维护变得更加容易,并且减少了发生意外的空间,例如如果您要在其他地方修改传入的参数。

至于它应该如何在模型中完成,这实际上只是作为开发人员希望获得什么输入的问题。您可以添加一个如上所示的回调,以维护您认为合适的数据库,或者您可以添加一个validates_presence_of 验证,如果该字段留空,它将向用户返回错误。

如果您要问是否应该完全避免将空字符串插入数据库,这完全取决于您作为开发人员,因为在某些情况下您可能需要该信息,但在这种情况下听起来就好像您要限制空字符串一样。

【讨论】:

  • bkunzi01 感谢您的回答,但这不是我的问题。我问什么是约定以及为什么。
  • 我会暂时保持原样。目前我不需要任何特殊的东西,只是觉得我错过了一些东西。
  • 使用whatever_attribute.blank? 时要小心,因为如果属性值为false,则会返回true(这反过来会导致该属性不被保存)
【解决方案2】:

我说的是我观察到的,不一定是 Rails 约定。

例如,当我们使用rails g post title content:text 时,您可能还记得标题(字符串)和内容(文本)都没有default: ''。这已经暗示了我没有关于此的 Rails 约定,或者具体来说,默认情况下每个属性都允许为 NULL 或 nil。

使用 NULL 的优点是您可以识别哪些记录设置了这些属性。

假设我们有一个 API 服务器。如果客户端向我们的 API 服务器创建了一个帖子,我们就知道哪些属性应该具有值。让我们这样说:

client-1 的 POST 参数:

post: {title: 'Foo bar'}
  • 如果允许为 NULL,将创建一个Post(title: 'Foo bar', content: nil)
  • 如果默认:'',将创建一个Post(title: 'Foo bar', content: '')

client-2 的 POST 参数:

post: {title: 'Foo bar', content: ''}
  • 如果允许为 NULL,将创建一个Post(title: 'Foo bar', content: '')
  • 如果默认:'',将创建一个Post(title: 'Foo bar', content: '')

从上面,请注意,如果我们有默认值:'',那么我们无法知道客户端是否真的打算有一个空的 content 值,因为对于客户端 1 和客户端 2,生成的 @987654331无论如何,帖子的 @ 值都会有 '' (空)。但是如果我们有 NULL 允许的属性,那么我们仍然可以通过不在参数中传递属性来识别客户端是否打算没有 content 值。这是一个重要的用例。

根据您的项目和属性的用途,您可以使用允许 NULL 或为该属性设置默认空字符串。

现在,您遇到的允许 NULL 的一个主要问题是您不能保证每个值都是 String,因此如果 location 为 NULL 或 nil,您的 &lt;%= @user.profile.location.upcase %&gt; 将引发错误.

这可能有点烦人,尤其是如果您有像示例这样的方法链

<%= @user.profile.location.upcase.downcase %>

如果@user.profile.location 为零,这将是一个问题,因为您必须优雅地确保它不会引发错误。如果@user.profile 为零(假设您允许@user.profile 为零),这也将是一个问题。通常,您将执行以下操作来完成这项工作:

<% if !@user.profile.nil? && !@user.profile.location.nil? %> 
  <%= @user.profile.location.upcase.downcase %>
<% end %>

if 条件仍然可以持续很长时间,因为您有更长的链接方法来优雅地确保它不会引发任何错误。

使用.try() 可能会“清理”它。我经常使用它,尤其是在模板文件中。解决方案将像下面这样更简洁,尽管可能会让不知道的人感到困惑:

 <%= @user.profile.try(:location).try(:upcase).try(:downcase) %>

如果.location.upcase 为nil,它将返回nil,并且不再引发任何undefined method ... for NilClass

【讨论】:

  • 谢谢 Jay-Ar,我不喜欢使用 iftry,这就是我在玩 null object pattern 的原因。但我将保留字段的默认 "" 设置。
猜你喜欢
  • 1970-01-01
  • 2015-10-28
  • 2011-08-02
  • 2017-11-05
  • 1970-01-01
  • 2013-03-03
  • 1970-01-01
  • 2018-04-12
  • 2018-09-14
相关资源
最近更新 更多