I’m writing some image upload code for Ruby on Rails with Paperclip, and I’ve got a working solution but it’s very hacky so I’d really appreciate advice on how to better implement it. I have an ‘Asset’ class containing information about the uploaded images including the Paperclip attachment, and a ‘Generator’ class that encapsulates sizing information. Each ‘Project’ has multiple assets and generators; all Assets should be resized according to the sizes specified by each generator; each Project therefore has a certain set of sizes that all of its assets should have.
Generator model:
class Generator < ActiveRecord::Base
attr_accessible :height, :width
belongs_to :project
def sym
"#{self.width}x#{self.height}".to_sym
end
end
Asset model:
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
Asset controller create method:
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
The problem I am having is a chicken-egg issue: the newly created Asset doesn’t know which generators (size specifications) to use until after it’s been instantiated properly. I tried using @project.assets.build, but then the Paperclip code is still executed before the Asset gets its project association set and nils out on me.
The ‘if @generators == nil’ hack is so the update method will work without further hacking in the controller.
All in all it feels pretty bad. Can anyone suggest how to write this in a more sensible way, or even an approach to take for this kind of thing?
Thanks in advance! 🙂
I ran into the same Paperclip chicken/egg issue on a project trying to use dynamic styles based on the associated model with a polymorphic relationship. I’ve adapted my solution to your existing code. An explanation follows:
Basically, to get around the issue of Paperclip trying to retrieve the dynamic styles before the project relation information has been propagated, you can assign all of the
imageattributes to a non-Paperclip attribute (in this instance, I have name itdeferred_image). Theafter_savehook assigns the value of@deferred_imagetoself.image, which kicks off all the Paperclip jazz.Your controller becomes:
And the view:
This solution also allows using
accepts_nested_attributesfor theassetsrelation in theProjectmodel (which is currently how I’m using it – to upload assets as part of creating/editing a Project).There are some downsides to this approach (ex. validating the Paperclip
imagein relation to the validity of theAssetinstance gets tricky), but it’s the best I could come up with short of monkey patching Paperclip to somehow defer execution of thestylemethod until after the association information had been populated.I’ll be keeping an eye on this question to see if anyone has a better solution to this problem!
At the very least, if you choose to keep using your same solution, you can make the following stylistic improvement to your
Asset#stylesmethod:Does the exact same thing as your existing method, but more succinctly.