簡體   English   中英

擴展ruby本征類以加載CarrierWave

[英]Extending a ruby eigenclass to load CarrierWave

更新 :我簡化了我的問題; 您可以通過查看我的編輯修訂來查看完整的歷史記錄。 感謝iainbernardk讓我走到一步


我想將carrierwave功能加載到我的User < ActiveRecord::Base模型的實例中。

require 'uploaders/avatar_uploader'

module HasAnAvatar
  def self.extended(host)
    if host.class == Class
      mount_uploader :avatar, AvatarUploader
    else
      class << host
        mount_uploader :avatar, AvatarUploader
      end
    end
  end
end

執行:

(user = User.first).extend(HasAnAvatar).avatar

結果是:

NoMethodError:未定義的方法new' for nil:NilClass from /Users/evan/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/carrierwave-0.6.2/lib/carrierwave/mount.rb:306:in上傳器中

我懷疑問題是mount_uploader中的HasAnAvatar沒有在user本征類上正確調用,因此沒有填充uploaders哈希

有關如何使這個工作的任何想法?


以下是此問題的示例Rails應用程序: https//github.com/neezer/extend_with_avatar_example

這里有兩種方法可以將模塊包含到實例中(這基本上是擴展實例的本征類)。 我不認為這是你問題的最佳答案,即使它可以回答問題(部分)。

class A
end
# => nil

module B
  def blah
    "Blah!"
  end
end
# => nil

a = A.new
=> #<A:0x0000010086cdf0>

a.blah
# NoMethodError: undefined method `blah' for #<A:0x0000010086cdf0>

class << a
  include B
end

a.blah
# => "Blah!"

b = A.new
# => #<A:0x0000010083b818>

b.blah
# NoMethodError: undefined method `blah' for #<A:0x0000010083b818>

b.extend B
# => #<A:0x0000010083b818>

b.blah
# => "Blah!"

c.blah
# NoMethodError: undefined method `blah' for #<A:0x0000010085eed0>

從你的編輯:

module Pacifiable
  def pacified_with(mechanism)
    class_eval do
      define_method(:"pacified_with_#{mechanism}?") { true }
    end
  end
end
# => nil

class JellyFish
  define_method(:is_squishy?) { true }
end
# => #<Proc:0x00000100850448@(irb):10 (lambda)>

class Lobster
  extend Pacifiable
  pacified_with :polar_bear
  define_method(:is_squishy?) { false }
end
# => #<Proc:0x00000100960540@(irb):16 (lambda)>

lobster = Lobster.new
# => #<Lobster:0x0000010095aa50>

lobster.pacified_with_polar_bear?
# => true

jelly = JellyFish.new
# => #<JellyFish:0x00000100951108>

jelly.pacified_with_polar_bear?
# NoMethodError: undefined method `pacified_with_polar_bear?' for #<JellyFish:0x00000100951108>

class << jelly
    extend Pacifiable
    pacified_with :polar_bear
  end
# => #<Proc:0x0000010093ddd8@(irb):4 (lambda)>

jelly.pacified_with_polar_bear?
# => true

big_jelly = JellyFish.new
# => #<JellyFish:0x0000010091ad38>

big_jelly.pacified_with_polar_bear?
# NoMethodError: undefined method `pacified_with_polar_bear?' for #<JellyFish:0x0000010091ad38>

根據我所知的Ruby類,一旦我將一個模塊包含到一個類中,......但不能追溯性地改變任何現有的User實例。

相反,include / extend會立即影響所有現有實例,因為它是類及其超類之間的指針問題。 請參閱繼承如何在Ruby中工作? 還有里面的鏈接。

module HasAnAvatar
    def m
        puts 'in HasAnAvatar#m'
    end
end

class AvatarUploader; end

class User
end

user = User.new
print 'user.respond_to?(:m) ? ';         puts user.respond_to?(:m) ? 'yes' : 'no'
print '1) user.respond_to?(:avatar) ? '; puts user.respond_to?(:avatar) ? 'yes' : 'no'

class User
    include HasAnAvatar
end

print 'user.respond_to?(:m) ? ';         puts user.respond_to?(:m) ? 'yes' : 'no'
print '2) user.respond_to?(:avatar) ? '; puts user.respond_to?(:avatar) ? 'yes' : 'no'

module HasAnAvatar
  def self.included(base)
    puts "#{self} included into #{base}"
#    base.mount_uploader :avatar, AvatarUploader
    base.send(:attr_reader, :avatar)
  end
end

class User
    include HasAnAvatar
end

print '3) user.respond_to?(:avatar) ? '; puts user.respond_to?(:avatar) ? 'yes' : 'no'
print 'user.avatar : '; p user.avatar
print 'user.avatar.class : '; p user.avatar.class

user.instance_variable_set(:@avatar, AvatarUploader.new)
print 'after set, user.avatar : '; p user.avatar
print 'user.avatar.class : '; p user.avatar.class

執行(Ruby 1.9.2):

$ ruby -w t.rb 
user.respond_to?(:m) ? no
1) user.respond_to?(:avatar) ? no
user.respond_to?(:m) ? yes
2) user.respond_to?(:avatar) ? no
HasAnAvatar included into User
3) user.respond_to?(:avatar) ? yes
user.avatar : nil
user.avatar.class : NilClass
after set, user.avatar : #<AvatarUploader:0x007fcc2b047cf8>
user.avatar.class : AvatarUploader

因此包含的方法立即可用於所有現有實例。
為什么user.avatar回答為nil? 因為實例變量屬於......單個實例。 請參閱為什么Ruby中的符號不​​被視為一種變量? 在這種情況下,舊用戶未被分配頭像。

但是為什么你會得到user2.avatar.class #=> AvatarUploader 我想,在場景后面, base.mount_uploader :avatar, AvatarUploader做了這樣的事情:avatar不是相應實例變量的訪問者,或者定義了一個初始化方法,它開始將這個變量設置為新的實例。


這是第二個例子的解決方案:

module Pacifiable
    def self.extended(host)
        puts "#{host} extended by #{self}"
        def host.pacified_with(mechanism)
            @@method_name = "pacified_with_#{mechanism}?"
            puts "about to define '#{@@method_name}' into #{self} of class #{self.class }"
            if self.class == Class
            then # define an instance method in a class
                define_method(@@method_name) { true }
            else # define a singleton method for an object
                class << self
                    define_method(@@method_name) { true }
                end
            end
        end
    end
end

class JellyFish
  define_method(:is_squishy?) { true }
end

class Lobster
  extend Pacifiable
  pacified_with :polar_bear
  define_method(:is_squishy?) { false }
end

a_lobster = Lobster.new
print 'a_lobster.pacified_with_polar_bear? '; p a_lobster.pacified_with_polar_bear?
print 'a_lobster.is_squishy? '; p a_lobster.is_squishy?

jelly = JellyFish.new

### Add functionality to instance
#
## what I want:
#
jelly.extend(Pacifiable)
jelly.pacified_with(:polar_bear)
print 'jelly.pacified_with_polar_bear? '; p jelly.pacified_with_polar_bear? #=> true  

執行:

Lobster extended by Pacifiable
about to define 'pacified_with_polar_bear?' into Lobster of class Class
a_lobster.pacified_with_polar_bear? true
a_lobster.is_squishy? false
#<JellyFish:0x007fcc2b047640> extended by Pacifiable
about to define 'pacified_with_polar_bear?' into #<JellyFish:0x007fcc2b047640> of class JellyFish
jelly.pacified_with_polar_bear? true


關於jelly.class.pacified_with(:polar_bear) :錯誤是調用class ,它回答類JellyFish ; 你想要的是實例對象果凍的單例類。 一旦在對象的單例類中定義了方法,就可以將其直接發送到對象。 這同樣適用於類,它們是Class的實例。 一旦在類的單例類中定義了一個方法,就可以將它直接發送給類。 我們稱它們為類方法,它們實際上是類的單例類的實例方法。 哇!


Last OR:如上所述,如果擴展類,它對所有現有實例都有效。 因此,您必須擴展單個實例:

class JellyFromTheBigBlueSea
  def find
    puts 'in JellyFromTheBigBlueSea#find'
    jelly = JellyFish.new
    jelly.extend(Pacifiable)
    jelly.pacified_with :polar_bear
    jelly
  end
end

class JellyFromAnIsolatedCove
  def find
    puts 'in JellyFromAnIsolatedCove#find'
    JellyFish.new
  end
end

normal_jelly   = JellyFromTheBigBlueSea.new.find
ignorant_jelly = JellyFromAnIsolatedCove.new.find

## what I want:
#
print 'normal_jelly.pacified_with_polar_bear? ';   p normal_jelly.pacified_with_polar_bear?
print 'ignorant_jelly.pacified_with_polar_bear?' ; p ignorant_jelly.pacified_with_polar_bear?

執行:

in JellyFromTheBigBlueSea#find
#<JellyFish:0x007fb5d9045060> extended by Pacifiable
about to define 'pacified_with_polar_bear?' into #<JellyFish:0x007fb5d9045060> of class JellyFish
in JellyFromAnIsolatedCove#find
normal_jelly.pacified_with_polar_bear? true
ignorant_jelly.pacified_with_polar_bear?t.rb:109:in `<main>': undefined method `pacified_with_polar_bear?' for #<JellyFish:0x007fb5d9044d18> (NoMethodError)

首先,你的背景或至少是命名有些奇怪。 上下文不返回RolePlayers。 角色僅存在於上下文中。 角色方法不是也不應該在上下文之外可訪問。 這就是說在紅寶石中處理DCI的標准方法是方法注入。 這種方法並不完美,但到目前為止,最接近純DCI的人都是Ruby。 有一個名為alias_dci的實驗庫可能有所幫助。

編輯現在有一個寶石可以在Ruby中實現無注射DCI。 它基於Marvin的工作,這是第一種支持無注射DCI的語言。 gem被稱為Moby ,可以安裝

gem install Moby

它目前仍處於試驗階段,但已經通過了從fullOO實施DCI示例的煙霧測試

  1. 首先,實例extend調用的方式是有效的(參見帖子的結尾)。

    1. 那么你應該考慮一些這對性能不利的觀點
    2. 最后,根據消息來源 ,它應該按預期工作。 所以我建議你戴上調試帽,試着看看user.extend部分究竟發生了什么。 例如,查看mount_uploader使用Carrierwave::Mount#mount_uploader方法,因為定義了'Uploader'everrides。

1.9.3p327 :001 > class A
1.9.3p327 :002?>   def foo
1.9.3p327 :003?>     '42'
1.9.3p327 :004?>     end
1.9.3p327 :005?>   end
 => nil 
1.9.3p327 :006 > A.new.foo
 => "42" 
1.9.3p327 :011 > module Ext
1.9.3p327 :012?>   def foo
1.9.3p327 :013?>     'ext'
1.9.3p327 :014?>     end
1.9.3p327 :015?>   end
 => nil 
1.9.3p327 :016 > class AFancy
1.9.3p327 :017?>   def call
1.9.3p327 :018?>     a = A.new
1.9.3p327 :019?>     a.extend Ext
1.9.3p327 :020?>     a
1.9.3p327 :021?>     end
1.9.3p327 :022?>   end
 => nil 
1.9.3p327 :023 > a1 = A.new
 => #<A:0x00000000e09b10> 
1.9.3p327 :024 > a2 = AFancy.new.call
 => #<A:0x00000000e17210> 
1.9.3p327 :025 > a3 = A.new
 => #<A:0x00000000e1bd38> 
1.9.3p327 :026 > [a1, a2, a3].map(&:foo)
 => ["42", "ext", "42"]

好的,我想我發現了導致我的問題的原因......

https://github.com/jnicklas/carrierwave/blob/master/lib/carrierwave/mount.rb中 ,在CarrierWave::Mount::Mounter的定義中,有三個對record.class引用。 這正確地引用回主User類,它沒有我加載到用戶的元類中的擴展方法。 所以,我改變了這個: https//gist.github.com/4465172 ,它似乎工作。

如果通常像在CarrierWave文檔中那樣使用,似乎也會繼續工作,所以這也很好。 不過會繼續測試它。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM