Ruby on Rails - Paperclip and dynamic parameters

前端 未结 3 1801
长情又很酷
长情又很酷 2021-02-04 15:24

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 imp

相关标签:
3条回答
  • 2021-02-04 15:58

    While I really like Cade's solution, just a suggestion. It seems like the 'styles' belong to a project...so why aren't you calculating the generators there?

    For example:

    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
    

    EDIT: Try changing your controller to (assuming the project has many assets):

    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
    
    0 讨论(0)
  • 2021-02-04 16:08

    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.

    0 讨论(0)
  • 2021-02-04 16:20

    I've just solved a similar problem that I had. In my "styles" lambda I am returning a different style depending on the value of a "category" attribute. The problem though is that Image.new(attrs), and image.update_attributes(attrs) doesn't set the attributes in a predictable order, and thus I can't be guaranteed that image.category will have a value before my styles lambda is called. My solution was to override attributes=() in my Image model as follows:

    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
    

    This ensures that the paperclip attribute is set after the other attributes and can thus use them in a :style lambda.

    It clearly won't help in situations where the paperclip attribute is "manually" set. However in those circumstances you can help yourself by specifying a sensible order. In my case I could write:

    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)

    0 讨论(0)
提交回复
热议问题