我有以下(其中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" %>
答案 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
...