漏洞?我必须两次批量分配参数来更新 has_many 关联
我有一个 has_many 的注册模型:电话 注册模型accepts_nested_attributes_for:电话,:reject_if号码和代码为空?,并且具有attr_accessible:telephones_attributes(和所有其他字段)
电话属于_to:注册并且对于所有字段都有attr_accessible
当表单发送参数时,它似乎格式良好,但是当表单字段发送到 @register.attributes = params[:register] 时,它将更新所有字段,但不会更新现有电话(也应该更新其字段),它们只是保持以前的样子(我在调试时检查过)
现在这就是它变得奇怪的地方,在控制台中当我测试它时,我认为这是关于参数的问题,因为我可以让它在那里工作,现在我发现它的行为就像我想要的 has_many 关联仅在我第一次尝试分配它之后,我在控制器中尝试了此操作(批量分配参数两次)并且它有效。
当然,这一定与我使用 model.attributes = params 进行分配这一事实有关,我这样做,这样我也可以保存对系统更改的监控(而且我没有这样做在 before_filters 中,因为我必须访问一些数据,我觉得这些数据是特定于控制器的,例如 current_user 等)也许是糟糕的设计......但这不是很奇怪吗?我必须批量分配两次才能正常工作?这是某种错误吗?有谁知道是否有办法解决它?
- 我使用的是 Rails 2.3.8,Ruby 1.8.6
编辑: 这是示例代码,这里的很多代码都是葡萄牙语的:/
telefone = 电话, cadastro = register
正如你所看到的,我有一个 all_changes 方法来聚合对某些关联所做的更改,还有一个用于注释的自定义设置器(comentario_interno/externo)作为一次添加一条注释的方法...
#models
class Telefone < ActiveRecord::Base
#relações
belongs_to :cadastro
#validações
validates_presence_of :ddd
validates_presence_of :numero
validates_numericality_of :ddd
validates_numericality_of :numero
attr_accessible :ddd, :numero, :cadastro_id, :id
end
class Cadastro < ActiveRecord::Base
#relações
#cliente
belongs_to :estado
belongs_to :indicacao
has_many :telefones
has_one :outra_indicacao
#venda
belongs_to :user
belongs_to :banco
belongs_to :plano
belongs_to :pacote
belongs_to :situacao
belongs_to :situacao_diversa
has_many :comentario_internos
has_many :comentario_externos
#system
#has_many :sys_logs
has_many :sys_logs, :as => :monitorable
has_many :email_history, :through => :sys_logs, :conditions => {:type => 'SysEmail'} , :source => :sys_actions
has_many :lock_history, :through => :sys_logs, :conditions => {:type => 'SysLock'}, :source => :sys_actions
has_many :alteracao_history, :through => :sys_logs, :conditions => {:type => 'SysAlteracao'}, :source => :sys_actions
#filtros
#validações
#cliente
validates_presence_of :tipo, :nome, :cpfcnpj, :rg, :data_nascimento, :profissao, :filiacao, :email, :logradouro,
:tp_logradouro, :numero, :bairro, :cep, :cidade
validates_uniqueness_of :cpfcnpj
validates_presence_of :estado
#validate :must_have_at_least_one_telephone
#venda
validates_presence_of :user
validates_presence_of :situacao
validates_numericality_of :agencia, :allow_blank => true
validates_numericality_of :digito_agencia, :allow_blank => true
validates_numericality_of :cc, :allow_blank => true
validates_numericality_of :digito_cc, :allow_blank => true
validates_numericality_of :cpf_titular, :allow_blank => true
#cpf must be unique
accepts_nested_attributes_for :telefones, :reject_if => lambda {|attr| attr['ddd'].blank? && attr['numero'].blank?}
accepts_nested_attributes_for :outra_indicacao
accepts_nested_attributes_for :comentario_internos, :reject_if => lambda {|attr| attr['comentario'].blank?}
accepts_nested_attributes_for :comentario_externos, :reject_if => lambda {|attr| attr['comentario'].blank?}
#attr_accessible :new_comentario_interno, :new_comentario_externo, :telefones_attributes
attr_accessible :telefones_attributes, :new_comentario_interno, :new_comentario_externo, :outra_indicacao_attributes,
:user_id, :cc, :digito_cc, :data_instalacao, :cpfcnpj, :profissao, :tp_logradouro, :agencia, :cpf_titular,
:situacao_id, :estado_id, :plano_id, :banco_id, :nome, :data_nascimento, :cep, :observacao, :data_agendamento,
:dia_vencimento, :digito_agencia, :pacote_id, :nome_titular, :logradouro,
:indicacao_id, :telefones_attributes, :contrato, :confirmacao_condicoes, :estado_civil, :cidade,
:horario_retorno, :tipo, :sexo, :filiacao, :complemento, :bairro, :rg, :expeditor, :email, :numero,
:situacao_diversa_id
def new_comentario_interno=(attributes = {})
self.comentario_internos << ComentarioInterno.new(:user_id => attributes[:user_id], :comentario => attributes[:comentario]) unless attributes[:comentario].blank?
end
def new_comentario_externo=(attributes = {})
self.comentario_externos << ComentarioExterno.new(:user_id => attributes[:user_id], :comentario => attributes[:comentario]) unless attributes[:comentario].blank?
end
def self.buscar_cadastros(options = {})
conditions = []
conditions << sanitize_sql(["cadastros.situacao_id = ?", options[:situacao_id]]) unless options[:situacao_id].blank?
conditions << sanitize_sql(["cadastros.user_id = ?", options[:user_id]]) unless options[:user_id].blank?
conditions << sanitize_sql(["cadastros.created_at >= ? AND cadastros.created_at < ?",
Date.civil(options[:ano].to_i, options[:mes].to_i, 1),
Date.civil(options[:ano].to_i, options[:mes].to_i, -1)]) unless options[:ano].blank? || options[:mes].blank?
self.find(:all, :conditions => conditions.join(" AND "))
end
def self.vendas_count_on(situacao_id, options = {})
select = sanitize_sql(["SELECT count(*) FROM cadastros LEFT JOIN situacaos ON cadastros.situacao_id = situacaos.id
WHERE situacaos.id = ?", situacao_id])
select << sanitize_sql([" AND cadastros.user_id = ?", options[:user_id]]) unless options[:user_id].blank?
select << sanitize_sql([" AND cadastros.created_at >= ? AND cadastros.created_at < ?",
Date.civil(options[:ano].to_i, options[:mes].to_i, 1),
Date.civil(options[:ano].to_i, options[:mes].to_i, -1)]) unless options[:ano].blank? || options[:mes].blank?
count_by_sql(select)
end
def all_changes
#agregar telefones, outra indicacao, comentarios internos, comentarios externos
changes = self.changes
h = Hash.new
h["outra_indicacao"] = self.outra_indicacao.descricao_change if self.outra_indicacao && self.outra_indicacao.changed?
if self.id
old_telefones = connection.execute("select ddd || '-' || numero as numformat from telefones where cadastro_id = #{self.id}").collect {|t| t["numformat"]}
else
old_telefones = []
end
new_telefones = self.telefones.collect {|t| "#{t.ddd}-#{t.numero}"}
h["telefones"] = [old_telefones.join(', '), new_telefones.join(', ')] unless (old_telefones - new_telefones).empty?
changes.delete("syslogid")
changes.merge(h)
end
def locked?
#pegar o ultimo lock e retornar o valor
last_lock = self.lock_history.last
if last_lock
return last_lock.locked?
else
return false
end
end
end
#here's what Ive got to do in controller for this to work, remember its only when updating existing phones, creating is working normally
@cadastro.attributes = params[:cadastro]
@cadastro.attributes = {:telefones_attributes => params[:cadastro][:telefones_attributes]}
I've a Register model which has_many :telephones
Register model accepts_nested_attributes_for :telephones, :reject_if number and code blank?, and has attr_accessible :telephones_attributes (and all other fields)
Telephones belongs_to :register and has attr_accessible for all fields
When the form sends the params, it seems to be well formed, but when the form fields are sent to @register.attributes = params[:register] it will update all fields but not the existing telephones(which should have gotten an update to its fields as well), they just stay the way they were before(I checked when debugging)
Now this is where it gets weird, in the console when I test it, I was thinking it was something about params, because I could make it work there, now I discovered it behaves like I want for the has_many association only after the first time I try to assign it, I tried this in controller(mass assigning params two times) and it works.
Of course it must have something to do with the fact that I'm using model.attributes = params to assign, Im doing it this way so I can also save a monitoring of the changes to the system(and I'm not doing this in before_filters because I have to access some data I feel is specific to the controller like current_user and on) maybe is poor design... but isn't it weird?I have to mass-assign two times for this to work? Is this some kind of bug? Does anyone know if there's a way to fix it?
- I'm on rails 2.3.8, ruby 1.8.6
Edit:
Here's sample code, a lot of this code here is in portuguese, tho :/
telefone = telephones,
cadastro = register
As you can see I've got an all_changes method that aggregates changes done to some associations, theres a custom setter for comments(comentario_interno/externo) as means to adding one comment at time...
#models
class Telefone < ActiveRecord::Base
#relações
belongs_to :cadastro
#validações
validates_presence_of :ddd
validates_presence_of :numero
validates_numericality_of :ddd
validates_numericality_of :numero
attr_accessible :ddd, :numero, :cadastro_id, :id
end
class Cadastro < ActiveRecord::Base
#relações
#cliente
belongs_to :estado
belongs_to :indicacao
has_many :telefones
has_one :outra_indicacao
#venda
belongs_to :user
belongs_to :banco
belongs_to :plano
belongs_to :pacote
belongs_to :situacao
belongs_to :situacao_diversa
has_many :comentario_internos
has_many :comentario_externos
#system
#has_many :sys_logs
has_many :sys_logs, :as => :monitorable
has_many :email_history, :through => :sys_logs, :conditions => {:type => 'SysEmail'} , :source => :sys_actions
has_many :lock_history, :through => :sys_logs, :conditions => {:type => 'SysLock'}, :source => :sys_actions
has_many :alteracao_history, :through => :sys_logs, :conditions => {:type => 'SysAlteracao'}, :source => :sys_actions
#filtros
#validações
#cliente
validates_presence_of :tipo, :nome, :cpfcnpj, :rg, :data_nascimento, :profissao, :filiacao, :email, :logradouro,
:tp_logradouro, :numero, :bairro, :cep, :cidade
validates_uniqueness_of :cpfcnpj
validates_presence_of :estado
#validate :must_have_at_least_one_telephone
#venda
validates_presence_of :user
validates_presence_of :situacao
validates_numericality_of :agencia, :allow_blank => true
validates_numericality_of :digito_agencia, :allow_blank => true
validates_numericality_of :cc, :allow_blank => true
validates_numericality_of :digito_cc, :allow_blank => true
validates_numericality_of :cpf_titular, :allow_blank => true
#cpf must be unique
accepts_nested_attributes_for :telefones, :reject_if => lambda {|attr| attr['ddd'].blank? && attr['numero'].blank?}
accepts_nested_attributes_for :outra_indicacao
accepts_nested_attributes_for :comentario_internos, :reject_if => lambda {|attr| attr['comentario'].blank?}
accepts_nested_attributes_for :comentario_externos, :reject_if => lambda {|attr| attr['comentario'].blank?}
#attr_accessible :new_comentario_interno, :new_comentario_externo, :telefones_attributes
attr_accessible :telefones_attributes, :new_comentario_interno, :new_comentario_externo, :outra_indicacao_attributes,
:user_id, :cc, :digito_cc, :data_instalacao, :cpfcnpj, :profissao, :tp_logradouro, :agencia, :cpf_titular,
:situacao_id, :estado_id, :plano_id, :banco_id, :nome, :data_nascimento, :cep, :observacao, :data_agendamento,
:dia_vencimento, :digito_agencia, :pacote_id, :nome_titular, :logradouro,
:indicacao_id, :telefones_attributes, :contrato, :confirmacao_condicoes, :estado_civil, :cidade,
:horario_retorno, :tipo, :sexo, :filiacao, :complemento, :bairro, :rg, :expeditor, :email, :numero,
:situacao_diversa_id
def new_comentario_interno=(attributes = {})
self.comentario_internos << ComentarioInterno.new(:user_id => attributes[:user_id], :comentario => attributes[:comentario]) unless attributes[:comentario].blank?
end
def new_comentario_externo=(attributes = {})
self.comentario_externos << ComentarioExterno.new(:user_id => attributes[:user_id], :comentario => attributes[:comentario]) unless attributes[:comentario].blank?
end
def self.buscar_cadastros(options = {})
conditions = []
conditions << sanitize_sql(["cadastros.situacao_id = ?", options[:situacao_id]]) unless options[:situacao_id].blank?
conditions << sanitize_sql(["cadastros.user_id = ?", options[:user_id]]) unless options[:user_id].blank?
conditions << sanitize_sql(["cadastros.created_at >= ? AND cadastros.created_at < ?",
Date.civil(options[:ano].to_i, options[:mes].to_i, 1),
Date.civil(options[:ano].to_i, options[:mes].to_i, -1)]) unless options[:ano].blank? || options[:mes].blank?
self.find(:all, :conditions => conditions.join(" AND "))
end
def self.vendas_count_on(situacao_id, options = {})
select = sanitize_sql(["SELECT count(*) FROM cadastros LEFT JOIN situacaos ON cadastros.situacao_id = situacaos.id
WHERE situacaos.id = ?", situacao_id])
select << sanitize_sql([" AND cadastros.user_id = ?", options[:user_id]]) unless options[:user_id].blank?
select << sanitize_sql([" AND cadastros.created_at >= ? AND cadastros.created_at < ?",
Date.civil(options[:ano].to_i, options[:mes].to_i, 1),
Date.civil(options[:ano].to_i, options[:mes].to_i, -1)]) unless options[:ano].blank? || options[:mes].blank?
count_by_sql(select)
end
def all_changes
#agregar telefones, outra indicacao, comentarios internos, comentarios externos
changes = self.changes
h = Hash.new
h["outra_indicacao"] = self.outra_indicacao.descricao_change if self.outra_indicacao && self.outra_indicacao.changed?
if self.id
old_telefones = connection.execute("select ddd || '-' || numero as numformat from telefones where cadastro_id = #{self.id}").collect {|t| t["numformat"]}
else
old_telefones = []
end
new_telefones = self.telefones.collect {|t| "#{t.ddd}-#{t.numero}"}
h["telefones"] = [old_telefones.join(', '), new_telefones.join(', ')] unless (old_telefones - new_telefones).empty?
changes.delete("syslogid")
changes.merge(h)
end
def locked?
#pegar o ultimo lock e retornar o valor
last_lock = self.lock_history.last
if last_lock
return last_lock.locked?
else
return false
end
end
end
#here's what Ive got to do in controller for this to work, remember its only when updating existing phones, creating is working normally
@cadastro.attributes = params[:cadastro]
@cadastro.attributes = {:telefones_attributes => params[:cadastro][:telefones_attributes]}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
在您的示例中不清楚如何使用 all_changes 或是否调用它。尝试简化您的代码,看看问题实际上是出在 Rails 还是您的实现上。例如,以下内容应该有效:
您还应该注意到您的accepts_nested_attributes_for 中不需要reject_if 条件,因为您已经在验证基类中是否存在:ddd 和:numero。
It's not clear in your example how all_changes is used or if it is even called. Try simplifying your code to see if the issue is actually with Rails or with your implementation. For example, the following should work:
You should also notice that you don't need the reject_if condition in your accepts_nested_attributes_for because you are already validating the presence of :ddd and :numero in the base class.