简体   繁体   中英

How to combine procs to filter a list in ruby?

I am trying to implement a filter-by-criteria approach in a little project of mine. I have a Filter class, in which i have filters for name, year, genre. For example:

Filter.new.name("The Shawshank Redemption")
Filter.new.year("1994")
Filter.new.genre("drama")

I also have a list of movies, each being an object that has methods name, year and genre. I want to be able to do the following (MoviesContainer is just a class that has a @movies list):

MoviesContainer.filter Filter.new.name("The Godfather") & Filter.new.year("1972") | Filter.year("1974")

I can easily overload the |, & and ! operators in my Filter class, but I don't know how to combine the filter object so that they become just one object which I can pass to filter. I'm gladly accepting any ideas. :)

My best idea so far is to create a proc for every Filter.new and then combine them in the &,| and ! methods, but I don't know how. I'm thinking something like this could work, however, it doesn't :D

proc { |movie| proc { |movie| movie.name == "The Godfather" } && proc { |movie| movie.year== "1972" }

and then call this with every of the @movies items. Can you please help me with the combining procs thing, or perhaps propose a better solution. Thank you.

Maybe something like this?

class Filter
  attr_accessor :proc
  class <<self; alias :old_new :new end
  def self.new attribute, value
    old_new.tap{|f| f.proc = ->x{x.send(attribute) == value}}
  end
  def & other
    self.class.old_new.tap{|f| f.proc = ->x{proc.call(x) && other.proc.call(x)}}
  end
  def | other
    self.class.old_new.tap{|f| f.proc = ->x{proc.call(x) || other.proc.call(x)}}
  end
  def !
    self.class.old_new.tap{|f| f.proc = ->x{!proc.call(x)}}
  end
end

class Movie
  attr_accessor :name, :year, :genre
end

MoviesContainer = [
  Movie.new.tap{|m| m.name = "The Godfather"; m.year = "1972"},
  Movie.new
]

module Enumerable
  def filter f
    select(&f.proc)
  end
end

filter1 = Filter.new(:name, "The Godfather")
filter2 = Filter.new(:year, "1972")
filter3 = Filter.new(:year, "1974")

MoviesContainer.filter(filter1 & filter2 | filter3)
# => [#<Movie:0x000000017dba08 @name="The Godfather", @year="1972">]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM