Domanda Scopo delle costanti nei moduli di Ruby


Sto avendo un piccolo problema con lo scopo costante nei moduli mixin. Diciamo che ho qualcosa di simile

module Auth

  USER_KEY = "user" unless defined? USER_KEY

  def authorize
    user_id = session[USER_KEY]
  def

end

La costante USER_KEY deve essere predefinita su "utente" a meno che non sia già definita. Ora potrei mescolare questo in un paio di posti, ma in uno di questi posti la USER_KEY deve essere diversa, quindi potremmo avere qualcosa di simile

class ApplicationController < ActionController::Base

  USER_KEY = "my_user"

  include Auth

  def test_auth
    authorize
  end

end

Mi aspetterei che USER_KEY sia "my_user" se usato in autorizzazione, poiché è già definito, ma è ancora "utente", preso dalla definizione di moduli di USER_KEY. Qualcuno ha idea di come ottenere l'autorizzazione a utilizzare la versione di USER_KEY?


44
2018-04-21 23:41


origine


risposte:


Il USER_KEY hai dichiarato (anche condizionatamente) in Auth è globalmente conosciuto come Auth::USER_KEY. Non viene "mescolato" ai moduli inclusi, sebbene includa moduli può fare riferimento alla chiave in modo non pienamente qualificato.

Se vuoi includere ciascun modulo (ad es. ApplicationController) per essere in grado di definire il proprio USER_KEY, prova questo:

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

Se hai intenzione di affrontare tutti questi problemi, potresti anche semplicemente renderlo un metodo di classe:

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

o un dispositivo di accesso a livello di classe:

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
2018-04-22 01:25



Le costanti non hanno portata globale in Ruby. Le costanti possono essere visibili da qualsiasi ambito, ma è necessario specificare dove trovare la costante. Quando si inizia una nuova classe, modulo o def, si inizia un nuovo ambito e se si desidera una costante da un altro ambito, è necessario specificare dove trovarlo.

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

34
2018-04-22 00:33



Ecco una soluzione semplice.

I cambiamenti:

  • Non c'è bisogno di verificare la presenza di USER_KEY.
  • Prova a cercare la costante sul modulo / classe del ricevitore (nel tuo caso sarebbe il controller). Se esiste, usalo, altrimenti usa il modulo / classe di default (vedi sotto per quale è l'impostazione predefinita).

.

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

Spiegazione

Il comportamento che stai vedendo non è specifico delle rotaie, ma è dovuto a dove Ruby cerca le costanti se non è esplicitamente definito tramite :: (quello che chiamo il "default" sopra). Le costanti vengono cercate usando lo "scope lessicale del codice attualmente in esecuzione". Ciò significa che ruby ​​cerca prima la costante nel modulo (o classe) del codice in esecuzione, quindi si sposta verso l'esterno per ogni modulo o classe che lo acclude finché non trova la costante definita su quell'ambito.

Nel tuo controller, chiami authorize. Ma quando authorize è in esecuzione, il codice attualmente in esecuzione è in Auth. Ecco dove vengono cercate le costanti. Se Auth non ha avuto USER_KEYma un modulo che lo racchiude, quindi verrà utilizzato quello che lo racchiude. Esempio:

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

Un caso particolare di questo è l'ambiente di esecuzione di livello superiore, che viene trattato come appartenente alla classe Object.

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

Una trappola è la definizione di un modulo o una classe con l'operatore di ambito (::):

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

Si noti che la costante può essere definita molto più tardi rispetto al metodo definito. La ricerca avviene solo quando si accede a USER_KEY, quindi anche questo funziona:

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
2017-07-26 20:52



Se il tuo progetto è in Rails, o almeno utilizza il ActiveSupport modulo, è possibile ridurre in modo significativo lo zucchero logico necessario:

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

Sono sorpreso che nessuno abbia suggerito questo approccio, visto come lo scenario dell'OP risiedeva all'interno di un'app Rails ...


1
2018-05-12 18:47



C'è una soluzione molto più semplice alla domanda dell'OP che le altre risposte qui rivelano:

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" 

E 'stata la risposta di @james-a-rosen a darmi l'ispirazione per provare questo. Non volevo seguire la sua strada perché avevo diverse costanti che sono condivise tra diverse classi, ognuna con un valore diverso, e il suo metodo sembrava un sacco di digitazione.


0
2017-11-08 22:07