Определить операторы <,>,> =, <= в Ruby и использовать метод .where (без активной записи) - PullRequest
1 голос
/ 27 июня 2019

В обычном рубине (без активной записи)

def where(options)
end

Опции могут быть ключом, парой значений или сравнением типа>, <,> =, <= </p>

Если у меня есть массив записей ...

record = Struct.new(:name, :address, :amount) 

Как проверить, какие из них и отфильтровать?

Например, если я сделаю:

.where(:amount > 5)

против

.where(name: 'John')

или даже

.where(name: 'John', :amount>5)

Я хотел бы, чтобы он возвратил товары, которые соответствуют требованию.

array_of_records.select { |record| record.name == 'John'  } 
array_of_records.select { |record| record.amount > 5  } 

Я думал, что мне нужно что-то подобное, но не уверен, с чего начать.

option_type(option)
   case option
   when keyvalue?(option)
   when gtr_then?(option)
   when gtr_then_eql?(option)
   when less_then?(option)
   when less_then_eql?(option)
   end
end


keyvalue?(mykey: 5)
   # Code detects key value pair
   # Return true
end

gtr_then?(mykey > 5)
   # Code detects > operator
   # Return True
end

less_then?(mykey < 5)
   # Code detects < operator
   # Return true
end

less_then_eql?(mykey <= 5)
   # Code detects <= operator
   # return true
end

gtr_then_eql?(mykey >= 5)
   # Code detects >= operator
   # return true
end

Ответы [ 2 ]

2 голосов
/ 28 июня 2019

Смешав @ комментарий Ричарда-Дегенна и ответ Максима, вы могли бы написать что-то вроде этого.

Вероятно, не очень хорошая идея определить:>,: <, ... для <code>Symbol, чтобы вы моглихочу использовать уточнения .Используйте на свой страх и риск!

class Record
  attr_reader :name, :address, :amount
  def initialize(name, address, amount)
    @name = name
    @address = address
    @amount = amount
  end

  def to_s
    [name, address, amount].join(' ')
  end

  def inspect
    to_s
  end
end

module Enumerable
  def where(query)
    select do |record|
      case query
      when Hash
        query.all? do |key, pattern|
          pattern === record.send(key)
        end
      when WhereComparator
        query.match? record
      end
    end
  end
end

class WhereComparator
  def initialize(sym, operator, other)
    @sym = sym
    @operator = operator
    @other = other
  end

  def match?(record)
    record.send(@sym).send(@operator, @other)
  end
end

module MyWhereSyntax
  refine Symbol do
    [:<, :<=, :==, :>=, :>].each do |operator|
      define_method operator do |other|
        WhereComparator.new(self, operator, other)
      end
    end
  end
end

using MyWhereSyntax

records = [
  Record.new('John', 'a', 7),
  Record.new('Jack', 'b', 12),
  Record.new('Alice', 'c', 19),
  Record.new('John', 'd', 2),
]

p records.where(name: 'John')
#=> [John a 7, John d 2]
p records.where(name: 'John', amount: 2)
#=> [John d 2]
p records.where(name: 'John').where(:amount > 5)
#=> [John a 7]
p records.where(name: 'John').where(:amount > 7)
#=> []
p records.where(:amount > 8).where(:address <= 'c')
#=> [Jack b 12, Alice c 19]
p records.where(name: /^J...$/)
#=> [John a 7, Jack b 12, John d 2]

В качестве бонуса вы можете написать:

long_enough = :size > 7
# => #<WhereComparator:0x00000000017072f8 @operator=:>, @other=7, @sym=:size>
long_enough.match? 'abcdefgh'
# => true
long_enough.match? 'abc':
# => false
2 голосов
/ 27 июня 2019

Ну, даже ActiveRecord использует строковое представление для такой опции: .where('amount > 5') Для AR это может быть проще, поскольку он может напрямую передаваться в базу данных. В вашем случае вам придется проанализировать строку, найти оператор и операнды и выполнить действие.

Но есть альтернатива (также поддерживаемая AR) для использования диапазонов. С бесконечными диапазонами в Ruby 2.6 довольно просто: .where(amount: 5..) в то время как в предыдущих версиях вы можете сделать это с .where(amount: 5..Float::INFINITY)

Тогда вам просто нужно проверить, является ли аргумент диапазоном и охватывает ли он значение (обязательно используйте cover?, а не include?, поскольку вы не хотите перебирать бесконечный массив)

...