我正在使用Paperclip为Ruby on Rails编写一些图像上传代码,我有一个可行的解决方案,但它非常hacky所以我真的很感激如何更好地实现它的建议。我有一个'资产'类,其中包含有关上传图像的信息,包括Paperclip附件,以及封装尺寸信息的'Generator'类。每个“项目”都有多个资产和发电机;所有资产应根据每台发电机规定的尺寸调整大小;因此,每个项目都有一定的规模,其所有资产都应该具有这些规模。
发电机型号:
class Generator < ActiveRecord::Base
attr_accessible :height, :width
belongs_to :project
def sym
"#{self.width}x#{self.height}".to_sym
end
end
资产模型:
class Asset < ActiveRecord::Base
attr_accessible :filename,
:image # etc.
attr_accessor :generators
has_attached_file :image,
:styles => lambda { |a| a.instance.styles }
belongs_to :project
# this is utterly horrendous
def styles
s = {}
if @generators == nil
@generators = self.project.generators
end
@generators.each do |g|
s[g.sym] = "#{g.width}x#{g.height}"
end
s
end
end
资产控制器创建方法:
def create
@project = Project.find(params[:project_id])
@asset = Asset.new
@asset.generators = @project.generators
@asset.update_attributes(params[:asset])
@asset.project = @project
@asset.uploaded_by = current_user
respond_to do |format|
if @asset.save_(current_user)
@project.last_asset = @asset
@project.save
format.html { redirect_to project_asset_url(@asset.project, @asset), notice: 'Asset was successfully created.' }
format.json { render json: @asset, status: :created, location: @asset }
else
format.html { render action: "new" }
format.json { render json: @asset.errors, status: :unprocessable_entity }
end
end
end
我遇到的问题是鸡蛋问题:新创建的资产在正确实例化之前不知道要使用哪些生成器(大小规格)。我尝试使用@project.assets.build,但是在资产获得项目关联集之前,Paperclip代码仍然执行,并且在我身上停了下来。
'if @generators == nil'hack是这样的,更新方法可以在没有进一步黑客攻击的情况下工作。
总而言之,感觉非常糟糕。任何人都可以建议如何以更合理的方式写这个,甚至是采取这种方式的方法吗?
提前致谢! :)
答案 0 :(得分:15)
我在一个试图使用基于具有多态关系的相关模型的动态样式的项目中遇到了相同的Paperclip鸡/蛋问题。我已经根据您现有的代码调整了我的解决方案。解释如下:
class Asset < ActiveRecord::Base
attr_accessible :image, :deferred_image
attr_writer :deferred_image
has_attached_file :image,
:styles => lambda { |a| a.instance.styles }
belongs_to :project
after_save :assign_deferred_image
def styles
project.generators.each_with_object({}) { |g, hsh| hsh[g.sym] = "#{g.width}x#{g.height}" }
end
private
def assign_deferred_image
if @deferred_image
self.image = @deferred_image
@deferred_image = nil
save!
end
end
end
基本上,为了解决Paperclip尝试在项目关系信息传播之前检索动态样式的问题,您可以将所有image
属性分配给非Paperclip属性(在本例中,我把它命名为deferred_image
)。 after_save
挂钩将@deferred_image
的值分配给self.image
,这将启动所有Paperclip爵士乐。
您的控制器变为:
# AssetsController
def create
@project = Project.find(params[:project_id])
@asset = @project.assets.build(params[:asset])
@asset.uploaded_by = current_user
respond_to do |format|
# all this is unrelated and can stay the same
end
end
观点:
<%= form_for @asset do |f| %>
<%# other asset attributes %>
<%= f.label :deferred_upload %>
<%= f.file_field :deferred_upload %>
<%= f.submit %>
<% end %>
此解决方案还允许accepts_nested_attributes
使用assets
模型中的Project
关系(目前我正在使用它) - 在创建/编辑资源时上传资源项目)。
这种方法有一些缺点(例如,验证Paperclip image
与Asset
实例的有效性相关是棘手的),但这是我能想到的最好的猴子修补Paperclip以某种方式推迟执行style
方法,直到填充了关联信息。
我会密切关注这个问题,看看是否有人能更好地解决这个问题!
至少,如果您选择继续使用相同的解决方案,则可以对Asset#styles
方法进行以下风格改进:
def styles
(@generators || project.generators).each_with_object({}) { |g, hsh| hsh[g.sym] = "#{g.width}x#{g.height}" }
end
与现有方法完全相同,但更简洁。
答案 1 :(得分:5)
虽然我真的很喜欢Cade的解决方案,但只是一个建议。看起来'样式'属于一个项目...那你为什么不计算那里的发电机?
例如:
class Asset < ActiveRecord::Base
attr_accessible :filename,
:image # etc.
attr_accessor :generators
has_attached_file :image,
:styles => lambda { |a| a.instance.project.styles }
end
class Project < ActiveRecord::Base
....
def styles
@generators ||= self.generators.inject {} do |hash, g|
hash[g.sym] = "#{g.width}x#{g.height}"
end
end
end
编辑:尝试将控制器更改为(假设项目有很多资产):
def create
@project = Project.find(params[:project_id])
@asset = @project.assets.new
@asset.generators = @project.generators
@asset.update_attributes(params[:asset])
@asset.uploaded_by = current_user
end
答案 2 :(得分:3)
我刚刚解决了类似的问题。 在我的“styles”lambda中,我根据“category”属性的值返回不同的样式。问题是Image.new(attrs)和image.update_attributes(attrs)没有按可预测的顺序设置属性,因此我不能保证image.category在我的样式lambda之前会有一个值叫做。我的解决方案是覆盖我的Image模型中的attributes =(),如下所示:
class Image
...
has_attached_file :image, :styles => my_lambda, ...
...
def attributes=(new_attributes, guard_protected_attributes = true)
return unless new_attributes.is_a?(Hash)
if new_attributes.key?("image")
only_attached_file = {
"image" => new_attributes["image"]
}
without_attached_file = new_attributes
without_attached_file.delete("image")
# set the non-paperclip attributes first
super(without_attached_file, guard_protected_attributes)
# set the paperclip attribute(s) after
super(only_attached_file, guard_protected_attributes)
else
super(new_attributes, guard_protected_attributes)
end
end
...
end
这确保了paperclip属性在其他属性之后设置,因此可以在:style lambda中使用它们。
在回形针属性“手动”设置的情况下,它显然无济于事。但是,在这种情况下,您可以通过指定合理的顺序来帮助自己。就我而言,我可以写:
image = Image.new
image.category = "some category"
image.image = File.open("/somefile") # styles lambda can use the "category" attribute
image.save!
(Paperclip 2.7.4,rails 3,ruby 1.8.7)