Skip to content

Instantly share code, notes, and snippets.

@leehambley
Created November 17, 2016 19:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save leehambley/cc686418ef0145bb5556a62e267fc511 to your computer and use it in GitHub Desktop.
Save leehambley/cc686418ef0145bb5556a62e267fc511 to your computer and use it in GitHub Desktop.

Struct-Like with Keyword Arguments

[~]$ irb -r ./event-struct.rb
Run options: --seed 53294

# Running:

....

Finished in 0.024471s, 163.4600 runs/s, 81.7300 assertions/s.

4 runs, 2 assertions, 0 failures, 0 errors, 0 skips
irb(main):002:0> SetAddress = Event.new(:street, :postcode)
=> SetAddress

irb(main):003:0> SetAddress.new("foo")
ArgumentError: wrong number of arguments (given 1, expected 0)
        from /Users/leehambley/event-struct.rb:36:in `new'
        from (irb):3
        from /Users/leehambley/.rbenv/versions/2.3.1/bin/irb:11:in `<main>'

irb(main):004:0> SetAddress.new(street: "foo")
ArgumentError:           keys do not match expected list:
          -- missing keys: [:postcode]
          -- extra keys:   []

        from /Users/leehambley/event-struct.rb:93:in `block in <class:Event>'
        from /Users/leehambley/event-struct.rb:38:in `new'
        from /Users/leehambley/event-struct.rb:38:in `new'
        from (irb):4
        from /Users/leehambley/.rbenv/versions/2.3.1/bin/irb:11:in `<main>'

irb(main):005:0> SetAddress.new(street: "foo", postcode: "1234")
=> #<SetAddress:0x007fbb0c121d98 @street="foo", @postcode="1234", @occurred_on=2016-11-17 19:16:59 UTC>
require 'byebug'
require 'time'
require 'timecop'
require 'minitest'
class Event
include Enumerable
class << self
alias_method :subclass_new, :new
end
def self.new(*attrs, &block)
attr_sym_names = attrs.push(:occurred_on).map do |a|
case a
when Symbol
a
when String
sym = a.to_sym
unless sym.kind_of? Symbol
raise TypeError, "#to_sym didn't return a symbol"
end
sym
else
raise TypeError, "#{a.inspect} is not a symbol"
end
end
klass = Class.new self do
attr_accessor(*attr_sym_names)
def self.new(**args, &block)
# args is a hash of key-value pairs representing the keyword arguments
return subclass_new(**args, &block)
end
const_set :EVENT_ATTRS, attr_sym_names
end
klass.module_eval(&block) if block
return klass
end
# def self.new(*attribute_names_as_symbols)
# c = Class.new
# l = attribute_names_as_symbols
#
# c.instance_eval {
# define_method(:initialize) do |**kwargs|
# unless kwargs.keys.sort == l.sort
# extra = kwargs.keys - l
# missing = l - kwargs.keys
#
# raise ArgumentError.new <<-MESSAGE
# keys do not match expected list:
# -- missing keys: #{missing}
# -- extra keys: #{extra}
# MESSAGE
# end
#
# kwargs.map do |k, v|
# instance_variable_set "@#{k}", v
# end
# end
#
# l.each do |sym|
# attr_reader sym
# end
# }
#
# c
# end
define_method(:initialize) do |**kwargs|
attrs = _attrs
unless kwargs.has_key? :occurred_on
kwargs.merge! occurred_on: Time.now.utc
end
unless kwargs.keys.sort == attrs.sort
extra = kwargs.keys - attrs
missing = attrs - kwargs.keys
raise ArgumentError.new <<-MESSAGE
keys do not match expected list:
-- missing keys: #{missing}
-- extra keys: #{extra}
MESSAGE
end
kwargs.map do |k, v|
instance_variable_set "@#{k}", v
end
end
private :initialize
private def _attrs # :nodoc:
return self.class::EVENT_ATTRS
end
end
class SetName < Event.new(:name); end
class SetNameEventTest < MiniTest::Test
def test_the_event_static_attr_list
puts = Event.new(:user)::EVENT_ATTRS
end
def test_the_event_class_happy_path
SetName.new(name: "Lee")
end
def test_implicit_occured_on
some_time = Time.now
Timecop.freeze(some_time) do
assert_equal some_time, SetName.new(name: "Lee").occurred_on
end
end
def test_explicit_occured_on
some_time = Time.parse("2013-11-06")
assert_equal some_time, SetName.new(name: "Lee", occurred_on: some_time).occurred_on
end
end
MiniTest.run
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment