it-swarm.com.ru

Область действия констант в модулях Ruby

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

module Auth

  USER_KEY = "user" unless defined? USER_KEY

  def authorize
    user_id = session[USER_KEY]
  def

end

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

class ApplicationController < ActionController::Base

  USER_KEY = "my_user"

  include Auth

  def test_auth
    authorize
  end

end

Я ожидал бы, что USER_KEY будет "my_user" при использовании в authorize, поскольку он уже определен, но все еще "user", взятый из определения модулей USER_KEY. У кого-нибудь есть идеи, как получить разрешение на использование версии классов USER_KEY?

52
user204078

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
53
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
38
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

Одним из подводных камней является определение модуля или класса с помощью оператора scoping (::):

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 ...

1
Frank Koehl

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

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