Область действия констант в модулях Ruby - PullRequest
52 голосов
/ 22 апреля 2010

У меня небольшая проблема с постоянной областью действия в модулях mixin.Допустим, у меня есть что-то вроде этого

module Auth

  USER_KEY = "user" unless defined? USER_KEY

  def authorize
    user_id = session[USER_KEY]
  def

end

Константа USER_KEY должна по умолчанию "user", если она уже не определена.Теперь я могу смешать это в нескольких местах, но в одном из этих мест USER_KEY должен быть другим, поэтому у нас может быть что-то подобноепри использовании в авторизации, поскольку он уже определен, но все еще "пользователь", взятый из определения модулей USER_KEY.Кто-нибудь знает, как получить разрешение на использование версии классов USER_KEY?

Ответы [ 5 ]

58 голосов
/ 22 апреля 2010

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
40 голосов
/ 22 апреля 2010

Константы не имеют глобальной области видимости в 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
12 голосов
/ 27 июля 2012

Вот простое решение.

Изменения:

  • Нет необходимости проверять наличие 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

Одна ловушка - определение модуля или класса с помощью оператора определения объема (::):

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.
2 голосов
/ 12 мая 2014

Если ваш проект находится в 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

Я удивлен, что никто не предложил такой подход, посколькукак сценарий ОП находился в приложении Rails ...

0 голосов
/ 09 ноября 2017

Существует гораздо более простое решение вопроса ОП, чем другие ответы здесь:

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

...