2016-03-06 4 views
0

Одна из вещей, которые мне очень нравятся в Active Record, - это ее облачные области и возможность объединять области для создания выразительных запросов.Как создать составные области для простых массивов Ruby

Что было бы аналогичным образом для достижения этого с помощью простых Ruby Enumerables/Arrays, в идеале без перехвата обезьян Перечисляемый или массив любым опасным способом?

Например:

### 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') ...? 

Я знаю, что могу построить метод customers_with_language внутри модуля, но я ищу общий способ решить эту проблему с любым количеством «областей».

ответ

1

Вот грубая реализация ScopableArray, который наследует Array:

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

Когда этот класс получает метод, который он не идентифицирует, он предполагает, что вы хотите, чтобы фильтровать по полю из метода имя со значением этого аргумента:

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"}] 

Вы также можете посмотреть на ActiveRecord's implementation pattern для более сложной схемы, где ты можно определить области, передав имя и вызываемый блок, что-то вроде этого:

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 

Затем вы можете сделать что-то вроде этого:

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

Спасибо Ури. Он выглядит аккуратно, но только обрабатывает выбор свойств. Я ищу что-то более универсальное/составное/функциональное/выразительное, надеюсь. Поэтому я могу делать такие вещи, как все клиенты с «j» от их имени, которые не говорят по-английски ... (в идеале все, что я могу сделать с областью ActiveRecord, просто работаю с массивами). Имеет ли это смысл? – gingerlime

+0

@gingerlime - добавлено более сложное решение. Вы можете использовать этот шаблон, чтобы сделать ваше решение еще более сложным. –

+0

Выглядит потрясающе, Ури! Спасибо. – gingerlime