Объем констант в Ruby-модулях
У меня небольшая проблема с постоянной областью в модулях mixin. Скажем, у меня есть что-то вроде этого
module Auth
USER_KEY = "user" unless defined? USER_KEY
def authorize
user_id = session[USER_KEY]
def
end
Константа USER_KEY должна по умолчанию "пользователь", если она уже не определена. Теперь я могу смешать это в нескольких местах, но в одном из этих мест USER_KEY должен быть другим, поэтому у нас может быть что-то вроде этого
class ApplicationController < ActionController::Base
USER_KEY = "my_user"
include Auth
def test_auth
authorize
end
end
Я бы ожидал, что USER_KEY будет "my_user" при использовании в authorize, поскольку он уже определен, но он все еще "пользователь", взятый из определения модулей USER_KEY. Кто-нибудь знает, как получить авторизацию для использования версии классов USER_KEY?
Ответы
Ответ 1
USER_KEY
, который вы объявили (даже условно) в Auth
, глобально известен как Auth::USER_KEY
. Он не "смешивается" с включенными модулями, хотя встроенные модули могут ссылаться на ключ не полностью соответствующим образом.
Если вы хотите, чтобы каждый включенный модуль (например, 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
Ответ 2
Константы не имеют глобального масштаба в 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
Ответ 3
Здесь простое решение.
Изменения:
- Не нужно проверять наличие
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
Объяснение
Поведение, которое вы видите, не относится к рельсам, но связано с тем, что 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
Одна ошибка - это определение модуля или класса с помощью оператора обзора (::
):
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.
Ответ 4
Если ваш проект находится в 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...
Ответ 5
Там гораздо более простое решение вопроса ОП, чем другие ответы здесь:
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, который дал мне вдохновение попробовать это. Я не хотел идти своим путем, потому что у меня было несколько констант, которые были разделены между несколькими классами, каждый из которых имел другое значение, и его метод выглядел как много набрав.