带有相互依赖字段的Rails验证和初始化

时间:2018-08-23 22:16:36

标签: ruby-on-rails rails-activerecord

在Rails中编码了两年之后,对于更高级的概念,我仍然不了解Rails Way。 Rails方式非常注重约定而不是配置,但是,当您进入企业编码时,所有规则都不适用,每个人都以非标准的方式编写博客。这是这些情况的另一种:上下文验证,其中上下文稍微复杂一些(字段相互依赖)。基本上在运输应用程序中,我需要使用请求参数和一些计算值来初始化一个AR对象。计算出的值取决于请求参数,我不确定如何初始化和验证成员变量。

table mailpieces
  mail_class
  weight
  sort_code
  t_indicator_id
end

class Mailpiece
  validates_presence_of: :mail_class
  validates_presence_of: :weight
  validates_presence_of: :sort_code
  validates_presence_of: :t_indicator_id

  def some_kind_of_initializer
    if mail_class == 'Priority'
      sort_code = '123'
    elsif mail_class == 'Express'
      if weight < 1
        sort_code = '456'
      else
        sort_code = '789'
      end
    end

    t_indicator = ndicator.find_by(name: 'blah')
    if sort_code = '456'
      t_indicator = Indicator.find_by(name: 'foobar')
    end
  end
end

mailpiece = Mailpiece.new(
  mail_class: params[:mail_class],
  weight: params[:weight])

#mailpiece.some_kind_of_initializer ?!

raise 'SomeError' if !mailpiece.valid?

some_kind_of_initializer应该是什么?

  1. 是否覆盖ActiveRecord初始化?这不是一个好习惯。
  2. after_initialize。更多Rails Way-sy。
  3. 之后调用的自定义方法 Mailpiece.new(例如mailpiece.some_kind_of_initializer)

无论采用哪种选择,问题都在于sort_code和t_indicator的初始化取决于mail_class和weight是否有效。鉴于在我输入some_kind_of_initializer之前mail_class和weight不应该为null,我应该如何编写验证?

  1. 将所有验证提取到json模式验证中。围绕mail_class和weight的更复杂的业务规则很难在json模式中编写。
  2. 将所有验证提取到某种类型的数据传输对象验证类中。远离Rails的处事方式。感觉就像我在用.NET / Java编写一样,我担心Rails会在以后(在验证,测试等方面)引起我的注意。
  3. 仅在已初始化mail_class和weight的情况下分配sort_code。这似乎是大多数Rails编写东西的方式,但这很难。这么多,如果/否则。这只是一个简单的示例,但是我的邮件中有带有引用的引用,并且它们都进行此类验证。如果这是正确的答案,那么我会直觉,将所有验证和所有初始化移至外部类/模块可能更容易-也许接近选项#2。

选项3代码重写

def some_kind_of_initializer
  if mail_class && weight
    if (mail_class == 'Priority')
      sort_code = '123'
    elsif (mail_class == 'Express')
      if weight < 1
        sort_code = '456'
      else
        sort_code = '789'
      end
    end
  end

  if sort_code
    t_indicator = Indicator.find_by(name: 'blah')
    if sort_code = '456'
      t_indicator = Indicator.find_by(name: 'foobar')
    end
  end
end

我很想听听您对此的看法。在我看来,这是AR的一种非常流行的用例,我不确定该怎么做。同样,这只是一个简单的例子。我的邮件模型具有许多其他引用,这些引用对邮件对象属性具有依赖性,并且它们之间的相互依存关系与上述样式相同。

2 个答案:

答案 0 :(得分:0)

我不确定它是否有帮助,或者我是否正确理解了您的问题,但也许使用“ case”而不是if / elsif / else,三元运算符,一些惰性分配和一些额外的验证可以使您的代码更好”铁轨的感觉。

对于惰性分配,我的意思是您可以等待初始化sort_code和t_indicator,直到您实际需要使用或保存它为止。

def sort_code
  return self[:sort_code] if self[:sort_code].present?

  sort_code = case mail_class
              when 'Priority' then '123'
              when 'Express'
                weight < 1 ? '456' : '789' if weight
              end
  self[:sort_code] = sort_code
end

这样,sort_code会在您第一次使用前就被初始化,因此您可以执行Mailpiece.new(mail_class:'Something'),而无需立即初始化sort_code。

当您需要保存对象并且从未调用过mailpiece.sort_code时,就会出现问题。您可能有一个before_validation回调,它可以调用sort_code进行初始化,以防万一尚未实现。

对于t_indicator也可以这样做。

我还将在您的验证中添加一些上下文

validates_presence_of :weight, if: Proc.new{|record| 'Express' == record.mail_class} #you could add some helper method "is_express?" so you can just do "if: :is_express?"
validates_presence_of :sort_code, if: Proc.new{|record| 'Priority' == record.mail_class or 'Express' == record.mail_class && record.weight.present?}

对不起,如果我错过了您的问题,我什至没有使用您的选择,哈哈。

答案 1 :(得分:0)

怎么样:

class Mailpiece
  validates_presence_of: :mail_class
  validates_presence_of: :weight
  validates_presence_of: :sort_code
  validates_presence_of: :t_indicator_id

  def default_mail_class
    'Priority'
  end

  def default_sort_code
    mail_class = mail_class || default_mail_class
    if mail_class == 'Priority'
      '123'
    elsif mail_class == 'Express'
      weight < 1 ? '456' : '789'
    end
  end
end

然后,当您需要找出t_indicator时,只需根据需要进行操作即可:

def is_foobar_indicator?
  sort_code || default_sort_code == '456'
end

def t_indicator
    indicator_params = {name: is_foobar_indicator? ? 'foobar' : 'blah'}
    Indicator.find_by(indicator_params)
end

因此,您仍然可以对sort_code进行验证(假设由用户提供),但是在查看t_indicator时仍使用默认值。我不知道模型的其余部分有多复杂,但是我建议您在需要它们之前不要查找值。另外,after_initialize很有风险,因为它会在声明的位置准确地运行您的代码-每次初始化之后,因此,如果您对N个项目运行查询,但只对它们进行查找,或者不使用设置的默认值,则after_initialize运行N次一无所有。