2017-10-25 1 views
0

Ich brauche ein Virtus Basis-Objekt erstellen zu können, die in einem Hash oder einen String. Wenn es ein Hash ist, dann ist normales Verhalten perfekt. Wenn es ein einfacher String ist, muss ich ihn in {'id' => "STRING"} konvertieren. Zu diesem Zeitpunkt bin ich nicht sicher, wie/wo die initialize-Methode überschrieben werden soll, um diese Funktion auszuführen. Oder vielleicht gibt es einen anderen Weg. Ihre Expertise wird sehr geschätzt.initialisieren mit String oder Hash in Virtus Ruby-Objekt

class Contact 
    include Virtus.model 

    attribute :id, String 
end 

class Account 
    include Virtus.model 

    attribute :id, String 
    attribute :contact, Contact 
    attribute :name, String 
end 

account = Account.new("1234") 
account.id #>1234 

# and still work like this 

account = Account.new(id: '1234', contact: '123456', name: 'Bob Jones') 
account.id #>1234 
account.contact.id #>123456 
account.name #>Bob Jones 

Beispieldaten

{'id' => '1234', 'contact' => '123456', 'name' => 'Bob Jones'} 

beide so zwischen Kontakt und Konto. Ich brauche sie mit einem String initialisieren zu können, der den @id-Parameter auffüllt.

Antwort

1

Hinweis: Dies ist nur zu Erbauzwecken und kann unbeabsichtigte Folgen haben, die möglicherweise schwer zu debuggen sind.

Sie können die Virtus::InstanceMethods::Constructor wie so überschreiben, wenn Sie wirklich wollen:

module VirtusOverride 
    def self.included(base) 
    raise "#{base.name} must include Virtus.model prior to including VirtusOverride" unless base.included_modules.include?(Virtus::InstanceMethods) 
    end 

    def initialize(*attributes) 
    super(construct(attributes)) 
    end 

    private 
    def construct(attributes) 
     return attributes.first if valid_constructor?(attributes) 
     build_attributes_from_array(attributes) 
    end 
    def valid_constructor?(attributes) 
     return false unless attributes.count == 1 
     constructor = attributes.first 
     constructor.is_a?(Hash) && 
     !(attribute_set.flat_map {|a| [a.name,a.name.to_s]} & constructor.keys).empty? 
    end 
    def build_attributes_from_array(attributes) 
     attribute_set.map(&:name).zip(attributes).to_h 
    end 
end 

Dann ist es enthalten, wie

benötigt
class Account 
    include Virtus.model 
    include VirtusOverride 

    attribute :id, String 
    attribute :contact, Hash 
end 

Jetzt können Sie entweder die Optionen Positions auf die Definition der Attribute übergeben (zB Account.new(id,contact)) oder als Hash.

Beispiel:

Account.new("1234",{name: 'mnky'}) 
#=> #<Account:0x2a071b8 @id="1234", @contact={:name=>"mnky"}> 

Account.new(id: "1234", contact: {name: 'mnky'}) 
#=> #<Account:0x2b42b78 @id="1234", @contact={:name=>"mnky"}> 

Sie könnten Virtus::InstanceMethods::Constructor Affen Patch gleich auszuführen, aber ich bin kein großer Fan von dieser Philosophie, wie es Verwirrung zu anderen Entwicklern hinzufügen könnte, wo, wie das Modul Inklusion Granularität und Klarheit bietet.

aktualisieren

class Account 
    include Virtus.model 
    include VirtusOverride 

    attribute :id, String 
    attribute :contact, Contact 
end 

class Contact 
    include Virtus.model 
    include VirtusOverride 

    attribute :id, String 
end 

Account.new(id: '1234', contact: '123456', name: 'Bob Jones') 
#=> #<Account:0x2bcb060 @id="1234", @contact=#<Contact:0x2bc9c50 @id="123456">, @name="Bob Jones"> 
Account.new('1234', '123456', 'Bob Jones') 
#=> #<Account:0x2faea00 @id="1234", @contact=#<Contact:0x2fae880 @id="123456">, @name="Bob Jones"> 
Account.new('id' => '1234', 'contact' => '123456', 'name' => 'Bob Jones') 
#=> #<Account:0x2faffc0 @id="1234", @contact=#<Contact:0x2fafb70 @id="123456">, @name="Bob Jones"> 
+0

Das ist genial, und ist sooo nah an, was für im suchen. Ich habe meinen Beispielcode aktualisiert, mit dem die Daten konsistent aussehen werden. Ein letzter Blick? –

+0

@JaisonBrooks haben Sie das tatsächlich versucht, weil es wie erwartet funktioniert. Ich habe mit einem Beispiel aktualisiert – engineersmnky

+0

Ich habe es versucht, ich empfange den folgenden Fehler. '' 'undefinierte Methode' Schlüssel? ' für "1234": String'''. Ihre erste Beispielausgabe ist perfekt. Ich werde den Array-Typ-Eintrag nicht verwenden. da die Daten ein Hash sein werden, es sei denn, diese einzelne Zeichenfolge. Nochmals vielen Dank für diese Hilfe/rette mich. –

2

Virtus’ author’s opinion ist, dass Sie für sanitization nicht Virtus werden. Wie Sie aus der Antwort von @ engineersmyky entnehmen können, benötigt die Methode initialize mehr Arbeit als nötig.

Es wäre stattdessen einfacher, das Objekt zu wickeln in Sie vorbei:

class Hashable 
    def initialize(hashable_object) 
    @obj = hashable_object 
    end 

    def to_hash 
    case @obj 
    when Hash then @obj 
    when String 
     { "id" => @obj } 
    else 
     raise "No conversion from #{@obj.class} to hash" 
    end 
    end 
end 

Account.new(Hashable.new(relationship)) 
+0

Ich kann mehr als mit diesem Gefühl zustimmen, aber ich habe nie 'virtus' so dass es Spaß eine neue Code-Basis zu erkunden. :) – engineersmnky