2016-07-20 31 views
3

ich Ruby-Gem ich schreibe, wo ich Connection Modul für Faraday Konfiguration habenRubin Mixins für Best Practice suchen

module Example 
    module Connection 
    private 

    def connection 
     Faraday.new(url: 'http://localhost:3000/api') do |conn| 
     conn.request :url_encoded    # form-encode POST params 
     conn.response :logger     # log requests to STDOUT 
     conn.adapter Faraday.default_adapter # make requests with Net::HTTP 
     conn.use  Faraday::Response::ParseJson 
     conn.use  FaradayMiddleware::RaiseHttpException 
     end 
    end 
    end 
end 

zweiten Modul, das Anfragen API macht sieht wie folgt aus:

module Example 
    module Request 
    include Connection 

    def get(uri) 
     connection.get(uri).body 
    end 

    def post(url, attributes) 
     response = connection.post(url) do |request| 
     request.body = attributes.to_json 
     end 
    end 

    def self.extended(base) 
     base.include(InstanceMethods) 
    end 

    module InstanceMethods 
     include Connection 

     def put(url, attributes) 
     response = connection.put(url) do |request| 
      request.body = attributes.to_json 
     end 
     end 
    end 
    end 
end 

Klasse Cusomer wo ich verwende Request sieht so aus:

module Example 
    class Customer 
    extend Request 

    attr_accessor :id, :name, :age 

    def initialize(attrs) 
     attrs.each do |key, value| 
     instance_variable_set("@#{key}", value) 
     end 
    end 

    def self.all 
     customers = get('v1/customer') 
     customers.map { |cust| new cust } 
    end 

    def save 
     params = { 
     id: self.id, 
     age: self.age 
     name: self.name, 
     } 

     put("v1/customers/#{self.id}", params) 
    end 
    end 
end 

So hier sehen Sie in Customer#all Klassenmethode Ich rufe Request#get Methode, die verfügbar ist, weil ich Request in Customer erweitert. dann verwende ich self.extended Methode in Request Modul zu machen Request#put verfügbar in Customer Klasse, so habe ich Frage ist dieser gute Ansatz zu Mixins wie diese zu verwenden, oder haben Sie einen Vorschlag?

Antwort

5

Mixins sind ein seltsames Biest. Best Practices variieren je nachdem, mit wem Sie sprechen. Was die Wiederverwendung betrifft, haben Sie das hier mit Mixins erreicht, und Sie haben eine nette Trennung von Bedenken.

Allerdings sind Mixins eine Form der Vererbung (Sie können einen Blick auf #ancestors werfen). Ich würde Sie herausfordern, zu sagen, dass Sie Vererbung hier nicht verwenden sollten, weil Customer keine Beziehung "ist-a" mit Connection hat. Ich würde empfehlen, dass Sie stattdessen Komposition verwenden (z. B. in Connection/Request übergeben), da es in diesem Fall für mich sinnvoller ist und eine stärkere Kapselung hat.

Ein Leitfaden für das Schreiben Mixins ist alles in „-able“ machen zu beenden, so dass Sie Enumerable haben würden, Sortable, Runnable, Callable usw. In diesem Sinne sind Mixins allgemeine Erweiterungen, die eine Art Helfer bieten, die abhängig von einer sehr spezifischen Schnittstelle (zB Enumerable hängt von der zu implementierenden Klasse ab #each).

Sie können Mixins auch für Querschnitte verwenden. Zum Beispiel haben wir in den Hintergrundjobs in der Vergangenheit Mixins verwendet, um beispielsweise die Protokollierung hinzuzufügen, ohne den Quellcode der Klasse anfassen zu müssen. Wenn in diesem Fall ein neuer Job protokolliert werden soll, dann mischt er einfach das Anliegen, das an das Framework gekoppelt ist, und wird sich selbst richtig injizieren.

Meine allgemeine Faustregel ist, verwenden Sie sie nicht, wenn Sie nicht müssen. Sie machen das Verständnis des Codes in den meisten Fällen viel komplizierter

EDIT: Hinzufügen eines Beispiels für die Zusammensetzung. Um die oben genannte Schnittstelle beizubehalten, müssen Sie einen globalen Verbindungsstatus haben, daher ist dies möglicherweise nicht sinnvoll. Hier ist eine Alternative, die Zusammensetzung

class CustomerConnection 
    # CustomerConnection is composed of a Connection and retains isolation 
    # of responsibilities. It also uses constructor injection (e.g. takes 
    # its dependencies in the constructor) which means easy testing. 
    def initialize(connection) 
    @connection = connection 
    end 

    def all_customers 
    @connection.get('v1/customers').map { |res| Customer.new(res) } 
    end 
end 

connection = Connection.new 
CustomerConnection.new(connection).all_customers 
+1

Danke Josh für Antwort verwendet, aber es wenig unklar, was meinst du hören: Ich würde Ihnen empfehlen Zusammensetzung verwenden, anstatt (zB passieren in Verbindung/Request), da es mehr Sinn macht, mich in diesem Fall und hat stärkere Einkapselung. – user525717

+0

Was Josh bedeutet, ist, dass Sie eine Connection/Request-Klasse erstellen sollten, die in Ihrer Customer-Klasse verwendet werden kann. Wenn Sie Ihren Kunden instanziieren, können Sie Ihre Request-Klasse weiterleiten, oder Sie können eine Anfrage in Ihrem Initialisierer für den Kunden instanziieren. Persönlich bevorzuge ich etwas mit Komposition b/c, dann kann ich die einzelnen Objekte einfach testen. – unflores

+0

Unflores können Sie bitte ein Beispiel geben? – user525717