【问题标题】:Gender Validation in Multiple Rails Models多个 Rails 模型中的性别验证
【发布时间】:2011-05-21 23:16:34
【问题描述】:

大家好——这个问题专门针对gender 验证,但我很想听听你们如何处理具有更大集合的类似情况(例如,国家/地区选择)。

我正在开发一个系统,让运动员可以注册各种赛事,目前正在开发一个良好的性别验证。我的问题是,在许多不同模型上运行相同验证的最佳、最干燥的方法是什么?

假设我想验证EventUser 的性别属性。我可以为validates_each 创建一个助手,在更新gender 属性之前检查值是否包含在["male", "female"] 的非常短的数组中。但是,如果我想在 form_for 块中访问相同的性别数组,比如作为 collection_select 的输入,该怎么办?

我让它适用于一个模型——我在Event 中声明了一个GENDERS 常量,并且有一个简短的类方法

def self.genders
    GENDERS
end

通过表单访问。但是如果多个模型需要访问,我应该将数组存储在哪里?

编辑:一个想法是在应用程序控制器中使用类方法。任何关于这种方法有多合适的想法都会很棒。

【问题讨论】:

  • 为什么不在你的设计中让 Gender 成为一个实体?这有点矫枉过正,但它可能比硬编码数组更适合,但它很短。这样它就可以像任何其他模型一样工作,并且可以在 collection_select 中访问它而无需进一步的箍。

标签: ruby-on-rails data-modeling


【解决方案1】:

我会编写一个自定义验证插件(例如,validates_gender)。然后你会打电话:

class Event < ActiveRecord::Base
   validates_gender :gender
end

获取我的validates_as_email 插件的副本并使用它,用您自己的逻辑替换value =~ EMAIL_ADDRESS_RE

【讨论】:

  • 我可以编写验证器——但如果我这样做了,是否有任何简单的方法可以访问我稍后使用的数组,在表单中?我知道整个问题对于性别来说是愚蠢的,但它对于在多个模型中验证更大的集合很重要。
  • 是的。您可以在验证器中定义内容(就像我对 EMAIL_ADDRESS_RE 所做的那样),然后在代码的其他地方使用 ActiveRecord::Validations::ClassMethods::EMAIL_ADDRESS_RE。
  • 这有助于验证部分,但不利于创建要在选择框中使用的集合。
  • @Beerlington,你错了。他可以在验证器中创建一个常量,并从他的 ERB 中访问它:。我会定义一个本地常量来节省打字时间。
  • 他可以,但我只是回复你的回答,没有提到这一点。向 ActiveRecord 验证类方法添加特定于应用程序的常量似乎也有点奇怪。
【解决方案2】:

通过将其存储在常量中,您得到了正确的想法。我唯一会做的不同是将它放在一个初始化文件中,这样它就不会像您的示例那样与任何特定模型相关联。如果您担心顶层可能存在名称冲突,可以将其放在 lib 目录中的模块中,并仅在您打算使用它的地方包含该模块。

【讨论】:

  • 太棒了,+1。我会选择另一个答案,因为这种模式使我无法复制 self.genders 类方法,但这也很棒。
【解决方案3】:

我同意把它放在一个常数中。我还将字符串本身放在常量中,因为(1)它们可以更改,并且(2)在条件中使用时,如果您输入错误,系统将捕获它们。例如,在您的环境中。rb:

MALE    = 'male'
FEMALE  = 'female'
GENDERS = [MALE, FEMALE]

然后在您的代码中,您只能引用这些常量,例如:

def male?
  return gender == MALE
end

【讨论】:

  • 为什么不直接使用符号呢? GENDERS = [:male, :female]
  • 是的,我们可以做到。但是当我想向用户显示一个字符串时,我喜欢使用实际的字符串来表示这些常量。例如,MALE.titleize 或 FEMALE.pluralize。
【解决方案4】:

这是我的解决方案。我喜欢使用标准的插件式库。我会把它放在 lib/acts_as_gendered 中:

module ActsAsGendered
  GENDERS = ['male', 'female']

  def self.included(base)
    base.extend(ActsAsGenderedMethods)
  end

  module ActsAsGenderedMethods
    def acts_as_gendered
      extend ClassMethods
      include InstanceMethods

      validates_inclusion_of :gender, :in => GENDERS
    end
  end

  module ClassMethods
    def is_gendered?
      true
    end
  end

  module InstanceMethods
    def is_male
      gender = 'male'
    end

    def is_female
      gender = 'female'
    end

    def is_male?
      gender == 'male'
    end

    def is_female?
      gender == 'female'
    end
  end
end

是的,对于简单的性别来说这可能有点过头了,但是您可以看到所有部分的去向 - GENDERS 常量、acts_as_gendered ActiveRecord 钩子,然后包括类和实例方法以及验证。

然后,在 config/initializers/gender.rb:

require 'acts_as_gendered'
ActiveRecord::Base.send(:include, ActsAsGendered)

然后,对于压轴戏,模特:

class User < ActiveRecord::Base
  acts_as_gendered
end

这种模式可能看起来过于复杂,但大多数库最终都是这样 :)

更新:为了回答您的评论,我将修改acts_as_gendered 方法以使每个模型的验证成为可选:

def acts_as_gendered options={}
  config = {:allow_nil => false}
  config.merge(options) if options.is_a?(Hash)

  extend ClassMethods
  include InstanceMethods

  if config[:allow_nil]
    validates_inclusion_of :gender, :in => (GENDERS + nil)
  else
    validates_inclusion_of :gender, :in => GENDERS
  end
end

现在您可以像这样在 User 模型中调用它:

class User < ActiveRecord::Base
  acts_as_gendered :allow_nil => true
end

我本可以将其作为您传入的简单参数,但我喜欢传入哈希的清晰性。它还为您在以后添加其他选项做好了准备。

【讨论】:

  • 太棒了,我会接受的。这是一个很棒的模式;当然,对于性别来说可能很复杂,但回答了我关于如何为更大的集合处理这些验证的问题。谢谢!
  • Jaime,您建议如何处理一个模型可能允许_nil(用户)而另一个模型(事件)不允许的情况?如果我需要这种级别的控制,常量是否更有意义?
  • 嘿山姆 - 我更新了我的答案,使用了一个符合您要求的变体。如您所见,这是一个非常通用的设置。当然,我不能声称自己发明了它——我是从 peepcode.com 学到的。看看他们,如果你还没有!
  • 那么这是否意味着 Gender 将作为字符串存储在数据库中?
  • 是的,确实如此。这完全正常化了吗?不,但这样做需要一个更复杂的插件来生成几个迁移,并且可能需要一个性别模型。它还在您的查询中引入了额外的连接。除非用户将性别模型正确地包含在他们现有的查询中,否则它还会导致额外的查询。像这样的小事是不值得的,尤其是当插件本身确保您的数据完整性时。
猜你喜欢
  • 1970-01-01
  • 2018-10-23
  • 1970-01-01
  • 1970-01-01
  • 2017-03-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多