8

私は Paperclip を使用して Ruby on Rails 用の画像アップロード コードを書いています。実用的なソリューションはありますが、非常にハックなので、より適切に実装する方法についてアドバイスをいただければ幸いです。Paperclip 添付ファイルを含むアップロードされた画像に関する情報を含む「Asset」クラスと、サイズ情報をカプセル化する「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 を使用してみましたが、アセットがプロジェクトの関連付けセットを取得して nil アウトする前に、Paperclip コードが実行されます。

「if @generators == nil」ハックは、コントローラーでさらにハッキングしなくても更新メソッドが機能するようにするためのものです。

全体的にかなり気持ち悪いです。これをより賢明な方法で記述する方法、またはこの種のことを行うためのアプローチを提案できる人はいますか?

前もって感謝します!:)

4

3 に答える 3

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)。フックはtoのafter_save値を割り当て、すべての Paperclip ジャズを開始します。@deferred_imageself.image

コントローラーは次のようになります。

# 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_attributesassetsリレーションを使用することもできますProject(これは現在私が使用している方法です - プロジェクトの作成/編集の一環としてアセットをアップロードするため)。

imageこのアプローチにはいくつかの欠点があります (たとえば、インスタンスの有効性に関連してPaperclip を検証するのAssetが難しくなります) が、Paperclip にモンキー パッチを適用しstyleて関連付けが完了するまでメソッドの実行を何らかの方法で延期する以外に考えられる最善の方法です。情報が入力されていました。

この質問に注目して、誰かがこの問題に対するより良い解決策を持っているかどうかを確認します!


少なくとも、同じソリューションを使い続けることを選択した場合は、Asset#stylesメソッドに次のスタイルの改善を加えることができます。

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

既存のメソッドとまったく同じことを行いますが、より簡潔です。

于 2013-01-26T17:43:46.893 に答える
5

私はケイドのソリューションが本当に好きですが、単なる提案です。「スタイル」はプロジェクトに属しているようです...では、そこでジェネレーターを計算しないのはなぜですか?

例えば:

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
于 2013-01-30T20:43:31.083 に答える
3

私はちょうど私が持っていた同様の問題を解決しました。私の「スタイル」ラムダでは、「カテゴリ」属性の値に応じて異なるスタイルを返しています。ただし問題は、Image.new(attrs) と image.update_attributes(attrs) が予測可能な順序で属性を設定しないため、スタイル ラムダの前に image.category が値を持つことを保証できないことです。と呼ばれます。私の解決策は、次のように 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 ラムダでそれらを使用できるようになります。

paperclip 属性が「手動で」設定されている状況では、明らかに役に立ちません。ただし、そのような状況では、賢明な順序を指定することで自分自身を助けることができます. 私の場合、次のように書くことができます:

image = Image.new
image.category = "some category"
image.image = File.open("/somefile") # styles lambda can use the "category" attribute
image.save!

(ペーパークリップ 2.7.4、レール 3、ルビー 1.8.7)

于 2013-02-05T16:23:52.980 に答える