1

更新:質問を簡略化しました。私の編集リビジョンをチェックアウトすることで、完全な履歴を見ることができます。私をここまで導いてくれたiainbernarkに感謝します。


モデルのインスタンスに 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_uploaderinHasAnAvatarが の固有クラスで適切に呼び出されていないためuserハッシュが入力されていないことにあると思われます。uploaders

これを機能させる方法について何か考えはありますか?


この問題の Rails アプリの例を次に示します: https://github.com/neezer/extend_with_avatar_example

4

5 に答える 5

2

モジュールをインスタンスに含めることができる 2 つの方法を次に示します (これは、基本的にインスタンスの固有クラスを拡張することです)。たとえそれが質問に(部分的に)答えるかもしれないとしても、これがあなたの問題に対する最良の答えだとは思いません。

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>
于 2013-01-05T10:34:54.370 に答える
1

私がRubyクラスについて知っていることから、モジュールをクラスに含めると...ただし、Userの既存のインスタンスをさかのぼって変更することはありません。

それどころか、include / extendsは、クラスとそのスーパークラスの間のポインタの問題であるため、既存のすべてのインスタンスにすぐに影響します。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.avatarnilと答えるのですか?インスタンス変数は...単一のインスタンスに属しているためです。Rubyのシンボルが変数の一種と見なされないのはなぜですか?を参照してください。 この場合、古いユーザーにはアバターが割り当てられていません。

しかし、なぜあなたは得るのですかuser2.avatar.class #=> AvatarUploader。舞台裏では、base.mount_uploader :avatar, AvatarUploader:avatarが対応するインスタンス変数のアクセサーではない、またはこの変数を新しいインスタンスに設定し始める初期化メソッドを定義するようなことをしていると思います。


2番目の例の解決策は次のとおりです。

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のシングルトンクラスです。オブジェクトのシングルトンクラスでメソッドが定義されると、それをオブジェクトに直接送信できます。クラスのインスタンスであるクラスにも同じことが当てはまります。クラスのシングルトンクラスでメソッドが定義されると、それをクラスに直接送信できます。それらをクラスメソッドと呼びます。実際には、クラスのシングルトンクラスのインスタンスメソッドです。Ouf!


最後の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)
于 2013-01-05T13:51:02.703 に答える
0
  1. まず、インスタンスextend呼び出しの方法が機能しています(投稿の最後を参照)。

    1. 次に、これはパフォーマンスに悪いといういくつかの意見を考慮に入れる必要があります
    2. そしてついに、情報筋によると、それは期待通りに機能するはずです。したがって、デバッグ用の帽子をかぶって、部分的に正確に何が起こるかを確認することをお勧めしますuser.extend。たとえば、「アップローダー」エバーライドが定義されているため、メソッドをmount_uploader使用するかどうかを確認します。Carrierwave::Mount#mount_uploader

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"]
于 2013-01-05T10:30:15.680 に答える
0

まず、あなたの文脈、または少なくとも命名について奇妙なことがあります。Context は RolePlayers を返しません。ロールはコンテキスト内にのみ存在します。ロール メソッドは、コンテキスト外ではアクセスできませんし、アクセスするべきではありません。つまり、Ruby で DCI を処理する標準的な方法はメソッド インジェクションです。このアプローチは完璧というわけではありませんが、これまでのところ、Ruby に登場した誰もが純粋な DCI に最も近いものです。役立つかもしれないalias_dciと呼ばれる実験的なライブラリがあります。

編集 RubyでインジェクションレスDCIを可能にするgemがあります。これは、インジェクションレス DCI をサポートする最初の言語であるMarvinに組み込まれた作業に基づいています。このgemはMobyと呼ばれ、次のようにインストールできます

gem install Moby

現在はまだ実験段階ですが、 fullOOから DCI の例を実装できるスモーク テストに合格しています。

于 2013-01-05T10:13:17.203 に答える
0

わかりました、問題の原因がわかったと思います...

https://github.com/jnicklas/carrierwave/blob/master/lib/carrierwave/mount.rbの の定義には、CarrierWave::Mount::Mounterへの参照が 3 つありますrecord.classUserこれは、ユーザーのメタクラスにロードした拡張メソッドを持たないメイン クラスを正しく参照しています。だから、私はこれをhttps://gist.github.com/4465172に変更しましたが、うまくいくようでした。

また、CarrierWaveのドキュメントのように普通に使えば動き続けるようですので、それもいいですね。ただし、それをテストし続けます。

于 2013-01-06T04:30:28.973 に答える