简体   繁体   English

扩展ruby本征类以加载CarrierWave

[英]Extending a ruby eigenclass to load CarrierWave

Update : I've simplified my question; 更新 :我简化了我的问题; you can see the full history by checking out my editing revisions . 您可以通过查看我的编辑修订来查看完整的历史记录。 Thanks to iain and bernardk for getting me this far. 感谢iainbernardk让我走到一步


I want to load carrierwave functionality into an instance of my User < ActiveRecord::Base model. 我想将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

Executing: 执行:

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

Results in: 结果是:

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' 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上传器中

I suspect the problem is that mount_uploader in HasAnAvatar is not being invoked properly on the eigenclass for user , such that the uploaders hash isn't populated . 我怀疑问题是mount_uploader中的HasAnAvatar没有在user本征类上正确调用,因此没有填充uploaders哈希

Any thoughts on how to get this working? 有关如何使这个工作的任何想法?


Here is an example Rails app for this issue: https://github.com/neezer/extend_with_avatar_example 以下是此问题的示例Rails应用程序: https//github.com/neezer/extend_with_avatar_example

Here are two ways you can can include a module into an instance (which is to basically extend the instance's eigenclass). 这里有两种方法可以将模块包含到实例中(这基本上是扩展实例的本征类)。 I don't think this is the best answer to your problem though, even if it may answer the question (in part). 我不认为这是你问题的最佳答案,即使它可以回答问题(部分)。

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>

From your edit: 从你的编辑:

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>

From what I know of Ruby classes, once I include a module into a class, ... but not retroactively alter any existing instances of User. 根据我所知的Ruby类,一旦我将一个模块包含到一个类中,......但不能追溯性地改变任何现有的User实例。

On the contrary, an include/extend immediately affects all existing instances, because it is a question of pointer between the class and its superclass. 相反,include / extend会立即影响所有现有实例,因为它是类及其超类之间的指针问题。 See How does Inheritance work in Ruby? 请参阅继承如何在Ruby中工作? and also the links inside. 还有里面的链接。

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

Execution (Ruby 1.9.2) : 执行(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

So included methods immediately become available to all existing instances. 因此包含的方法立即可用于所有现有实例。
Why does user.avatar answer nil ? 为什么user.avatar回答为nil? Because instance variables belong to ... single instances. 因为实例变量属于......单个实例。 See Why are symbols in Ruby not thought of as a type of variable? 请参阅为什么Ruby中的符号不​​被视为一种变量? In this case, the old user was not assigned an avatar. 在这种情况下,旧用户未被分配头像。

But why do you get user2.avatar.class #=> AvatarUploader . 但是为什么你会得到user2.avatar.class #=> AvatarUploader I suppose that, behind the scene, base.mount_uploader :avatar, AvatarUploader does something such that :avatar is not an accessor of a corresponding instance variable, or defines an initialize method which starts to set this variable into new instances. 我想,在场景后面, base.mount_uploader :avatar, AvatarUploader做了这样的事情:avatar不是相应实例变量的访问者,或者定义了一个初始化方法,它开始将这个变量设置为新的实例。


Here is a solution for the second example : 这是第二个例子的解决方案:

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  

Execution : 执行:

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


About jelly.class.pacified_with(:polar_bear) : the mistake was to call class , which answers the class JellyFish ; 关于jelly.class.pacified_with(:polar_bear) :错误是调用class ,它回答类JellyFish ; what you want is the singleton class of the instance object jelly. 你想要的是实例对象果冻的单例类。 Once a method is defined in the singleton class of an object, you can send it directly to the object. 一旦在对象的单例类中定义了方法,就可以将其直接发送到对象。 The same applies to classes, which are instances of Class. 这同样适用于类,它们是Class的实例。 Once a method is defined in the singleton class of a class, you can send it directly to the class. 一旦在类的单例类中定义了一个方法,就可以将它直接发送给类。 We call them class methods, they are actually instance methods of the singleton class of the class. 我们称它们为类方法,它们实际上是类的单例类的实例方法。 Ouf ! 哇!


Last OR : as explained, if you extend the class, it is valid for all existing instances. Last OR:如上所述,如果扩展类,它对所有现有实例都有效。 Thus you have to extend individual instances : 因此,您必须扩展单个实例:

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?

Execution : 执行:

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)

Firstly there's something odd about your contexts or at least the naming. 首先,你的背景或至少是命名有些奇怪。 Context do not return RolePlayers. 上下文不返回RolePlayers。 Roles exist only inside a context. 角色仅存在于上下文中。 The role methods are not and should not be accessible outside the context. 角色方法不是也不应该在上下文之外可访问。 That said the standard way of dealing with DCI in ruby is method injection. 这就是说在红宝石中处理DCI的标准方法是方法注入。 This approach is not flawless but is so far the closest to pure DCI anyone have come in Ruby. 这种方法并不完美,但到目前为止,最接近纯DCI的人都是Ruby。 There's an experimental library called alias_dci that might help. 有一个名为alias_dci的实验库可能有所帮助。

EDIT There now is a gem that makes injectionless DCI possible in Ruby. 编辑现在有一个宝石可以在Ruby中实现无注射DCI。 It's based on the work gone into Marvin , the first language to support injectionless DCI. 它基于Marvin的工作,这是第一种支持无注射DCI的语言。 The gem is called Moby and can be installed with gem被称为Moby ,可以安装

gem install Moby

it's currently still somewhat experimental but the smoke test of being able to implement the DCI examples from fullOO has been passed 它目前仍处于试验阶段,但已经通过了从fullOO实施DCI示例的烟雾测试

  1. First of all, the way with instance extend calls is working (See end of the post). 首先,实例extend调用的方式是有效的(参见帖子的结尾)。

    1. Then you should take into account some opinions that this is bad for performance 那么你应该考虑一些这对性能不利的观点
    2. And at last, according to sources , it should work as expected. 最后,根据消息来源 ,它应该按预期工作。 So I suggest you to wear your debug hat and try to see what exactly happens on user.extend part. 所以我建议你戴上调试帽,试着看看user.extend部分究竟发生了什么。 For example, see if mount_uploader uses Carrierwave::Mount#mount_uploader method, as there are defined 'Uploader' everrides. 例如,查看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"]

Ok, I think I found out what was causing my issues... 好的,我想我发现了导致我的问题的原因......

In https://github.com/jnicklas/carrierwave/blob/master/lib/carrierwave/mount.rb , in the definition of CarrierWave::Mount::Mounter , there are three references to record.class . https://github.com/jnicklas/carrierwave/blob/master/lib/carrierwave/mount.rb中 ,在CarrierWave::Mount::Mounter的定义中,有三个对record.class引用。 This rightly references back to the main User class, which doesn't have the extended methods I loaded into the user's metaclass. 这正确地引用回主User类,它没有我加载到用户的元类中的扩展方法。 So, I changed those to this: https://gist.github.com/4465172 , and it seemed to work. 所以,我改变了这个: https//gist.github.com/4465172 ,它似乎工作。

Also seems to continue to work if used normally like in the CarrierWave docs, so that's good too. 如果通常像在CarrierWave文档中那样使用,似乎也会继续工作,所以这也很好。 Will continue testing it, though. 不过会继续测试它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM