运行除“任何”之外的所有验证:存在

时间:2012-02-19 07:54:09

标签: ruby-on-rails ruby-on-rails-3 validation activerecord

有没有办法只运行特定类型的验证?

我有一个应用程序可以在一个表单中更新多个类实例。通过创建 building 的实例并对其进行验证来执行验证。

问题:如果没有更新属性,则表单字段留空,表单提交一个空字符串。你可以在这里看到一个例子,其中params [:building] [:name]是一个空字符串。

params = {:building => {:name =>“”,:short_name =>“”,:code =>“test”},:commit =>“更新建筑物”,:building_ids => [“2”,“5”,“7”],:action =>“update_multiple”,:controller =>“buildings”}

如何运行除检查是否存在属性的验证之外的所有验证?

def update_multiple
  @building = Building.new(params[:building].reject {|k,v| v.blank?})

  respond_to do |format|
    if @building.valid?
      Building.update_all( params[:building].reject {|k,v| v.blank?}, {:id => params[:building_ids]} )
      format.html { redirect_to buildings_path, notice: 'Buildings successfully updated.' }
    else
      @buildings = Building.all
      format.html { render action: 'edit_multiple' }
    end
  end
end

我花了很多时间研究这个问题,这是我迄今为止所发现的:

检索模型验证

$ Building.validators
=> [#<ActiveModel::Validations::PresenceValidator:0x007fbdf4d6f0b0 @attributes=[:name], @options={}>]

获取验证者种类

$ Building.validators[0].kind
=> :presence

这是rails用于运行验证的方法: https://github.com/rails/rails/blob/master/activesupport/lib/active_support/callbacks.rb 第353行

  # This method runs callback chain for the given kind.
  # If this called first time it creates a new callback method for the kind.
  # This generated method plays caching role.
  #
  def __run_callbacks(kind, object, &blk) #:nodoc:
    name = __callback_runner_name(kind)
    unless object.respond_to?(name, true)
      str = object.send("_#{kind}_callbacks").compile
      class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
        def #{name}() #{str} end
        protected :#{name}
      RUBY_EVAL
    end
    object.send(name, &blk)
  end

如果有办法直接运行验证?如果是这样,我可以遍历Building.validators,只运行kind != :presence

我很想听听你的任何想法。

2 个答案:

答案 0 :(得分:1)

我认为绕过特定的验证是一个有趣的想法,但我认为这是一种更简单的方法。我会通过编写一个验证批量更新的自定义方法来处理这个问题,如下所示:

def valid_for_batch?(params)
  @building = Building.new(params[:building].reject {|k,v| v.blank?})
  @building.name = "Foo" if @building.name.blank?
  @building.shortname = "Bar" if @building.shortname.blank?
  # etc...
  @building.valid?
end

只需确保“Foo”和“Bar”上有值将通过验证 - 所有代码正在查看值是否为空,如果是,请将其替换为临时值通过验证。这样,@building.valid?最终返回false的唯一方法就是存在不良数据。

答案 1 :(得分:0)

哇,在这个问题上工作了好几个小时之后,它看起来是一个非常困难的问题。

创建一个类实例@building,并为已验证其存在的属性设置占位符。

我尝试了很多不同的方法,这是迄今为止我能想到的最好方法。

def update_multiple
  valid = true

  @building = Building.new(params[:building])
  set_bulk_edit_placeholders_for_presence_validators(@building)
  building_valid = @building.valid?

  # Check if buildings were selected to edit
  if params[:building_ids].blank?
    valid = false
    @building.errors.add(:base, 'You must select at least one Building')
  end

  # Check if all form fields are blank
  if params[:building].values.delete_if {|v| v.blank?}.blank?
    valid = false
    @building.errors.add(:base, 'The edit form must not be empty')
  end

  respond_to do |format|
    if valid && building_valid
      @buildings = Building.find(params[:building_ids])
      @buildings.each do |building|
        building.update_attributes!(params[:building].reject { |k,v| v.blank? })
      end
      format.html { redirect_to buildings_path, notice: 'Buildings were successfully updated.' }
    else
      @buildings = Building.all
      format.html { render edit_multiple_buildings_path }
    end
  end
end

这是设置占位符的一般功能。它可以用于任何控制器的任何模型。

application_controller.rb

private

def set_bulk_edit_placeholders_for_presence_validators(class_instance, hash={})
  model_name = hash[:model_name] || self.class.name.sub("Controller", "").singularize.downcase
  klass = self.class.name.sub("Controller", "").singularize.constantize
  klass.send(:validators).each { |validator|
    if (validator.kind == :presence)
      validator.attributes.each { |attribute|
        if params[model_name][attribute].blank?
          class_instance.send("#{attribute}=", 'placeholder')
        end
      }
    end
  }
end

为了在表单上正确显示错误,它必须是form_for @buildings。 由于占位符是为@buildings对象设置的,因此我们必须指定表单值直接来自params

edit_multiple.haml

= bootstrap_form_for @building, :url => update_multiple_buildings_path, :method => :put, :html => {:class => 'form-horizontal'} do |f|
  = f.text_field :name, :value => params[:building].try(:send, :[], :name)
  = f.text_field :short_name, :value => params[:building].try(:send, :[], :short_name)
  = f.text_field :code, :value => params[:building].try(:send, :[], :code)

  = f.submit 'Update Buildings', :name => nil

  %table.table.table-striped.table-bordered
    %thead
      %tr
        %th.check_box_column= check_box_tag 'select_all'
        %th.sortable= sortable :name
        %th Abbr Name
        %th.sortable= sortable :code
        %th
    %tbody
      - @buildings.each do |building|
        %tr
          %td.check_box_column= check_box_tag 'building_ids[]', building.id, (params[:building_ids].include?(building.id.to_s) if params[:building_ids])
          %td= building.name
          %td= building.short_name
          %td= building.code
          %td.links.span2
            = link_to 'Show', building
            = link_to 'Edit', edit_building_path(building)
            = link_to 'Delete', building, :method => :delete, :confirm => 'Are you sure you want to delete this building?'

我希望这对处理类似“批量更新”验证问题的其他人有所帮助。