纸夹:根据模型设置样式(具有多态图像)

10

我已经设置了我的模型来使用多态的图像模型。这个工作得很好,但是我想知道是否可能为每个模型更改:styles设置。找到一些使用STI(Model < Image)的示例, 但这对我不是一个选项,因为我正在使用has_many关系。

Art

has_many :images, :as => :imageable

图片

belongs_to :imageable, :polymorphic => true
has_attached_file :file, :styles => { :thumb => "150x150>", :normal => "492x600>"}
                         #Change this setting depending on model

更新

我尝试在Proc方法内启动调试器。只有与附加文件相关的字段被填充:

run'irb(Image):006:0> a.instance => #<Image id: nil, created_at: nil, updated_at: nil, imageable_id: nil, imageable_type: nil, file_file_name: "IMG_9834.JPG", file_content_type: "image/jpeg", file_file_size: 151326, file_updated_at: "2010-10-30 08:40:23">

这是来自ImageController#create的对象

ImageController#create
@image => #<Image id: nil, created_at: nil, updated_at: nil, imageable_id: 83, imageable_type: "Art", file_file_name: "IMG_9834.JPG", file_content_type: "image/jpeg", file_file_size: 151326, file_updated_at: "2010-10-30 08:32:49">

我正在使用Paperclip(2.3.5)和Rails 3.0.1。无论我做什么,a.instance对象都只有与附件相关的字段填充。有任何想法吗?

更新2

在阅读了很多关于Paperclip论坛的内容后,我不认为在保存实例之前可以访问它是可能的。您只能查看Paperclip的内容,仅此而已。

我通过在Image控制器中使用before过滤器预先保存了不带附件的图像来解决了这个问题。

  before_filter :presave_image, :only => :create

  ...

  private

  def presave_image
    if @image.id.nil? # Save if new record / Arts controller sets @image
      @image = Image.new(:imageable_type => params[:image][:imageable_type], :imageable_id => params[:image][:imageable_id])
      @image.save(:validate => false)
      @image.file = params[:file] # Set to params[:image][:file] if you edit an image.
    end
  end

也许这真的是由于多态关联 - 或类似的原因,但至少现在你有了一个解决方案。 - lwe
10个回答

8
我非常晚才参与此次活动,但我想为任何遇到这个问题的人澄清有关访问模型数据的事情。我在使用Paperclip应用基于模型数据的水印时遇到了这个问题,在进行了很多调查后终于解决了问题。
你说:
在阅读了关于Paperclip论坛的大量信息之后,我认为在实例保存之前无法访问它。您只能看到Paperclip的内容,仅此而已。
实际上,如果在分配您的附件之前将模型数据设置在对象中,您可以看到模型数据!
当您的模型中的附件被分配时,会调用您的Paperclip处理器等内容。如果您依赖于大规模分配(或实际上不依赖),则一旦附件被分配值,Paperclip就会完成其工作。
以下是我解决问题的方法:
在我的拥有附件(照片)的模型中,我使除附件以外的所有属性都可访问,从而在大规模分配期间防止分配附件。
class Photo < ActiveRecord::Base
  attr_accessible :attribution, :latitude, :longitude, :activity_id, :seq_no, :approved, :caption

  has_attached_file :picture, ...
  ...
end

在我的控制器的创建方法(例如)中,我从参数中提取了图片,然后创建了对象。(不需要从params删除图片,因为attr_accessible语句应该防止分配picture,但这并不会影响)。然后,在设置照片对象的所有其他属性之后,我分配了picture属性。
def create
  picture = params[:photo].delete(:picture)
  @photo = Photo.new(params[:photo])
  @photo.picture = picture

  @photo.save
  ...
end

在我的情况下,其中一种图片样式需要应用水印,这是保存在attribution属性中的文本字符串。在我进行这些代码更改之前,归属字符串从未被应用,在水印代码中attachment.instance.attribution始终为nil。这里总结的更改使整个模型在paperclip处理器中可用。关键是要将附件属性放在最后赋值。希望对某些人有所帮助。

请帮帮我。我能看出您知道自己在说什么。我正在使用Paperclip,如何创建带有附件字段但不带附件的对象? - Tim Kozak

6

我找到了在创建过程中获取样式的解决方法。 关键是实现 before_post_processafter_save 钩子。

class Image < ActiveRecord::Base

  DEFAULT_STYLES = {
    medium: "300x300>", thumb: "100x100>"
  }

  has_attached_file :file, styles: ->(file){ file.instance.styles }


  validates_attachment_content_type :file, :content_type => /\Aimage\/.*\Z/

  # Workaround to pickup styles from imageable model
  # paperclip starts processing before all attributes are in the model
  # so we start processing after saving

  before_post_process ->{
    !@file_reprocessed.nil?
  }

  after_save ->{
    if !@file_reprocessed && (file_updated_at_changed? || imageable_type_changed?)
      @file_reprocessed = true
      file.reprocess!
    end
  }


  belongs_to :imageable, polymorphic: true

  def styles
    if imageable_class.respond_to?(:image_styles)
      imageable_class.image_styles
    end || DEFAULT_STYLES
  end

  def imageable_class
    imageable_type.constantize if imageable_type.present?
  end


end

所以您需要在imageable_class中定义image_styles类方法 在我的情况下是

class Property < ActiveRecord::Base

  def self.image_styles
    {
      large: "570x380#",
      thumb: "50x70#",
      medium: "300x200#"
    }
  end

end

对我有用。关键在于 before_post_process 和 after_save 钩子。 - Yeonho

3

:styles属性接受一个Proc作为参数,因此您可以做各种花哨的事情 :)

class Image < AR::Base
  has_attached_file :file, :styles => Proc.new { |a| a.instance.file_styles }

  def file_styles; { :thumb => "150x150>", :normal => "492x600>" } end
end

class Didum < Image
  def file_styles; { :thumb => "50x50>", :normal => "492x600>" } end
end

注意-上面的代码应该可以工作,但是说实话我没有设置来验证它,但看起来像Paperclip :: Attachment#styles如果它响应,则确实会call ,请参见http://rdoc.info/github/thoughtbot/paperclip/master/Paperclip/Attachment:styles 更新传递到Proc 中的对象不是实例,而是Paperclip :: Attachment ,但是实例可以通过附件上的instance 访问
PS:我在其他地方也看到过这个,但是忘记了在哪里...

和Adam的例子一样的问题。当模型是一个新的未保存记录时,模型会崩溃并报 nil 错误。但对于已保存的记录则没问题。 - atmorell
你有关于错误具体发生在哪里的信息吗?例如在Proc中还是其他地方? - lwe
我在我的一个项目(rails 3.0.0,paperclip 2.3.4)中添加了Proc.new { |a| Rails.logger.error(a.instance.inspect); { ... } },它正确地返回了我的实例 - 所以...它可能真的归结于使用的版本或类似的东西... - lwe

2
我也遇到了同样的问题,在寻找优雅的解决方案后,我没有找到任何“真正”有用的东西。所以,这里是我的简单解决方案,目前看起来还不错;(尽管我现在不确定它是否有任何缺点)
在模型中;
class Asset < ActiveRecord::Base
  belongs_to :assetable, polymorphic: true

  has_attached_file :attachment,  path: ":rails_root/#{path}/assets/images/:style/:filename",
                                  url: '/assets/images/:style/:filename',
                                  styles: -> (a) { a.instance.send(:styles) }
private
  def styles
    raise 'Undefined assetable.' unless assetable

    if assetable.class == Photo
      { small: 'x40', medium: '120x', large: '300x' }
    elsif assetable.class == Avatar
      { small: 'x40', medium: '120x', large: '300x' }
    else
      raise "Styles for #{assetable.class} is not defined."
    end
  end
end

在控制器中;

@photo = Photo.new
@photo.build_image(assetable: @photo, attachment: params[:photo][:image_attributes][:attachment])
@photo.save

谢谢,它帮助我解决了一个非常烦人的问题。这个解决方案非常有效,我喜欢它的简单性。我还对它进行了一些修改,以便在我的项目中更好地工作。 - sunquan

1
class Banner < ActiveRecord::Base  

  belongs_to :banner_categoria
  validates :banner_categoria_id ,{:presence =>{:message => "não informada"}} 

  has_attached_file :arquivo
  after_initialize :init_attachment




  def init_attachment
    self.class.has_attached_file :arquivo,
      :url => "/system/:class/:attachment/:id/:style/:basename.:extension",
      :path => ":rails_root/public/system/:class/:attachment/:id/:style/:basename.:extension",
      :styles => hash = {
      :banner => {
        :geometry => "#{self.banner_categoria.largura}x#{self.banner_categoria.altura}>",
        :quality => 80
      },
      :thumb => "100x100#"
    }
  end

结束


1
几个小时的挖掘 ActiveRecordPaperclip 的源代码之后,我认为有一种解决方案涉及到一些猴子补丁技巧。我没有进行彻底的测试,但似乎适合我的需求。下面是我的 config/initializers/activerecord_associations_patch.rb:
module ActiveRecord
  module Associations
    class HasManyAssociation

      def build_record(attributes)
        if options[:as] && owner
          # Unicorns
          reflection.build_association({}) do |record|
            set_owner_attributes(record)
            unless foreign_key_for?(record)
              record.public_send "#{options[:as]}=", owner
            end
            initialize_attributes(record)
            record.assign_attributes(attributes)
          end
        else
          # Classic Rails way
          reflection.build_association(attributes) do |record|
            initialize_attributes(record)
          end
        end
      end

    end
  end
end

由于reflection不知道我们的@owner,它首先分配attributes,触发调用record.#{instance}=(即Image#file=),然后将其转发到#assign并执行post_processing钩子,最终导致调用has_attached_file中提供的非多态imageable设置的stylesProc。嗯...
我重写了方法,先构建记录,然后再分配提供的属性。 此外,它考虑了foreign_key_for?检查中的record#new_record?
通过这种方式,只有在记录正确设置后才会分配attributes。至少我是这么认为的 ;)
更清晰的解决方案和建设性意见是非常受欢迎的 :)

1

我喜欢MarkGranoff的回答,但我想出了一个更简单的实现方式,对我来说很有效,并且似乎更易于维护。

#create new and instantiate the fields you need ( or all except the attachment )
@photo = Photo.new(:attribution => params[:photo][:attribution],
                   :latitude => params[:photo][:latitude ])

#then just assign all params as normal
@photo = Photo.assign_attributes(params[:node])

第二个语句分配附件,从而触发处理器,由于你已经分配了:attribution和:latitude,它们的值将通过attachment.instance方法在处理器中可用。
感谢这里的每个人为洞察力所做的贡献!

0

在处理模型的动态行为变化时,我曾经有过类似的问题。在 irb 中尝试了一下,我发现这个方法是可行的:

module Foo
   attr_accessor :bar
end
class Bar
   extends Foo
end
bar.bar = 'test' # 'test'
bar.bar # 'test'
# also works for instances of Bar!

因此,我将创建一个名为image_style的属性,可以通过在图像初始化时使用以下代码来更改为任何要添加的模块:

  def after_initialize
    if self.image_style?
       extend Object.const_get(image_style)
    else
       extend DefaultImageStyle
    end
  end

我只是想知道这是否适用于纸夹,因为after_initialize方法可能会在纸夹执行其操作之后调用..不过值得一试!


我忘了提到:该模块将包含has_attached_file指令,以及您想要的样式! - Lucas d. Prim
嗯,我不知道如何在我的情况下使用它。看起来很漂亮 :) - atmorell

0

从Paperclip附件的源代码来看,styles哈希可以接受一个响应call的对象,因此您可以尝试做如下操作:

class Image < ActiveRecord::Base
  belongs_to :imageable, :polymorphic => true
  has_attached_file :file, :styles => lambda {|attachment| attachment.instance.imageable_type.constantize.image_styles }
end

然后在任何有多张图片的模型中:

class Art < ActiveRecord::Base
  has_many :images, :as => :imageable

  def self.image_styles
    { :thumb => "150x150>", :normal => "492x600>" }
  end
end

编辑: 看起来有人比我先完成了 :P。


显然,instance_read用于与图像本身相关的属性,因为它会在附件名称前面添加。我所做的修改应该可以工作。 - Adam Tanner

-2

在生产/暂存服务器上遇到了同样的问题...但是在我的本地环境中没有。我在所有服务器上使用相同的rails/paperclip版本(2.3.2和2.2.6)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接