Skip to content

Instantly share code, notes, and snippets.

@a2ikm
Last active September 9, 2015 13:13
Show Gist options
  • Save a2ikm/747b9bf79a0350fa1453 to your computer and use it in GitHub Desktop.
Save a2ikm/747b9bf79a0350fa1453 to your computer and use it in GitHub Desktop.
配列とかに使えるscopeみたいなやつ
module Filterable
def self.included(base)
fail "#{base} MUST include Enumerable to include #{name}" unless base.include?(Enumerable)
base.extend ClassMethods
base.const_set(base.filter_chain_class_name, base.filter_chain_class)
end
def filter_chain
self.class.filter_chain_class.new(self)
end
module ClassMethods
def filter_chain_class_name
@filter_chain_class_name ||= begin
parent = name.split("::").last
child = ::Filterable::FilterChain.name.split("::").last
parent + "_" + child
end
end
def filter_chain_class
@filter_chain_class ||= Class.new(::Filterable::FilterChain)
end
def add_filter(name, &block)
send(:define_method, name) do |*args|
filter_chain.send(name, *args)
end
filter_chain_class.send(:define_method, name) do |*args|
filter = ::Filterable::Filter.new(*args, &block)
add_filter(filter)
end
end
end
class Filter
attr_reader :args, :block
def initialize(*args, &block)
@args = args
@block = block
end
end
class FilterChain
include Enumerable
delegate *(Enumerable.public_instance_methods(false) - [:to_a]), to: :to_a
def initialize(source, filters = nil)
@source = source
@filters = filters || []
end
def filter_chain
self
end
def add_filter(filter)
self.class.new(@source, [*@filters, filter])
end
def to_a
@source.select do |item|
@filters.all? do |filter|
args = filter.args
block = filter.block
@source.instance_exec(item, *args, &block)
end
end
end
end
end
require "test_helper"
class FilterableTest < ActiveSupport::TestCase
require "filterable"
class Container
include Enumerable
include Filterable
def initialize(items = [])
@items = items
end
def each
if block_given?
@items.each do |item|
yield(item)
end
else
@items.each
end
end
add_filter :big do |item|
item > 1
end
add_filter :bigger_than do |item, other|
item > other
end
add_filter :even do |item|
even?(item)
end
def even?(item)
item.even?
end
end
def test_filter_chain_class_name
assert_equal Container.filter_chain_class_name, "Container_FilterChain"
end
def test_filter_chain_class
assert_equal Container::Container_FilterChain.name, "#{self.class.name}::Container::Container_FilterChain"
assert_equal Container::Container_FilterChain.superclass, Filterable::FilterChain
end
def test_filter_chain
assert_kind_of Container.filter_chain_class, Container.new.filter_chain
end
def test_filter_with_no_arguments
container = Container.new([1, 2])
assert_equal [2], container.big.to_a
end
def test_filter_with_arguments
container = Container.new([1, 2])
assert_equal [2], container.bigger_than(1).to_a
end
def test_filter_with_instance_method
container = Container.new([1, 2])
assert_equal [2], container.even.to_a
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment