web-dev-qa-db-ja.com

Rubyモジュールの定数の範囲

ミックスインモジュールの一定のスコープに少し問題があります。このようなものがあるとしましょう

module Auth

  USER_KEY = "user" unless defined? USER_KEY

  def authorize
    user_id = session[USER_KEY]
  def

end

USER_KEY定数は、既に定義されていない限り、デフォルトで「user」になります。今、私はこれをいくつかの場所にミックスするかもしれませんが、それらの場所の1つではUSER_KEYが異なる必要があるので、このようなものがあるかもしれません

class ApplicationController < ActionController::Base

  USER_KEY = "my_user"

  include Auth

  def test_auth
    authorize
  end

end

USER_KEYは既に定義されているため、authorizeで使用する場合は「my_user」になると予想されますが、USER_KEYのモジュール定義から取得した「ユーザー」のままです。 USER_KEYのクラスバージョンの使用を許可する方法はありますか?

53
user204078

Authで(条件付きでも)宣言したUSER_KEYは、Auth::USER_KEYとしてグローバルに知られています。モジュールを含めることは「混在」しませんが、モジュールを含めることはできますcan完全に修飾されていない方法でキーを参照します。

各インクルードモジュール(例:ApplicationController)で独自のUSER_KEYを定義できるようにするには、次を試してください。

module Auth
  DEFAULT_USER_KEY = 'user'
  def self.included(base)
    unless base.const_defined?(:USER_KEY)
      base.const_set :USER_KEY, Auth::DEFAULT_USER_KEY
    end
  end
  def authorize
    user_id = session[self.class.const_get(:USER_KEY)]
  end
end

class ApplicationController < ActionController::Base
  USER_KEY = 'my_user'
  include Auth
end

ただし、このすべての問題に取り組む場合は、単にクラスメソッドにすることもできます。

module Auth
  DEFAULT_USER_KEY = 'user'
  def self.included(base)
    base.extend Auth::ClassMethods
    base.send :include, Auth::InstanceMethods
  end
  module ClassMethods
    def user_key
      Auth::DEFAULT_USER_KEY
    end
  end
  module InstanceMethods
    def authorize
      user_id = session[self.class.user_key]
    end
  end
end

class ApplicationController < ActionController::Base
  def self.user_key
    'my_user'
  end
end

またはクラスレベルのアクセサー:

module Auth
  DEFAULT_USER_KEY = 'user'
  def self.included(base)
    base.send :attr_accessor :user_key unless base.respond_to?(:user_key=)
    base.user_key ||= Auth::DEFAULT_USER_KEY
  end
  def authorize
    user_id = session[self.class.user_key]
  end
end

class ApplicationController < ActionController::Base
  include Auth
  self.user_key = 'my_user'
end
58
James A. Rosen

定数は、Rubyではグローバルスコープを持ちません。定数はどのスコープからでも表示できますが、定数の検索場所を指定する必要があります。新しいクラス、モジュール、またはdefを開始するとき、新しいスコープを開始します。別のスコープから定数が必要な場合は、どこで見つけるかを指定する必要があります。

X = 0
class C
  X = 1
  module M
    X = 2
    class D
      X = 3
      puts X          # => 3
      puts C::X       # => 1
      puts C::M::X    # => 2
      puts M::X       # => 2
      puts ::X        # => 0
    end
  end
end
40
Fred

これが簡単な解決策です。

変更点:

  • USER_KEYの存在を確認する必要はありません。
  • 受信者のモジュール/クラスで定数を検索してみてください(あなたの場合はコントローラーになります)。存在する場合はそれを使用し、存在しない場合はデフォルトのモジュール/クラスを使用します(デフォルトについては以下を参照してください)。

module Auth
  USER_KEY = "user"

  def authorize
    user_key = self.class.const_defined?(:USER_KEY) ? self.class::USER_KEY : USER_KEY
    user_id = session[user_key]
  def
end

説明

あなたが見ている動作はRailsに固有のものではありませんが、Rubyは::を介して明示的にスコープされていない場合、定数を探します)。「現在実行中のコードのレキシカルスコープ」を使用して定数が検索されます。つまり、Rubyは最初に実行中のコードのモジュール(またはクラス)で定数を探し、次に外側に移動しますそのスコープで定義された定数が見つかるまで、連続する各囲みモジュール(またはクラス)。

コントローラーで、authorizeを呼び出します。ただし、authorizeが実行されている場合、現在実行中のコードはAuthにあります。そのため、定数が検索されます。 AuthがUSER_KEYを持っていなかったが、それを囲むモジュールが持っている場合、それを囲むものが使用されます。例:

module Outer
  USER_KEY = 'outer_key'
  module Auth
     # code here can access USER_KEY without specifying "Outer::"
     # ...
  end
end

この特殊なケースは、クラスObjectに属するものとして扱われるトップレベルの実行環境です。

USER_KEY = 'top-level-key'
module Auth
  # code here can access the top-level USER_KEY (which is actually Object::USER_KEY)
  # ...
end

1つの落とし穴は、スコープ演算子(::)を使用してモジュールまたはクラスを定義することです。

module Outer
  USER_KEY = 'outer_key'
end
module Outer::Auth
  # methods here won't be able to use USER_KEY,
  # because Outer isn't lexically enclosing Auth.
  # ...
end

定数は、メソッドが定義されるよりもずっと後で定義できることに注意してください。 USER_KEYにアクセスしたときにのみ検索が行われるため、これも機能します。

module Auth
  # don't define USER_KEY yet
  # ...
end

# you can't call authorize here or you'll get an uninitialized constant error

Auth::USER_KEY = 'user'

# now you can call authorize.
12
Kelvin

プロジェクトがRailsにある場合、または少なくともActiveSupportモジュールを利用している場合、必要なロジックシュガーを大幅に削減できます。

module Auth

  extend ActiveSupport::Concern

  included do
    # set a global default value
    unless self.const_defined?(:USER_KEY)
      self.const_set :USER_KEY, 'module_user'
    end
  end

end

class ApplicationController < ActionController::Base
  # set an application default value
  USER_KEY = "default_user"
  include Auth  
end

class SomeController < ApplicationController
  # set a value unique to a specific controller
  USER_KEY = "specific_user"
end

OPのシナリオがどのようにRails= app ...

2
Frank Koehl

OPの質問には、ここでの他の回答が明らかにするよりもはるかに簡単な解決策があります。

module Foo
  THIS_CONST = 'foo'

  def show_const
    self.class::THIS_CONST
  end
end

class Bar
  include Foo

  THIS_CONST ='bar'
  def test_it
    show_const
  end
end

class Baz
  include Foo

  def test_it
    show_const
  end
end

2.3.1 :004 > r = Bar.new
 => #<Bar:0x000000008be2c8> 
2.3.1 :005 > r.test_it
 => "bar" 
2.3.1 :006 > z = Baz.new
 => #<Baz:0x000000008658a8> 
2.3.1 :007 > z.test_it
 => "foo" 

@ james-a-rosenの答えが、これを試すためのインスピレーションを与えてくれました。私はいくつかのクラスで共有され、それぞれ異なる値を持ついくつかの定数があり、彼の方法は多くの入力のように見えたため、彼のルートに行きたくありませんでした。

0
Derrell Durrett