2016-03-06 1 views
0

L'une des choses que j'aime vraiment dans Active Record est ses étendues nommées et sa capacité à enchaîner des oscilloscopes pour créer des requêtes expressives.Comment créer des étendues composables pour des tableaux simples Ruby

Quelle serait une manière similaire de réaliser cela avec des Ruby Enumerables/Arrays, de manière idéale sans utiliser de patchs de type singulier ou Array de manière dangereuse?

Par exemple:

### ActiveRecord Model 
class User < ActiveRecord::Base 
    scope :customers, -> { where(:role => 'customer') } 
    scope :speaking, ->(lang) { where(:language => lang) } 
end 

# querying 
User.customers.language('English') # all English customers 

### Plain-Ruby Array 
module User 
    class << self 
    def customers 
     users.select { |u| u[:role] == 'customer' } 
    end 

    def speaking(lang) 
     users.select { |u| u[:language] == lang } 
    end 

    private 

    def users 
     [ 
     {:name => 'John', :language => 'English', :role => 'customer'}, 
     {:name => 'Jean', :language => 'French', :role => 'customer'}, 
     {:name => 'Hans', :language => 'German', :role => 'user'}, 
     {:name => 'Max', :language => 'English', :role => 'user'} 
     ] 
    end 
    end 
end 

User.customers # all customers 
User.language('English') # all English speakers 
# how do I achieve something similar to User.customers.language('English') ...? 

Je sais que je peux construire une méthode customers_with_language à l'intérieur du module, mais je suis à la recherche d'une manière générale pour résoudre ce avec un certain nombre de « champs ».

Répondre

1

Voici une mise en œuvre brute d'un ScopableArray, qui hérite d'un Array:

class ScopableArray < Array 
    def method_missing(method_sym, *args) 
    ScopableArray.new(select { |u| u[method_sym] == args[0] }) 
    end 
end 

Lorsque cette classe reçoit une méthode ne permet pas d'identifier, il suppose que vous souhaitez filtrer selon un champ de la méthode de nom avec la valeur de l'argument:

users = ScopableArray.new([ 
    {:name => 'John', :language => 'English', :role => 'customer'}, 
    {:name => 'Jean', :language => 'French', :role => 'customer'}, 
    {:name => 'Hans', :language => 'German', :role => 'user'}, 
    {:name => 'Max', :language => 'English', :role => 'user'} 
]) 

users.role('customer') 
# => [{:name=>"John", :language=>"English", :role=>"customer"}, {:name=>"Jean", :language=>"French", :role=>"customer"}] 
users.role('customer').language('English') 
# => [{:name=>"John", :language=>"English", :role=>"customer"}] 

vous pouvez également consulter ActiveRecord's implementation pattern pour un système plus élaboré où vous peut définir des étendues en passant un nom et un bloc appelable, quelque chose comme ceci:

class ScopableArray2 < Array 
    class << self 
    def scope(name, body) 
     unless body.respond_to?(:call) 
     raise ArgumentError, 'The scope body needs to be callable.' 
     end 

     define_method(name) do |*args| 
     dup.select! { |x| body.call(x, *args) } 
     end 
    end 
    end 
end 

vous pouvez faire quelque chose comme ceci:

class Users < ScopableArray2 
    scope :customers, ->(x) { x[:role] == 'customer' } 
    scope :speaking, ->(x, lang) { x[:language] == lang } 
end 

users = Users.new([ 
     {:name => 'John', :language => 'English', :role => 'customer'}, 
     {:name => 'Jean', :language => 'French', :role => 'customer'}, 
     {:name => 'Hans', :language => 'German', :role => 'user'}, 
     {:name => 'Max', :language => 'English', :role => 'user'} 
    ]) 

users.customers.speaking('English') 
# => [{:name=>"John", :language=>"English", :role=>"customer"}] 
+0

Merci Uri. Il semble soigné, mais ne gère que la sélection des propriétés. Je cherche quelque chose de plus général/composable/fonctionnel/expressif, je l'espère. Je peux donc faire des choses comme tous les clients avec un «j» dans leur nom qui ne parle pas anglais ... (Idéalement, tout ce que je peux faire avec une portée ActiveRecord, opérer uniquement sur des tableaux). Est-ce que ça fait du sens? – gingerlime

+0

@gingerlime - ajouté une solution plus élaborée. Vous pouvez utiliser ce modèle pour rendre votre solution encore plus élaborée. –

+0

Ça a l'air génial, Uri! Merci beaucoup. – gingerlime