Created
November 7, 2014 09:54
-
-
Save waterlink/997f2106aacef0e520fe to your computer and use it in GitHub Desktop.
Struct implementation as a module to extend
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'rspec' | |
module StructDsl | |
def with_attributes(*args) | |
include Enumerable | |
args.map!(&:to_sym) | |
args.freeze | |
args.each { |p| attr_accessor p } | |
define_method(:initialize) do |*values| | |
args.each_with_index { |p, i| send(:"#{p}=", values[i]) } | |
end | |
define_method(:[]) do |key| | |
raise ArgumentError unless members.include?(key.to_sym) | |
public_send(key) | |
end | |
define_method(:[]=) do |key, value| | |
raise ArgumentError unless members.include?(key.to_sym) | |
public_send(:"#{key}=", value) | |
end | |
define_method(:members) { args } | |
define_method(:each) do |&blk| | |
members.each(&blk) | |
end | |
define_method(:each_pair) do |&blk| | |
each.map { |e| [e, self[e]] }.each(&blk) | |
end | |
define_method(:select) do |&blk| | |
values.select(&blk) | |
end | |
define_method(:values) { each.map { |e| self[e] } } | |
define_method(:hash) do | |
to_h.hash | |
end | |
define_method(:==) do |other| | |
return false unless self.class === other | |
self.each_pair.to_a == other.each_pair.to_a | |
end | |
define_method(:inspect) do | |
"<struct #{self.class.name} #{each_pair.map { |k, v| "#{k}=#{v.inspect}" }.join(", ")}" | |
end | |
define_method(:to_s) { inspect } | |
define_method(:length) { members.length } | |
define_method(:size) { length } | |
define_method(:to_a) { values.to_a } | |
define_method(:to_h) { each_pair.to_h } | |
define_method(:values_at) { |*a| to_h.values_at(*a) } | |
class << self | |
define_method(:call) do |*values| | |
new(*values) | |
end | |
alias :[] :call | |
end | |
end | |
end | |
class HelloWorld | |
extend StructDsl | |
with_attributes :greeting, :name | |
end | |
RSpec.describe "HelloWorld" do | |
let(:hello_world) { HelloWorld["hello", "world"] } | |
let(:other_hello_world) { HelloWorld["hello", "world"] } | |
let(:other_greeting) { HelloWorld["hi", "alex"] } | |
it { expect(hello_world).to eq(other_hello_world) } | |
it { expect(hello_world).not_to eq(other_greeting) } | |
it { expect(hello_world).not_to eq(nil) } | |
it { expect(hello_world).not_to eq(Object.new) } | |
it { expect(hello_world.hash).to eq(other_hello_world.hash) } | |
it { expect(hello_world.hash).not_to eq(0) } # unlikely to be false | |
it { expect(hello_world[:greeting]).to eq("hello") } | |
it { expect(hello_world["greeting"]).to eq("hello") } | |
it { expect{ hello_world[:greeting] = "hallo" }.to change(hello_world, :greeting).to("hallo") } | |
it { expect{ hello_world["greeting"] = "hallo" }.to change(hello_world, :greeting).to("hallo") } | |
it { expect(hello_world).to be(hello_world) } | |
it { expect(hello_world).not_to be(other_hello_world) } | |
it { expect(hello_world.inspect).to eq(%{<struct HelloWorld greeting="hello", name="world"}) } | |
it { expect(hello_world.to_s).to eq(%{<struct HelloWorld greeting="hello", name="world"}) } | |
it { expect(hello_world.length).to eq(2) } | |
it { expect(hello_world.size).to eq(2) } | |
it { expect(hello_world.select { |v| v =~ /e/ }).to eq(["hello"]) } | |
it { expect(other_greeting.select { |v| v =~ /e/ }).to eq(["alex"]) } | |
it { expect(hello_world.to_a).to eq(["hello", "world"]) } | |
it { expect(hello_world.to_h).to eq({ greeting: "hello", name: "world" }) } | |
it { expect(hello_world.values).to eq(["hello", "world"]) } | |
it { expect(hello_world.values_at(:greeting)).to eq(["hello"]) } | |
it { expect(hello_world.values_at(:name)).to eq(["world"]) } | |
it { expect(hello_world.values_at(:name, :greeting)).to eq(["world", "hello"]) } | |
it { expect(hello_world.all? { |k| k.is_a?(Symbol) }).to eq(true) } | |
it { expect(hello_world.all? { |k| k.to_s =~ /t/ }).to eq(false) } | |
it "does not have anything weird in hierarchy" do | |
# PP::ObjectMixin is added to Object by RSpec. | |
expect(HelloWorld.ancestors).to eq([HelloWorld, Enumerable, Object, PP::ObjectMixin, Kernel, BasicObject]) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment