2016-07-12 17 views
0

eine hashmap, wie zu haben:Mit einem Hash, setzten Objekteigenschaften in Ruby

{:foo => 1, :bar => 2} 

in Ruby, ist es eine einfache Möglichkeit, diese Werte als Eigenschaften des Objekts zuweisen, um automatisch das verursachen passieren :

obj.foo = 1 
obj.bar = 2 

präzise, ​​einige Rubin-idiomatische Weise des Tuns zu sein:

hashmap.each { |k,v| obj.send("#{k}=", v) } 

obj ist ein Objekt, das nicht der Fall ist erben ActiveModel und es ist kein Struct, und ich kann seinen Typ nicht steuern, da er von einer Third-Party-Bibliothek kommt.

Ich verwende Rails, also wenn die Antwort von Rails kommt, ist das akzeptabel.

+0

Nun, es hängt von dem, was 'obj' ... zum Beispiel, wenn es sich um eine Active Instanz können Sie tun' obj.attributes = hashmap' – mdesantis

+0

ich für Ihre Frage denke, ein Struct könnte sein, was Sie sind Auf der Suche nach. http://ruby-doc.org/core-2.2.0/Struct.html –

+0

obj ist ein Objekt einer dritten Klasse, die bereits existiert. Ich habe die Frage aktualisiert, um das zu berücksichtigen. – Pablo

Antwort

0

Sie können method_missing verwenden, wenn Sie Daten dynamisch festlegen müssen.

class Foo 
    def method_missing(sym, *args) 
    super unless instance_variables.include?("@#{sym}".to_sym) 
    instance_variable_get("@#{sym}") 
    end 
end 

obj = Foo.new 

h = {:foo => 1, :bar => 2} 

h.each { |k, v| obj.instance_variable_set("@#{k}", v) } 

obj.foo 
# => 1 
obj.bar 
# => 2 
+1

Es ist viel besser 'define_method' unter' instance_variable_set' und dann 'method_missing' überladen. – mudasobwa

+0

'self.singleton_class.define_method'? – mudasobwa

+2

Oh, es ist 'privat', was für ein Jammer! https://repl.it/CbLI/2 – mudasobwa

0

tun dies auf mobilen so lassen Sie uns dies einen Versuch geben:

class Something 
     attr_reader :foo, :bar 

      def initialize(hash = {}) 
       foo = hash[:foo] 
       bar = hash[:bar] 
      end 
    end 

    obj = Something.new({foo: 1, bar: 2}) 

    obj.foo = 1 
    obj.bar =2 
+0

Ich erstelle das Objekt nicht, es existiert bereits. – Pablo

1

Vielleicht könnten Sie eine OpenStruct von Ihrem Hash zu erstellen, tun, was Sie mit dem OpenStruct und seine Attribute benötigen, ist es dann zurück konvertieren in einen Hash?

require 'ostruct' 

h = {:foo => 1, :bar => 2} 
o = OpenStruct.new(h) 
o.foo # Output: => 1 
o.bar # Output: => 2 

# if necessary, convert it back into a hash 
h = o.to_h 

Vom Ruby docs:

Ein OpenStruct ist eine Datenstruktur, ähnlich einem Hash, der die Definition willkürlicher Attribute mit ihren zugehörigen Werten ermöglicht. Dies wird erreicht, indem die Metaprogrammierung von Ruby verwendet wird, um die Methoden für die Klasse selbst zu definieren.

Ein OpenStruct verwendet Rubys Methode Lookup-Struktur zu finden und definieren die erforderlichen Methoden für Eigenschaften. Dies wird erreicht durch die Methode method_missing und define_method.

Dies sollte eine Überlegung sein, wenn es eine Besorgnis über die Leistung der Objekte, die erstellt werden, da es viel mehr Overhead in der Einstellung dieser Eigenschaften im Vergleich zur Verwendung eines Hash oder einen Struct ist.

+0

Aus dem OP: "Obj ist ein Objekt, das ActiveModel nicht erbt und es ist kein Struct und ich kann seinen Typ nicht steuern, da es von einer Third-Party-Bibliothek kommt." – mudasobwa

+0

Er würde seinen Typ nicht kontrollieren; Er würde ein neues OpenStruct mit dem Inhalt des Hashes erstellen, was er mit dem OpenStruct machen würde (auf Attribute mit der Punktnotation zugreifen) und es wieder in einen Hash konvertieren. Nicht sehr elegant, aber es vermeidet zusätzliche Komplexität durch unnötige Metaprogrammierung. Wie auch immer, du hast einen gültigen Punkt, vielleicht ist mein Vorschlag nicht durchführbar ... – BrunoFacca

2

Was Sie haben, gibt es (fast) die knappste, lesbar, idiomatische Lösung bereits:

hashmap.each { |k,v| obj.send("#{k}=", v) } 

Es gibt nur eine Sache zu verbessern links:

hashmap.each { |k,v| obj.public_send("#{k}=", v) } 

Verwenden public_send statt von send, um anderen klarzumachen, dass Sie es nur verwenden, um einen Methodennamen dynamisch zu übergeben und Zugangsbeschränkungen nicht zu umgehen.

+0

Es war mir nicht klar, ob die Accessoren bereits in der Klasse definiert waren. Wenn nicht, könnte er sie immer hinzufügen mit 'Foo.class_eval {attr_accessor: bar,: foo}' (unter wahrscheinlich einem Dutzend anderer anderer Optionen auch) –

+2

@JayDorsey was ist falsch mit Direktruf: 'Foo.attr_accessor: bar,: foo'? – mudasobwa

+1

Das OP erklärte (in den Kommentaren), dass das Objekt außerhalb der Kontrolle des OP liegt (was wahrscheinlich bedeutet, dass es nicht geändert werden sollte, obwohl das natürlich immer in Ruby möglich ist), dass die Schlüssel mit bereits vorhandenen Accessoren übereinstimmen und das Scheitern mit einer Ausnahme ist das Richtige, wenn sie es nicht tun. –

0
class Klass 
    def initialize(h) 
    h.each { |iv, val| instance_variable_set("@#{iv}", val) } 
    end 
end 

k = Klass.new(:foo => 1, :bar => 2) 
    #=> #<Klass:0x007ff1d9073118 @foo=1, @bar=2> 
0

Wenn Sie Rails verwenden, wäre eine geringfügige Verbesserung auf Ihre vorgeschlagene Lösung Object#try von Active Verlängerungen

hashmap.each {|k,v| obj.try "#{k}=", v } 

Per documentation,

Ruft die öffentliche Methode zu benutzen, wessen Name geht als erstes Argument genau wie public_send tut, außer dass, wenn der Empfänger nicht darauf antwortet der call gibt nil zurück, anstatt eine Ausnahme auszulösen.