[英]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.
感谢iain和bernardk让我走到这一步 。
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示例的烟雾测试
First of all, the way with instance extend
calls is working (See end of the post). 首先,实例
extend
调用的方式是有效的(参见帖子的结尾)。
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.