Zakres stałych w modułach Ruby

Mam mały problem ze stałym zasięgiem w modułach mixin. Powiedzmy, że mam coś takiego

module Auth

  USER_KEY = "user" unless defined? USER_KEY

  def authorize
    user_id = session[USER_KEY]
  def

end

Stała USER_KEY powinna domyślnie mieć wartość "user", chyba że została już zdefiniowana. Teraz mogę mieszać to w kilku miejscach, ale w jednym z tych miejsc USER_KEY musi być inny, więc możemy mieć coś takiego

class ApplicationController < ActionController::Base

  USER_KEY = "my_user"

  include Auth

  def test_auth
    authorize
  end

end

Spodziewałbym się, że USER_KEY będzie "my_user", gdy zostanie użyty w authorize, ponieważ jest już zdefiniowany, ale nadal jest" user", wzięty z definicji modułów user_key. Czy ktoś ma jakiś pomysł jak uzyskać autoryzację do korzystania z klasowej wersji USER_KEY?

Author: user204078, 2010-04-22

5 answers

USER_KEY zadeklarowany (nawet warunkowo) w Auth jest globalnie znany jako Auth::USER_KEY. Nie jest "mieszany" z włączaniem modułów, chociaż włączenie modułów może odwoływać się do klucza w sposób nie w pełni wykwalifikowany.

Jeśli chcesz, aby każdy moduł zawierający (np. ApplicationController) mógł zdefiniować własne USER_KEY, Spróbuj tego:

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

Jeśli masz zamiar zadawać sobie tyle trudu, możesz równie dobrze zrobić z tego metodę klasową:

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

Lub na poziomie klasy accessor:

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
 52
Author: James A. Rosen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-22 01:25:27

Stałe nie mają globalnego zasięgu w Rubim. Stałe mogą być widoczne z dowolnego zakresu, ale musisz określić, gdzie ma być znaleziona stała. Gdy rozpoczynasz nową klasę, moduł lub def, rozpoczynasz nowy zakres, a jeśli chcesz mieć stałą z innego zakresu, musisz określić, gdzie ją znaleźć.

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
 35
Author: Fred,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-11 21:01:53

Oto proste rozwiązanie.

Zmiany:

  • nie ma potrzeby sprawdzania istnienia USER_KEY.
  • spróbuj poszukać stałej na module/klasie odbiornika (w Twoim przypadku byłby to kontroler). Jeśli istnieje, użyj go, w przeciwnym razie użyj domyślnego modułu/klasy (zobacz poniżej, jaka jest domyślna).

.

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

Wyjaśnienie

Zachowanie, które widzisz, nie jest specyficzne dla rails, ale wynika z tego, gdzie ruby szuka stałe, jeśli nie mają jawnego zasięgu przez :: (co nazywam" domyślną " powyżej). Stałe są sprawdzane za pomocą "zakresu leksykalnego aktualnie wykonywanego kodu". Oznacza to, że ruby najpierw szuka stałej w module (lub klasie) kodu wykonującego, a następnie przesuwa się na zewnątrz do każdego kolejnego modułu (lub klasy), aż znajdzie stałą zdefiniowaną w tym zakresie.

W kontrolerze, dzwonisz authorize. Ale kiedy authorize jest wykonywany, aktualnie wykonywany kod znajduje się w Auth. Więc tam właśnie sprawdzane są stałe. Jeśli Auth nie miał USER_KEY, ale posiada go Moduł obudowy, wtedy zostanie użyty Moduł obudowy. Przykład:

module Outer
  USER_KEY = 'outer_key'
  module Auth
     # code here can access USER_KEY without specifying "Outer::"
     # ...
  end
end

Szczególnym przypadkiem tego jest środowisko wykonawcze najwyższego poziomu, które jest traktowane jako należące do klasy Object.

USER_KEY = 'top-level-key'
module Auth
  # code here can access the top-level USER_KEY (which is actually Object::USER_KEY)
  # ...
end

Jedną z pułapek jest definiowanie modułu lub klasy za pomocą operatora zakresu(::):

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

Zauważ, że stała może być zdefiniowana znacznie później niż metoda jest zdefiniowana. Wyszukiwanie odbywa się tylko wtedy, gdy Dostęp do USER_KEY jest możliwy, więc to też działa:

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
Author: Kelvin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-26 21:06:26

Jeśli twój projekt jest w Rails, lub przynajmniej wykorzystuje moduł ActiveSupport, możesz znacznie zmniejszyć niezbędny cukier logiczny:

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

Jestem zaskoczony, że nikt nie zasugerował takiego podejścia, widząc, jak scenariusz OP rezydował w aplikacji Rails...

 1
Author: Frank Koehl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-12 18:47:40

Jest o wiele prostsze rozwiązanie pytania OP niż inne odpowiedzi tutaj ujawniają:

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" 
To była odpowiedź @ james-a-rosen, która dała mi inspirację do wypróbowania tego. Nie chciałem iść jego trasą, ponieważ miałem kilka stałych, które są dzielone między kilka klas, każda z inną wartością, a jego metoda wyglądała jak dużo pisania.
 0
Author: Derrell Durrett,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-08 22:07:16