Come implementare Enums in Ruby?


324

Qual è il modo migliore per attuare il linguaggio enumatico in Ruby? Sto cercando qualcosa che posso usare (quasi) come gli enumerati Java / C #.


7
@auramo, bella domanda e ottima scelta per la migliore risposta. Lo adori o lo odi, non ottieni alcuna sicurezza del tipo e (almeno in Ruby) nessuna sicurezza dell'errore di battitura. Ero elettrizzato quando ho scoperto gli enum in C # e successivamente in Java (scegli un valore, ma da questi!), Ruby non fornisce un modo reale di farlo in ogni caso.
Dan Rosenstark l'

2
Il problema con questa domanda è che gli enumerati Java e C # sono cose drammaticamente diverse. Un membro enum Java è un'istanza di oggetto e un singleton. Un enum Java può avere un costruttore. Al contrario, gli enumerati C # si basano su valori primitivi. Quale comportamento sta cercando l'interrogante? Mentre è probabile che il caso C # sia voluto, Java viene esplicitamente menzionato, piuttosto che C o C ++, quindi c'è qualche dubbio. Per quanto riguarda suggerire che non c'è modo di essere "sicuri" in Ruby, è chiaramente falso, ma devi implementare qualcosa di più sofisticato.
user1164178

Risposte:


319

Due strade. Simboli ( :foonotazione) o costanti ( FOOnotazione).

I simboli sono appropriati quando si desidera migliorare la leggibilità senza sporcare il codice con stringhe letterali.

postal_code[:minnesota] = "MN"
postal_code[:new_york] = "NY"

Le costanti sono appropriate quando si ha un valore sottostante importante. Dichiarare semplicemente un modulo per contenere le costanti e quindi dichiarare le costanti al suo interno.

module Foo
  BAR = 1
  BAZ = 2
  BIZ = 4
end

flags = Foo::BAR | Foo::BAZ # flags = 3

2
E se questi enum fossero troppo archiviati nel database? La notazione dei simboli funzionerà? Dubito ...
Phương Nguyễn l'

Userei l'approccio delle costanti se stavo salvando su un database. Ovviamente allora devi fare una sorta di ricerca quando tira indietro i dati dal DB. È inoltre possibile utilizzare qualcosa di simile :minnesota.to_sdurante il salvataggio in un database per salvare la versione stringa del simbolo. Rails, credo, ha alcuni metodi di supporto per affrontare alcuni di questi.
mlibby,

7
Un modulo non sarebbe meglio per raggruppare le costanti, dato che non ne farai nessuna istanza?
Thom

3
Solo un commento Ruby è un po 'preoccupato per le convenzioni di denominazione, ma non è davvero ovvio su di esse fino a quando non le inciampi. I nomi degli enum devono essere tutti maiuscoli e la prima lettera del nome del modulo deve essere maiuscola affinché ruby ​​sappia che il modulo è un modulo di costanti.
Rokujolady,

3
Non del tutto vero. La prima lettera della costante deve essere in maiuscolo, ma non tutte le lettere devono esserlo. Questa è una questione di preferenza convenzionale. Ad esempio, tutti i nomi dei moduli e i nomi delle classi sono in realtà anche costanti.
Michael Brown,

59

Sono sorpreso che nessuno abbia offerto qualcosa di simile al seguente (raccolto dalla gemma RAPI ):

class Enum

  private

  def self.enum_attr(name, num)
    name = name.to_s

    define_method(name + '?') do
      @attrs & num != 0
    end

    define_method(name + '=') do |set|
      if set
        @attrs |= num
      else
        @attrs &= ~num
      end
    end
  end

  public

  def initialize(attrs = 0)
    @attrs = attrs
  end

  def to_i
    @attrs
  end
end

Quale può essere usato così:

class FileAttributes < Enum
  enum_attr :readonly,       0x0001
  enum_attr :hidden,         0x0002
  enum_attr :system,         0x0004
  enum_attr :directory,      0x0010
  enum_attr :archive,        0x0020
  enum_attr :in_rom,         0x0040
  enum_attr :normal,         0x0080
  enum_attr :temporary,      0x0100
  enum_attr :sparse,         0x0200
  enum_attr :reparse_point,  0x0400
  enum_attr :compressed,     0x0800
  enum_attr :rom_module,     0x2000
end

Esempio:

>> example = FileAttributes.new(3)
=> #<FileAttributes:0x629d90 @attrs=3>
>> example.readonly?
=> true
>> example.hidden?
=> true
>> example.system?
=> false
>> example.system = true
=> true
>> example.system?
=> true
>> example.to_i
=> 7

Questo funziona bene in scenari di database, o quando si ha a che fare con costanti / enumerazioni in stile C (come nel caso dell'utilizzo di FFI , di cui RAPI fa ampio uso).

Inoltre, non devi preoccuparti di errori di battitura che causano errori silenziosi, come faresti con l'uso di una soluzione di tipo hash.


1
È un ottimo modo per risolvere quel particolare problema, ma il motivo per cui nessuno ha suggerito che probabilmente ha a che fare con il fatto che non è molto simile agli enumerati C # / Java.
mlibby,

1
Questo è un po 'incompleto, ma offre un buon suggerimento su come implementare soluzioni con un approccio dinamico. Assomiglia in qualche modo a un enumerazione C # con il set FlagsAttribute, ma come il simbolo / soluzioni basate sulla costante sopra, è una risposta di molti. Il problema è la domanda originale, che è confusa nelle sue intenzioni (C # e Java non sono intercambiabili). Esistono molti modi per classificare gli oggetti in Ruby; la selezione di quella giusta dipende dal problema da risolvere. Le funzioni di replica slava di cui non hai bisogno sono sbagliate. La risposta corretta deve dipendere dal contesto.
user1164178

52

Il modo più idiomatico per farlo è usare i simboli. Ad esempio, anziché:

enum {
  FOO,
  BAR,
  BAZ
}

myFunc(FOO);

... puoi semplicemente usare i simboli:

# You don't actually need to declare these, of course--this is
# just to show you what symbols look like.
:foo
:bar
:baz

my_func(:foo)

Questo è un po 'più aperto rispetto agli enum, ma si adatta bene allo spirito di Ruby.

Anche i simboli funzionano molto bene. Il confronto tra due simboli per l'uguaglianza, ad esempio, è molto più veloce del confronto tra due stringhe.


107
Quindi lo spirito di Ruby è: "Typos compilerà"
mxcl

82
I framework Ruby popolari fanno molto affidamento sulla metaprogrammazione del tempo di esecuzione e l'esecuzione di un eccessivo controllo del tempo di caricamento eliminerebbe la maggior parte del potere espressivo di Ruby. Per evitare problemi, la maggior parte dei programmatori Ruby pratica la progettazione guidata dai test, che troverà non solo errori di battitura, ma anche errori logici.
emk

10
@yar: il design del linguaggio è una serie di compromessi e le funzionalità del linguaggio interagiscono. Se vuoi un linguaggio buono e altamente dinamico, vai con Ruby, scrivi prima i test unitari e segui lo spirito della lingua. :-) Se non è quello che stai cercando, ci sono dozzine di altre lingue eccellenti là fuori, ognuna delle quali rende diversi compromessi.
emk

10
@emk, sono d'accordo, ma il mio problema personale è che mi sento abbastanza a mio agio in Ruby, ma non mi sento a mio agio nel refactoring in Ruby. E ora che ho iniziato a scrivere unit test (finalmente), mi rendo conto che non sono una panacea: la mia ipotesi è 1) che il codice Ruby non viene massicciamente riformattato che spesso, in pratica e 2) Ruby non è la fine di punta in termini di linguaggi dinamici, proprio perché è difficile rifattorizzare automaticamente. Vedi la mia domanda 2317579 che è stata rilevata, stranamente, dalla gente di Smalltalk.
Dan Rosenstark,

4
Sì, ma usare quelle stringhe non sarebbe nello spirito del linguaggio C #, è semplicemente una cattiva pratica.
Ed S.

38

Uso il seguente approccio:

class MyClass
  MY_ENUM = [MY_VALUE_1 = 'value1', MY_VALUE_2 = 'value2']
end

Mi piace per i seguenti vantaggi:

  1. Raggruppa i valori visivamente come un tutto
  2. Esegue un controllo del tempo di compilazione (in contrasto con il solo utilizzo di simboli)
  3. Posso facilmente accedere all'elenco di tutti i possibili valori: solo MY_ENUM
  4. Posso accedere facilmente a valori distinti: MY_VALUE_1
  5. Può avere valori di qualsiasi tipo, non solo Symbol

I simboli potrebbero essere migliori perché non devi scrivere il nome della classe esterna, se lo stai usando in un'altra classe ( MyClass::MY_VALUE_1)


4
Penso che questa sia la risposta migliore. Funzionalità, sintassi e sovraccarico di codice minimo si avvicinano a Java / C #. Inoltre puoi annidare le definizioni anche più in profondità di un livello e recuperare comunque tutti i valori con MyClass :: MY_ENUM.flatten. Come nota a margine userei qui i nomi in maiuscolo come lo standard per le costanti in Ruby. MyClass :: MyEnum potrebbe essere scambiato per un riferimento a una sottoclasse.
Janosch,

@ Janosch, ho aggiornato i nomi. grazie per il suggerimento
Alexey,

Sono ancora un po 'confuso e il link 410'd (no, non 404). Potresti fornire esempi su come sarebbe usato questo enum?
Shelvacu,

17

Se si utilizza Rails 4.2 o versione successiva, è possibile utilizzare gli enum Rails.

Rails ora ha enumerazioni di default senza la necessità di includere gemme.

Questo è molto simile (e più con funzionalità) a enumerazioni Java, C ++.

Citato da http://edgeapi.rubyonrails.org/classes/ActiveRecord/Enum.html :

class Conversation < ActiveRecord::Base
  enum status: [ :active, :archived ]
end

# conversation.update! status: 0
conversation.active!
conversation.active? # => true
conversation.status  # => "active"

# conversation.update! status: 1
conversation.archived!
conversation.archived? # => true
conversation.status    # => "archived"

# conversation.update! status: 1
conversation.status = "archived"

# conversation.update! status: nil
conversation.status = nil
conversation.status.nil? # => true
conversation.status      # => nil

7
Come hai detto, non è utile se l'OP non utilizza Rails (o più precisamente l'oggetto non è di tipo ActiveRecord). Spiegare solo il mio downvote è tutto.
Ger,

2
Questi non sono enumerati in Ruby, è un'interfaccia ActiveRecord per Enums nel tuo database. Non è una soluzione generalizzabile che può essere applicata in qualsiasi altro caso d'uso.
Adam Lassek,

L'ho già detto nella mia risposta.
vedant

Questa è la migliore risposta IFF utilizzando Rails.
theUtherSide

Non mi piace perché deve essere archiviato in un database Rails (per funzionare) e perché consente di creare molte istanze della Conversationclasse - credo che debba consentire solo 1 istanza.
prograils

8

Questo è il mio approccio alle enumerazioni in Ruby. Stavo andando in breve e dolce, non necessariamente il più C-like. qualche idea?

module Kernel
  def enum(values)
    Module.new do |mod|
      values.each_with_index{ |v,i| mod.const_set(v.to_s.capitalize, 2**i) }

      def mod.inspect
        "#{self.name} {#{self.constants.join(', ')}}"
      end
    end
  end
end

States = enum %w(Draft Published Trashed)
=> States {Draft, Published, Trashed} 

States::Draft
=> 1

States::Published
=> 2

States::Trashed
=> 4

States::Draft | States::Trashed
=> 3


8

Forse il miglior approccio leggero sarebbe

module MyConstants
  ABC = Class.new
  DEF = Class.new
  GHI = Class.new
end

In questo modo i valori hanno nomi associati, come in Java / C #:

MyConstants::ABC
=> MyConstants::ABC

Per ottenere tutti i valori, puoi farlo

MyConstants.constants
=> [:ABC, :DEF, :GHI] 

Se vuoi il valore ordinale di un enum, puoi farlo

MyConstants.constants.index :GHI
=> 2

1
IMHO questo replica molto da vicino l'uso e lo scopo (tipo di sicurezza) di Java, inoltre, in termini di preferenza, le costanti possono essere definite in questo modo:class ABC; end
wiki

8

So che è passato molto tempo da quando il ragazzo ha pubblicato questa domanda, ma ho avuto la stessa domanda e questo post non mi ha dato la risposta. Volevo un modo semplice per vedere cosa rappresenta il numero, un facile confronto e soprattutto il supporto ActiveRecord per la ricerca usando la colonna che rappresenta l'enum.

Non ho trovato nulla, quindi ho realizzato un'impressionante implementazione chiamata yinum che ha permesso tutto ciò che stavo cercando. Ho fatto tonnellate di specifiche, quindi sono abbastanza sicuro che sia sicuro.

Alcune caratteristiche di esempio:

COLORS = Enum.new(:COLORS, :red => 1, :green => 2, :blue => 3)
=> COLORS(:red => 1, :green => 2, :blue => 3)
COLORS.red == 1 && COLORS.red == :red
=> true

class Car < ActiveRecord::Base    
  attr_enum :color, :COLORS, :red => 1, :black => 2
end
car = Car.new
car.color = :red / "red" / 1 / "1"
car.color
=> Car::COLORS.red
car.color.black?
=> false
Car.red.to_sql
=> "SELECT `cars`.* FROM `cars` WHERE `cars`.`color` = 1"
Car.last.red?
=> true

5

Se sei preoccupato per errori di battitura con simboli, assicurati che il tuo codice generi un'eccezione quando accedi a un valore con una chiave inesistente. Puoi farlo usando fetchinvece di []:

my_value = my_hash.fetch(:key)

o facendo in modo che l'hash sollevi un'eccezione per impostazione predefinita se si fornisce una chiave inesistente:

my_hash = Hash.new do |hash, key|
  raise "You tried to access using #{key.inspect} when the only keys we have are #{hash.keys.inspect}"
end

Se l'hash esiste già, puoi aggiungere il comportamento di aumento delle eccezioni:

my_hash = Hash[[[1,2]]]
my_hash.default_proc = proc do |hash, key|
  raise "You tried to access using #{key.inspect} when the only keys we have are #{hash.keys.inspect}"
end

Normalmente, non devi preoccuparti della sicurezza dell'errore di battitura con le costanti. Se si scrive erroneamente un nome costante, si solleverà un'eccezione.


Sembra che tu stia sostenendo di emulare enum con hash , senza dirlo esplicitamente. Potrebbe essere una buona idea modificare la tua risposta per dirlo. (Ho anche momento ho bisogno di qualcosa come enumerazioni in Ruby, e il mio primo approccio per risolverlo è quello di utilizzare gli hash: FOO_VALUES = {missing: 0, something: 1, something_else: 2, ...}Questo definisce i simboli chiave. missing, somethingEcc, e li rende anche confrontabili con i valori associati.)
Teemu Leisti,

Voglio dire, senza dirlo all'inizio della risposta.
Teemu Leisti,

4

Qualcuno è andato avanti e ha scritto una gemma rubino chiamata Renum . Afferma di ottenere il comportamento simile a Java / C # più vicino. Personalmente sto ancora imparando Ruby, ed ero un po 'scioccato quando volevo che una classe specifica contenesse un enum statico, possibilmente un hash, che non era facilmente reperibile tramite google.


Non ho mai avuto bisogno di un enum in Ruby. Simboli e costanti sono idiomatici e risolvono gli stessi problemi, no?
Chuck,

Probabilmente Chuck; ma cercare su Google un enum nel rubino non ti porterà così lontano. Ti mostrerà i risultati per il miglior tentativo delle persone in un equivalente diretto. Il che mi fa meravigliare, forse c'è qualcosa di bello nell'avvolgere il concetto insieme.
dlamblin,

@Chuck I simboli e le costanti non impongono, ad esempio, che un valore debba essere uno di un piccolo insieme di valori.
David Moles,

3

Dipende tutto da come usi enumerazioni Java o C #. Il modo in cui lo utilizzerai determinerà la soluzione che sceglierai in Ruby.

Prova il Settipo nativo , ad esempio:

>> enum = Set['a', 'b', 'c']
=> #<Set: {"a", "b", "c"}>
>> enum.member? "b"
=> true
>> enum.member? "d"
=> false
>> enum.add? "b"
=> nil
>> enum.add? "d"
=> #<Set: {"a", "b", "c", "d"}>

9
Perché non usare i simboli Set[:a, :b, :c]?
Dan Rosenstark l'

2
Pratica molto migliore per usare i simboli qui, IMO.
Collin Graves,

3

Recentemente abbiamo rilasciato una gemma che implementa Enums in Ruby . Nel mio post troverai le risposte alle tue domande. Ho anche descritto lì perché la nostra implementazione è migliore di quelle esistenti (in realtà ci sono molte implementazioni di questa funzionalità in Ruby ma come gemme).


Permette valori auto-incrementanti, senza indicarli esplicitamente. +1
dimido

3

Un'altra soluzione sta usando OpenStruct. È piuttosto semplice e pulito.

https://ruby-doc.org/stdlib-2.3.1/libdoc/ostruct/rdoc/OpenStruct.html

Esempio:

# bar.rb
require 'ostruct' # not needed when using Rails

# by patching Array you have a simple way of creating a ENUM-style
class Array
   def to_enum(base=0)
      OpenStruct.new(map.with_index(base).to_h)
   end
end

class Bar

    MY_ENUM = OpenStruct.new(ONE: 1, TWO: 2, THREE: 3)
    MY_ENUM2 = %w[ONE TWO THREE].to_enum

    def use_enum (value)
        case value
        when MY_ENUM.ONE
            puts "Hello, this is ENUM 1"
        when MY_ENUM.TWO
            puts "Hello, this is ENUM 2"
        when MY_ENUM.THREE
            puts "Hello, this is ENUM 3"
        else
            puts "#{value} not found in ENUM"
        end
    end

end

# usage
foo = Bar.new    
foo.use_enum 1
foo.use_enum 2
foo.use_enum 9


# put this code in a file 'bar.rb', start IRB and type: load 'bar.rb'

2

Simboli è il modo rubino. Tuttavia, a volte è necessario parlare con un codice C o qualcosa o Java che espone qualche enum per varie cose.


#server_roles.rb
module EnumLike

  def EnumLike.server_role
    server_Symb=[ :SERVER_CLOUD, :SERVER_DESKTOP, :SERVER_WORKSTATION]
    server_Enum=Hash.new
    i=0
    server_Symb.each{ |e| server_Enum[e]=i; i +=1}
    return server_Symb,server_Enum
  end

end

Questo può quindi essere usato in questo modo


require 'server_roles'

sSymb, sEnum =EnumLike.server_role()

foreignvec[sEnum[:SERVER_WORKSTATION]]=8

Questo può ovviamente essere reso astratto e puoi tirare la nostra classe Enum


Stai capitalizzando la seconda parola in variabili (ad es. server_Symb) Per un motivo particolare? A meno che non ci sia una ragione particolare, è idiomatico che le variabili siano snake_case_with_all_lower_casee che i simboli siano :lower_case.
Andrew Grimm,

1
@Andrea; questo esempio è stato preso da una cosa del mondo reale e la documentazione del protocollo di rete utilizzava xxx_Yyy, quindi il codice in diverse lingue utilizzava lo stesso concetto in modo da poter seguire i cambiamenti delle specifiche.
Jonke

1
Codice golf: server_Symb.each_with_index { |e,i| server_Enum[e] = i}. Non c'è bisogno di i = 0.
Andrew Grimm,

2

Ho implementato enum così

module EnumType

  def self.find_by_id id
    if id.instance_of? String
      id = id.to_i
    end 
    values.each do |type|
      if id == type.id
        return type
      end
    end
    nil
  end

  def self.values
    [@ENUM_1, @ENUM_2] 
  end

  class Enum
    attr_reader :id, :label

    def initialize id, label
      @id = id
      @label = label
    end
  end

  @ENUM_1 = Enum.new(1, "first")
  @ENUM_2 = Enum.new(2, "second")

end

quindi è facile fare operazioni

EnumType.ENUM_1.label

...

enum = EnumType.find_by_id 1

...

valueArray = EnumType.values

2

Questo sembra un po 'superfluo, ma questa è una metodologia che ho usato alcune volte, specialmente dove mi sto integrando con XML o qualcosa del genere.

#model
class Profession
  def self.pro_enum
    {:BAKER => 0, 
     :MANAGER => 1, 
     :FIREMAN => 2, 
     :DEV => 3, 
     :VAL => ["BAKER", "MANAGER", "FIREMAN", "DEV"]
    }
  end
end

Profession.pro_enum[:DEV]      #=>3
Profession.pro_enum[:VAL][1]   #=>MANAGER

Questo mi dà il rigore di ac # enum ed è legato al modello.


Non consiglierei questo approccio perché si basa sul fatto che tu abbia impostato manualmente i valori e che ti assicurassi che l'ordine fosse corretto :VAL. Sarebbe meglio iniziare con un array e costruire l'hash usando.map.with_index
DaveMongoose,

1
Il punto esatto è legarsi a un valore dettato da terzi. Non si tratta di estensibilità di per sé, ma di dover affrontare vincoli estranei che incidono sulla calcolabilità entro i limiti del processo.
jjk,

Punto valido! In questo caso rende sicuramente senso per specificare i valori, ma mi piacerebbe essere incline a fare la ricerca inversa con .keyo .invertpiuttosto che una :VALchiave ( stackoverflow.com/a/10989394/2208016 )
DaveMongoose

Sì, questo è (di nuovo a te) un punto giusto. Il mio rubino era inelegante e ingombrante. Def userebbe keyoinvert
jjk

1

Molte persone usano simboli (questa è la :foo_barsintassi). Sono una sorta di valori opachi unici. I simboli non appartengono a nessun tipo di stile enumico, quindi non sono in realtà una rappresentazione fedele del tipo enumatico di C, ma questo è praticamente buono come sembra.


1
irb(main):016:0> num=[1,2,3,4]
irb(main):017:0> alph=['a','b','c','d']
irb(main):018:0> l_enum=alph.to_enum
irb(main):019:0> s_enum=num.to_enum
irb(main):020:0> loop do
irb(main):021:1* puts "#{s_enum.next} - #{l_enum.next}"
irb(main):022:1> end

Produzione:

1 - a
2 - b
3 - c
4 - d


to_enumti dà un enumera tor , mentre enumin senso C # / Java è un enumera zione
DaveMongoose,

1
module Status
  BAD  = 13
  GOOD = 24

  def self.to_str(status)
    for sym in self.constants
      if self.const_get(sym) == status
        return sym.to_s
      end
    end
  end

end


mystatus = Status::GOOD

puts Status::to_str(mystatus)

Produzione:

GOOD

1

A volte tutto ciò di cui ho bisogno è essere in grado di recuperare il valore di enum e identificare il suo nome simile al mondo Java.

module Enum
     def get_value(str)
       const_get(str)
     end
     def get_name(sym)
       sym.to_s.upcase
     end
 end

 class Fruits
   include Enum
   APPLE = "Delicious"
   MANGO = "Sweet"
 end

 Fruits.get_value('APPLE') #'Delicious'
 Fruits.get_value('MANGO') # 'Sweet'

 Fruits.get_name(:apple) # 'APPLE'
 Fruits.get_name(:mango) # 'MANGO'

Questo per me ha lo scopo di enum e lo rende anche molto estensibile. È possibile aggiungere più metodi alla classe Enum e Viola li ottiene gratuitamente in tutti gli enum definiti. per esempio. get_all_names e cose del genere.


0

Un altro approccio consiste nell'utilizzare una classe Ruby con un hash contenente nomi e valori come descritto nel seguente post sul blog di RubyFleebie . Ciò consente di convertire facilmente tra valori e costanti (soprattutto se si aggiunge un metodo di classe per cercare il nome per un determinato valore).


0

Penso che il modo migliore per implementare l'enumerazione come i tipi sia con i simboli poiché praticamente si comportano come numeri interi (quando si tratta di performace, object_id viene usato per fare confronti); non devi preoccuparti dell'indicizzazione e sembrano davvero ordinati nel tuo codice xD


0

Un altro modo per imitare un enum con una gestione coerente dell'uguaglianza (adottato spudoratamente da Dave Thomas). Consente enumerazioni aperte (molto simili ai simboli) e enumerazioni chiuse (predefinite).

class Enum
  def self.new(values = nil)
    enum = Class.new do
      unless values
        def self.const_missing(name)
          const_set(name, new(name))
        end
      end

      def initialize(name)
        @enum_name = name
      end

      def to_s
        "#{self.class}::#@enum_name"
      end
    end

    if values
      enum.instance_eval do
        values.each { |e| const_set(e, enum.new(e)) }
      end
    end

    enum
  end
end

Genre = Enum.new %w(Gothic Metal) # creates closed enum
Architecture = Enum.new           # creates open enum

Genre::Gothic == Genre::Gothic        # => true
Genre::Gothic != Architecture::Gothic # => true

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.