Подтвердить что ты не робот

Объем констант в 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?

4b9b3361

Ответ 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, который дал мне вдохновение попробовать это. Я не хотел идти своим путем, потому что у меня было несколько констант, которые были разделены между несколькими классами, каждый из которых имел другое значение, и его метод выглядел как много набрав.