ミックスインモジュールの一定のスコープに少し問題があります。このようなものがあるとしましょう
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のクラスバージョンの使用を許可する方法はありますか?
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
定数は、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
これが簡単な解決策です。
変更点:
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.
プロジェクトが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 ...
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の答えが、これを試すためのインスピレーションを与えてくれました。私はいくつかのクラスで共有され、それぞれ異なる値を持ついくつかの定数があり、彼の方法は多くの入力のように見えたため、彼のルートに行きたくありませんでした。