我正在使用Rails 5构建一个Web应用程序,当我在相关记录中定义了非持久性属性(带有attr_accessor
)时,遇到了通过父记录更新关联记录的问题。具体来说,我让用户以某种方式在子记录上提供非持久性属性,并根据这些属性的值,为 persistent 属性赋值。一个before_save
回调。问题是子记录没有保存到数据库中(因此不会调用保存回调),除非通过父记录在子记录上更改了持久性属性。
我在几种不同的情况下遇到过这个问题,但这里的(简化)示例涉及使用Paperclip Gem来处理客户端浏览器上传到AWS S3的图像。
应用/模型/ dog.rb
class Dog < ApplicationRecord
has_many :certificates, :dependent => :destroy
accepts_nested_attributes_for :certificates, :allow_destroy => true
end
应用/模型/ certificate.rb
class Certificate < ApplicationRecord
# load with path to client-uploaded file on S3 and save to
# update digitized_proof attachment
attr_accessor :s3_key
belongs_to :dog
has_attached_file :digitized_proof,
:content_type => { :content_type => ['image/jpg', 'image/png'] }
before_save :fetch_digitized_proof_from_s3
def fetch_digitized_proof_from_s3
return unless self.s3_key.present?
# note `S3_BUCKET` is set in the aws initializer
s3_obj = S3_BUCKET.object(self.s3_key)
# load paperclip attachment via S3 presigned URL
s3_presigned_url = s3_obj.presigned_url(:get,
:expires_in => 10.minutes.to_i)
self.digitized_proof = URI.parse(s3_presigned_url)
end
end
apps / controllers / dogs_controller.rb摘录
def update
@dog = Dog.find(params[:id])
if @dog.update(dog_params)
redirect_to ...
...
end
private
def dog_params
params.require(:dog).permit(
...,
:certificates_attributes => [:id, :_destroy, :s3_key]
)
end
我已经编写了javascript,可以直接从客户端的浏览器将图像上传到S3存储桶中的临时文件夹,并将s3_key
添加到更新表单中,以便可以在服务器端识别和处理图像(请参阅certificate.rb中的fetch_digitized_proof_from_s3
方法。问题是除非更新参数中的实际数据库属性已更改,否则永远不会更新证书。
为什么会发生这种情况,我该如何解决?
示例参数
{
...,
certificates_attributes: [
{id: '1', _destroy: '0', s3_key: 'tmp/uploads/certificates/.../photo.jpg'},
{id: '2', _destroy: '0', s3_key: 'tmp/uploads/certificates/.../photo2.jpg'}
]
}
宝石版
rails-5.0.0
activerecord-5.0.0
paperclip-5.1.0
aws-sdk-2.10.0
我可以通过在fetch_digitized_proof_from_s3
的setter方法中调用s3_key
来完成证书更新(并删除before_save
回调):
# app/models/certificate.rb
def s3_key=(key)
@s3_key = key
self.fetch_digitized_proof_from_s3
end
这会触发关联的证书正确保存(我认为这是因为digitized_proof
,这是一个持久属性,通过调用fetch_digitized_proof_from_s3
更新。这有效,但我还是宁愿在保存记录时从S3获取图像。
答案 0 :(得分:1)
除非在ActiveModel :: Dirty中注册了更改,否则相关记录似乎不会更新。设置非持久性属性时不会发生这种情况:
cert = Certificate.find(1)
cert.s3_key = 'tmp/uploads/certificates/...'
cert.changed? # => false
将以下方法添加到Certificate.rb会产生所需的行为:
def s3_key=(key)
attribute_will_change!('s3_key') unless s3_key == key
@s3_key = key
end
现在结果是
cert = Certificate.find(1)
cert.s3_key = 'tmp/uploads/certificates/...'
cert.changed? # => true
,并且在设置s3_key
时相关记录会相应更新。