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

1

0

Basically I have an Image model which polymorphically belongs to imageable which are by far List and Item. Since an image will have its own attribute and relationship, I don't want to treat images like attributes of the List and Item and mess it up. So I create the Image model.

What I want to achieve is that List should have a logo thumb image where height equals width but Item has a different style. Paperclip doc has told us to create dynamic styles using lambda. So here's my Image model:

class Image < ActiveRecord::Base

  belongs_to :imageable, polymorphic: true

  has_attached_file :file,
                    :styles => lambda { |file| { thumb: (file.instance.imageable_type == "List") ? "300x300!" : "200x100!") } }
                    :default_url => "/images/:style/missing.png"

end

And my List model:

class List < ActiveRecord::Base
  def list_params
    params.require(:list).permit(:title, :image_attributes)
  end

  has_one :image, as: :imageable
  accepts_nested_attributes_for :image
  validates :image, presence: true
end

And my lists_controller.rb:

class ListsController < ApplicationController
  def list_params
    params.require(:list).permit(:title, :image_attributes)
  end

  def create
    @list = List.new(list_params)
    if @list.save
      redirect_to @list
    else
      render :action => "new"
    end 
  end
end

And I have nested form in my new.html.erb for lists. Everything works well if I don't use dynamic styles in Image model. If I do so, the imageable_type remains nil when the image styles are processed. It is believed that the Paperclip processor comes in too early when everything related to the imageable aren't assigned. So the result is that I always have an image with 200x100 size even when the imageable is a List.

I've been seeking around for a solution. But many solutions are for Rails 3 and failed in my app (like attr_accessible solution and any solution intending to retrieve anything about the imageable). Now I'd be grateful if anyone can provide a clean solution before I give in and use STI or monkey-patch Active Record.

Marcelo answered 18/12, 2015 at 18:37 Comment(0)
M
0

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)
Marcelo answered 19/12, 2015 at 7:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.