显示委托属性的验证错误

时间:2012-12-22 08:40:17

标签: ruby-on-rails delegates simple-form

我有以下(其中Venue是演员的CTI后裔):

class Actor < ActiveRecord::Base
  has_one :profile, validate: true, autosave: true
end

class Venue < ActiveRecord::Base
...
  %w{description address website phone}.each do |attr|
    delegate attr.to_sym, "#{attr}=".to_sym, to: :profile!
  end

  def profile!
    actor.profile || actor.build_profile
  end
...
end

我直接在Venue表单中包含这些委托属性的字段。当其中一个属性未通过验证时,我看到的只是顶部的通知,而不是该字段周围的包装器。我想这一定是因为Venue实例的错误哈希中的键与属性名称不完全匹配,设置为:"actor.profile.website"而不是:website

有什么方法可以让这些错误正确显示?

修改

以下是表格:

<%= simple_form_for @venue do |f| %>
  <%= f.error_notification %>

  <%= f.input :name %>
  <%= f.input :address, required: true %>
  <%= f.input :phone %>
  <%= f.input :website %>
  <%= f.input :members, collection: [], class: "form_tag" %>
  <%= f.input :tag_list, as: :string, class: "form_tag", hint: t("misc.hint.tag"),
              input_html: { "data-pre" => @venue.tag_list.map {|t| { id: t, name: t }}.to_json } %>
  <%= f.input :description, as: :text, input_html: {rows: 6, cols: 53, class: "form_tag"} %>

  <div class="actions center">
<%= f.submit class: "btn btn-success" %>

4 个答案:

答案 0 :(得分:2)

module OtherValidation
  extend ActiveSupport::Concern

  module ClassMethods
    def delegate_with_validations(*attr_names)
      options = attr_names.extract_options!
      delegate *attr_names, options
      attr_names.each {|a| validate_using(options[:to], a)}
    end      

    def validate_using(target, *args)
      options = args.extract_options!
      args.each do |attr_name|
        class_eval <<-EOV
          dup = #{target}._validators[:#{attr_name}].dup
          validate do
            dup.each do |v|
              validator = v.dup
              validator.attributes.delete(:#{attr_name})
              validator.attributes << :#{options[:to]||attr_name}
              validator.validate(self)
            end
          end
        EOV
      end
    end
  end

end

现在进入Venue模型:

class Venue < ActiveRecord::Base
  include OtherValidation
  delegate_with_validations :website, :to => :profile!
end

# venue = Venue.new(:website => nil)
# venue.valid? # Profile validates presence of :website
#=> false
# venue.errors
#=> #<ActiveModel::Errors....., @messages={:website=>["can't be blank"]}>

更新:

对于任何自定义属性:

class Venue < ActiveRecord::Base
  include OtherValidation
  attr_accessor: title

  validate_using("Profile", :website, :to => :title)
end

# :website validation behavior constraints to :title attribute

# venue = Venue.new(:title => nil)
# venue.valid? # Profile validates presence of :website
#=> false
# venue.errors
#=> #<ActiveModel::Errors....., @messages={:title=>["can't be blank"]}>

配置/初始化/ other_delegation.rb

module OtherValidation
  ...
end

ActiveSupport.on_load :active_record do
  include OtherValidation
end

答案 1 :(得分:1)

没错。而纠正的方法是使用这样的东西:

class Venue < ActiveRecord::Base
...
  after_validation do
    if errors.any?
      errors.messages.keys.each do |key|
        errors.messages[key.to_s.gsub(/actor.profile./, "").to_sym] = errors.messages.delete(key)
      end
    end
  end
...
end

更新:

HOWTO使用div class =“field_with_error”封装内容

注意:仅当基础对象有错误并且错误具有等于属性名称的适当密钥(实际上是方法名称)时,Rails才会包装字段。对于嵌套的关联属性,它根据相关的序列使用前缀键(actor.profile.website)。

顺便说一下,常用的方法是:

<%= field_error_proc.call(content) %>

# where content is any string/symbol stuff.

触发错误处理:

<%= form_for... do |f| %>
  <% website_field = capture do %>
    <%= f.text_field :website %>
  <% end %>

  <% if f.object.errors[:"actor.profile.website"] %>
    <%= website_field %>
  <% else %>
    <%= field_error_proc.call(website_field) %>
  <% end %>
<% end %>

有点单调乏味吗?最好使用Rails的本机包装机制。

见下一个答案。

答案 2 :(得分:1)

你可以用我的宝石来避免这些东西:

gem 'delegate_attributes'

class Blog < ActiveRecord::Base
  delegate_attributes :theme, :errors => :fit, :writer => true, :to => :category
end

选项 :errors => :fit 声明现在可以在以下位置定义错误消息的i18n翻译:

en:
  activerecord:
    errors:
      models:
        blog:
          attributes:
            theme:
              blank: "Can not be blank"

选项 :writer => true 委派作家方法: .theme=

答案 3 :(得分:0)

Valery的答案在我的案例中有效,但对于带前缀的委托不起作用。此外,我一直在寻找一种隔离解决方案的方法,因此它只会影响有错误的表单字段的HTML生成。结束了这一点(可能不是最干净的代码,但似乎可以完成这项工作):

initializers/delegate.rb

module MappedDelegation
  extend ActiveSupport::Concern

  included do
    cattr_reader :delegation_mappings
    @@delegation_mappings ||= {}

    def self.delegate_with_mappings(*methods)
      delegate_without_mappings(*methods)

      options = methods.pop
      prefix, to = options.values_at(:prefix, :to)
      method_prefix = \
        if prefix
          "#{prefix == true ? to : prefix}_"
        else
          ''
        end

      methods.each do |method|
        @@delegation_mappings["#{method_prefix}#{method}"] = to
      end
    end

    self.class_eval do
      class << self
        alias_method_chain :delegate, :mappings
      end
    end
  end
end

initializers/simple_form_delegate_errors.rb

module SimpleForm
  module Components
    module Errors
      def errors
        @errors ||= (errors_on_attribute + errors_on_delegate + errors_on_association).compact
      end

      def errors_on_delegate
        delegated_to = ( object.class.respond_to?(:delegation_mappings) ? object.class.delegation_mappings : {} )[attribute_name.to_s]
        delegated_to ? object.send(delegated_to).errors[attribute_name] : []
      end
    end
  end
end

app/models/venue.rb

class Venue < ActiveRecord::Base
  include MappedDelegation
  ...