Ruby on Rails - Paperclip and Dynamic Parameters

Ruby on Rails - Paperclip and dynamic parameters

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:

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

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 image attributes to a non-Paperclip attribute (in this instance, I have name it deferred_image). The after_save hook assigns the value of @deferred_image to self.image, which kicks off all the Paperclip jazz.

Your controller becomes:

# 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

And the view:

<%= form_for @asset do |f| %>
<%# other asset attributes %>
<%= f.label :deferred_upload %>
<%= f.file_field :deferred_upload %>
<%= f.submit %>
<% end %>

This solution also allows using accepts_nested_attributes for the assets relation in the Project model (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 image in relation to the validity of the Asset instance gets tricky), but it's the best I could come up with short of monkey patching Paperclip to somehow defer execution of the style method 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#styles method:

def styles
(@generators || project.generators).each_with_object({}) { |g, hsh| hsh[g.sym] = "#{g.width}x#{g.height}" }
end

Does the exact same thing as your existing method, but more succinctly.

Paperclip dynamic url?

Completely scrap the whole idea of dynamic URL parameter given to the Paperclip attachment. It breaks S3 image uploading cause Paperclip can't figure out which URL to use.

The solution is to introduce a new column in your schema called image_url.
The column will be updated on initialize/update in the ActiveModel and used in the web pages.

In code

class Product
has_attached_file: :image
after_create :update_image_url
after_update :update_image_url

def update_image_url
new_image_url = # some logic based on image_file_name that would either return the CloudFront URL or save the URL from image.url which is generated by Paperclip
# Paperclip does not update image_file_name_changed? so we can't say
# after_create or after_update if: image_file_name_changed? instead
# we have to manually check that image_url and new_image_url are different
update(image_url: new_image_url) if image_url != new_image_url
end
end

Dynamic use of :default_url in Paperclip

I found a solution, following this gist and this other question in stackoverflow.

My working solution:

Class Service

has_attached_file :logo,
:path => "/:id-:style-:filename",
:url => ":s3_eu_url",
:default_url => :set_default_url_on_category,
:styles => { :large => "600x400>",
:medium => "300x200>",
:small => "100x75>",
:thumb => "60x42>" }

private

def set_default_url_on_category
"/logos/:style/#{category.name}.png"
end
end

And an initializer paperclip_default_url_fix.rb

module Paperclip
module Interpolations
def self.interpolate(pattern, *args)
pattern = args.first.instance.send(pattern) if pattern.kind_of? Symbol

all.reverse.inject(pattern.dup) do |result, tag|
result.gsub(/:#{tag}/) do |match|
send(tag, *args)
end
end
end
end
end

Paperclip dynamic styles based on polymorphic parent class doesn't work (Rails 4.2.5, Paperclip 4.3.1)

The monkey-patch solution explains pretty much about why this is happening. But it's not easy to understand if you don't have comprehensive knowledge about Active Record. Basically, you have to make Rails assign imageable related attributes before assigning the Paperclip ones.

I've found a simpler solution thanks to @Eren CAY here. But I made some modifications for it to work better in my app.

In my Image model:

class Image < ActiveRecord::Base

belongs_to :imageable, polymorphic: true

has_attached_file :file,
:styles => -> (file) {file.instance.get_customized_styles},
:default_url => "/images/:style/missing.png"

def get_customized_styles
raise "Imageable not found." unless imageable_type

if imageable_type == "List"
imageable_type.constantize.image_styles
else
raise "Please define styles for #{imageable_type}."
end
end

end

And in my lists_controller.rb:

class ListsController < ApplicationController

def list_params
params.require(:list).permit(:title, :description, :image_attributes)
end

def new
@list = List.new
@list.build_image
end

def create
cleaned_list_params = list_params.dup
cleaned_list_params.delete(:image_attributes)
@list = List.new(cleaned_list_params)

image_params = {imageable_type: "List", file: params[:list][:image_attributes][:file]}
@list.build_image(image_params)
if @list.save
redirect_to :action => "index"
else
render :action => "new"
end
end

end

I think the essential is the specified parameter passed to tell Image its imageable type (no matter the parameter is a string or object or sth else). Normally, it's only after the file attribute is assigned that other imageable related attributes are assigned.

The main differences from the original solution is that:

  1. Pass only a string rather than a object to image. Then constantize the string if it's needed.
  2. Store image_styles in imageable models. I prefer this way to maintain the styles rather than put them all in Image model.
  3. Pass strong parameters to List.new as it has its own attributes. (The 'clean' process is optional, I just don't want the whole bunch of image_attributes to be passed and trigger Paperclip)

paperclip - dynamic use of :path

We can use Paperclip.interpolates for this work.

class Attachment < ActiveRecord::Base
self.table_name = 'attachments'
self.primary_key = 'srl'

validates :document_srl,
:presence => true,
:numericality => { only_integer: true },
allow_nil: false

has_attached_file :attached,
:path => ":attachment/:document_srl/:id/:style/:filename"
validates_attachment_content_type :attached, :content_type => /\Aimage\/.*\Z/

Paperclip.interpolates :document_srl do |attachment, style|
attachment.instance.document_srl
end
end

Dynamic Attachment Size for Paperclip (Rails)

Paperclip doesn't allow to pass function as size limit parameter. So you probably need to write custom validation:

  validate :validate_image_size

def validate_image_size
if document.file? && document.size > get_current_file_size_limit
errors.add_to_base(" ... Your error message")
end
end

Paperclip - dropbox. How can I setup a conditional has_attached_file?

I'm going to answer my own question because the other answers were too vague to accept - although they were on the right path. I think the community would prefer an answer with more code to back it up.

So here goes. To change the has_attached_file on a dynamic basis, you have to have a user_id column in the attachment model so that you're not calling current_user (which isn't possible without ugly hacks). Then you need a belongs_to as well to complete the user association. Let's assume I'm attaching an audio file to a Song model for this example.

The key to getting the dynamically set variables is to initialize the attachment with the after_initialize callback.

Song.rb

belongs_to :user    
has_attached_file :audio
after_initialize :init_attachment

def init_attachment
self.class.has_attached_file :audio,
:storage => :dropbox,
:dropbox_credentials => {app_key: DROPBOX_KEY,
app_secret: DROPBOX_SECRET,
access_token: self.user.token,
access_token_secret: self.user.secret,
user_id: self.user.id
access_type: "app_folder"},
:dropbox_options => {}
end

You are, of course, free to setup your association differently, but this is a working code example for the question posed.

Paperclip :style depending on model (has_many polymorphic images)

I am really late to the party here, but I wanted to clarify something about accessing model data for anyone else that happens on to this thread. I just faced this issue while using Paperclip to apply watermarks based on data from the model, and got it working after a lot of investigation.

You said:

After reading a lot on the Paperclip forum I don't believe it's possible to access the instance before it has been saved. You can only see the Paperclip stuff and that's it.

In fact, you can see the model data if it's been set in the object before your attachment is assigned!

Your paperclip processors and whatnot are invoked when the attachment in your model is assigned. If you rely on mass assignment (or not, actually), as soon as the attachment is assigned a value, paperclip does its thing.

Here's how I solved the problem:

In my model with the attachment (Photo), I made all the attributes EXCEPT the attachment attr_accessible, thereby keeping the attachment from being assigned during mass assignment.

class Photo < ActiveRecord::Base
attr_accessible :attribution, :latitude, :longitude, :activity_id, :seq_no, :approved, :caption

has_attached_file :picture, ...
...
end

In my controller's create method (for example) I pulled out the picture from the params, and then created the object. (It's probably not necessary to remove the picture from params, since the attr_accessible statement should prevent picture from being assgined, but it doesn't hurt). Then I assign the picture attribute, after all the other attributes of the photo object have been setup.

def create
picture = params[:photo].delete(:picture)
@photo = Photo.new(params[:photo])
@photo.picture = picture

@photo.save
...
end

In my case, one of the styles for picture calls for applying a watermark, which is the text string held in the attribution attribute. Before I made these code changes, the attribution string was never applied, and in the watermark code, attachment.instance.attribution was always nil. The changes summarized here made the entire model available inside the paperclip processors. The key is to assign your attachment attribute last.

Hope this helps someone.



Related Topics



Leave a reply



Submit