Flexibility
is a mix-in for ruby classes that allows you to easily #define
methods that can take a mixture of positional and keyword arguments.
For example, suppose we define
class Banner include Flexibility define( :show, message: [ required, validate { |s| String === s }, transform { |s| s.upcase } ], width: [ default { @width }, validate { |n| 0 <= n } ], symbol: default('*') ) do |message,width,symbol,unused_opts| width = [ width, message.length + 4 ].max puts "#{symbol * width}" puts "#{symbol} #{message.ljust(width - 4)} #{symbol}" puts "#{symbol * width}" end def initialize @width = 40 end end
Popping over to IRB, we could use Banner#show
with keyword arguments,
irb> banner = Banner.new irb> banner.show( message: "HELLO", width: 10, symbol: '*' ) ********** * HELLO * ********** => nil
positional arguments
irb> banner.show( "HELLO WORLD!", 20, '#' ) #################### # HELLO WORLD! # #################### => nil
or a mix
irb> banner.show( "A-HA", symbol: '-', width: 15 ) --------------- - A-HA - --------------- => nil
The keyword arguments are taken from the last argument, if it is a Hash, while the preceeding positional arguments are matched up to the keyword in the same position in the argument description.
Flexibility
also allows the user to run zero or more callbacks on each argument, and includes a number of callback generators to specify a #default
value, mark a given argument as #required
, #validate
an argument, or #transform
an argument into a more acceptable form.
Continuing our prior example, this means Banner#show
only requires one argument, which it automatically upper-cases:
irb> banner.show( "celery?" ) **************************************** * CELERY? * ****************************************
And it will raise an error if the message
is missing or not a String, or if the width
argument is negative:
irb> banner.show !> ArgumentError: Required argument :message not given irb> banner.show 8675309 !> ArgumentError: Invalid value 8675309 given for argument :message irb> banner.show "hello", -9 !> ArgumentError: Invalid value -9 given for argument :width
Just as Flexibility#define
allows the method caller to determine whether to pass the method arguments positionally, with keywords, or in a mixture of the two, it also allows method authors to determine whether the method receives arguments in a Hash or positionally:
class Banner opts_desc = { a: [], b: [], c: [], d: [], e: [] } define :all_positional, opts_desc do |a,b,c,d,e,opts| [ a, b, c, d, e, opts ] end define :all_keyword, opts_desc do |opts| [ opts ] end define :mixture, opts_desc do |a,b,c,opts| [ a, b, c, opts ] end end
irb> banner.all_positional(1,2,3,4,5) => [ 1, 2, 3, 4, 5, {} ] irb> banner.all_positional(a:1, b:2, c:3, d:4, e:5, f:6) => [ 1, 2, 3, 4, 5, {f:6} ] irb> banner.all_keyword(1,2,3,4,5) => [ { a:1, b:2, c:3, d:4, e:5 } ] irb> banner.all_keyword(a:1, b:2, c:3, d:4, e:5, f:6) => [ { a:1, b:2, c:3, d:4, e:5, f:6 } ] irb> banner.mixture(1,2,3,4,5) => [ 1, 2, 3, { d:4, e:5 } ] irb> banner.mixture(a:1, b:2, c:3, d:4, e:5, f:6) => [ 1, 2, 3, { d:4, e:5, f:6 } ]