2013-01-13 9 views
8

私はRuby on Rails for Paperclipの画像アップロードコードを書いていますが、実用的な解決策がありますが、非常にハッキーです。それ。私は、Paperclip添付ファイルを含むアップロードされた画像に関する情報を含む 'Asset'クラスと、サイジング情報をカプセル化する 'Generator'クラスを持っています。各「プロジェクト」には複数の資産と発電機があります。すべてのアセットは、各ジェネレータで指定されたサイズに従ってリサイズする必要があります。したがって、各プロジェクトには、すべての資産に必要なサイズのセットがあります。Ruby on Rails - ペーパークリップと動的パラメータ

Generatorモデル:

class Generator < ActiveRecord::Base 
    attr_accessible :height, :width 

    belongs_to :project 

    def sym 
    "#{self.width}x#{self.height}".to_sym 
    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 

:新しく

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は、インスタンス化された後まで使用するジェネレータ(サイズ指定)を認識しませんまあ。私は@ project.assets.buildを使ってみましたが、Assetがプロジェクトの関連付けを取得して、私に返ってくる前に、Paperclipコードがまだ実行されています。

'if @generators == nil'ハックは、更新メソッドがコントローラでさらにハッキングすることなく動作するようになっています。

全体的にはかなり悪いと感じます。誰かがこれをもっと賢明なやり方で書く方法や、このようなことをするためのアプローチを提案することはできますか?

ありがとうございます! :)

+0

この質問とそれが答えは私のアプリで同じ機能を構築するために多くのことを助けています。みんなありがとう! :) – Gediminas

答えて

15

多相関係を持つ関連モデルに基づいて動的スタイルを使用しようとしているプロジェクトで、同じPaperclip chicken/eggの問題が発生しました。自分のソリューションを既存のコードに適合させました。説明は次のとおりです。

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 

を基本的には、プロジェクト関係情報が伝播される前に、あなたは(非ペーパークリップの属性にimageすべての属性を割り当てることができ、ダイナミックなスタイルを取得しようとペーパークリップの問題を回避するためにこの例では、名前はdeferred_imageです)。 after_saveフックは@deferred_imageの値をself.imageに割り当てます。この値は、Paperclipのすべてのジャズをキックオフします。

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

# 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 %> 

このソリューションはまた、私はそれを使用していますどのように現在Projectモデル(中assets関係のためaccepts_nested_attributesを使用できます - プロジェクトの作成/編集の一部として資産をアップロードすること)。

が、このアプローチにはいくつかの欠点があります(例:Assetインスタンスの有効性に関連してペーパークリップimageを検証するには、トリッキー取得)、それは私が猿の短いが、何らかの形での実行を延期するペーパークリップをパッチ適用を考え出すことができる最高ですアソシエーション情報が入力されるまで、このメソッドを呼び出す。

私はこの質問に誰かがこの問題のより良い解決策を持っているかどうかを見守っています!非常に少なくとも


あなたが同じソリューションを使用し続けることを選択した場合、あなたはあなたのAsset#styles方法を次のように文体改善することができます:

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

は、既存の方法とまったく同じことを行いしかし、より簡潔に。

+0

ねえ、あなたの答えに感謝! :) – Dave

5

私はCadeのソリューションが本当に好きですが、ちょっとした提案です。 「スタイル」はプロジェクトに属しているようです...なぜジェネレータを計算していないのですか?例えば

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:(プロジェクトは、多くの資産を持っていると仮定した場合)に、あなたのコントローラを変更してみてください:

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

実際のスタイルメソッドの場所はどこか興味深いですが、これは新しいレコードでは機能しません。スタイルラムダでは、レコードが保存されるまで 'a.instance.project'は' nil'になるので、 'nil'には' NoMethodError'が返されます。 – Cade

+0

私の解決策を編集しました...インスタンスが@asset変数を指している必要がありますか?私がしたようにコントローラを変更すると、それはすでにクリップを使用する隠れたインスタンス変数の中にプロジェクトがあるはずです。 –

+0

Railsはプロジェクトモデル、スタイルアクションでエラーを出します。言う: '構文エラー、予期しないkeyword_do_block、expecting keyword_end' – Gediminas

2

私はちょうど私が持っていた同様の問題を解決してきました。 私の "スタイル"ラムダでは、 "カテゴリ"属性の値に応じて別のスタイルを返しています。しかし問題はImage.new(attrs)、image.update_attributes(attrs)は属性を予測可能な順序で設定しないため、image.categoryがスタイルlambdaより前の値を持つことは保証できませんが呼び出されます。次のように私のソリューションは、私のイメージモデルに)(=属性を上書きすることでした:

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属性が「手動」に設定されている状況では、明らかに役立ちません。しかし、そのような状況では、賢明な命令を指定することによって自分自身を助けることができます。私の場合、私は書くことができます:

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)