扩展一个ruby特征类来加载CarrierWave



更新 :我已经简化了我的问题;您可以通过查看我的编辑修订来查看完整的历史记录。感谢iain和bernardk让我走了这么远。


我想将载波功能加载到我的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: undefined method 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 uploader'

我怀疑问题是HasAnAvatar中的mount_uploader没有在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类的了解,一旦我将一个模块包含到类,…但不能追溯性地更改任何现有实例用户。

相反,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类;您需要的是实例对象jelly的单例类。一旦在对象的单例类中定义了方法,就可以将其直接发送给对象。这同样适用于类,它们是Class的实例。一旦在类的单例类中定义了方法,就可以直接将其发送到类中。我们称它们为类方法,它们实际上是类的单例类的实例方法。Ouf !


最后或:如前所述,如果扩展类,它对所有现有实例都有效。因此,您必须扩展单个实例:

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)
  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文档中正常使用,

似乎也可以继续工作,所以这也很好。我将继续测试它

首先,您的上下文或至少命名有些奇怪。Context不返回RolePlayers。角色只存在于一个上下文中。角色方法不能也不应该在上下文之外访问。也就是说,在ruby中处理DCI的标准方法是方法注入。这种方法并不是完美无缺的,但它是迄今为止Ruby中最接近纯DCI的方法。有一个名为alias_dci的实验库可能会有所帮助。

编辑现在有一个gem使Ruby中的无注入DCI成为可能。它基于Marvin的工作,这是第一种支持无注入DCI的语言。这个gem名为Moby,可以通过

命令安装。
gem install Moby

它目前仍然是实验性的,但是能够实现来自fullloo的DCI示例的冒烟测试已经通过了

相关内容

  • 没有找到相关文章

最新更新