Enable Labs @mark_menard
Write Small ThingsMark Menard
Los Angeles Ruby Conf 2014
@mark_menard Enable Labs
1 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
Who is Mark Menard
Husband of Sylva!
Father of Ezra and Avi!
From and Resides inTroy, NY!
Owner of Enable Labs a boutique consultancy doing Web and Mobile Development!
Has done for about five years!
Doing software development for a long time
2 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
‘The great thing about writing shitty code that “just works,” is that it is too risky and too expensive to change, so it lives forever.’!
-Reginald Braithwaite @raganwald
3 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
Introduction
4 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
What is meant by small?
5 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
It’s not about total line count.
6 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
It’s not about method count.
7 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
It’s not about class count.
8 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
So, what do I mean by small things?
Small methods! Small classes
9 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
Why should we strive for small code?• We don’t know what the future will bring!• Raise the level of abstraction!• Create composable components!• Prefer delegation over inheritance
Enable Future Change
10 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
What are the challenges of small code?• Dependency Management!• Context Management
11 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
The goal: Small units of understandable code that are amenable to change.
12 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
Our Three Main Tools
• Extract Method!
• Extract Class!
• Composed Method
13 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
Methods
14 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
“The object programs that live best and longest are those with
short methods.”!! ! ! ! ! ! -Refactoring by Fields, Harvey, Fowler, Black
15 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
The first rule of methods:
16 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
Do one thing. Do it well. Do only that thing.
17 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
Use Descriptive Names
18 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
The fewer arguments the better.
19 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
Separate Queries from Commands
20 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
Don’t Repeat Yourself
21 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
Let’s Build a Command Line Option Library
22 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!23
options = CommandLineOptions.new(ARGV) do option :c option :v option :e end !if options.has(:c) # Do something end !if options.has(:v) # Do something else end !if options.has(:e) # Do the other thing end
23 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!24
describe CommandLineOptions do ! describe 'options' do ! let(:parser) { CommandLineOptions.new { option :c } } ! it "are true if present" do … ! it "are false if absent" do … end end
24 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!25
CommandLineOptions options are true if present are false if absent !
Finished in 0.00206 seconds 2 examples, 2 failures
25 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!26
class CommandLineOptions ! attr_accessor :argv attr_reader :options ! def initialize (argv = [], &block) @options = [] @argv = argv instance_eval &block end ! def has (option_flag) options.include?(option_flag) && argv.include?("-#{option_flag}") end ! def option (option_flag) options << option_flag end !end
options = CommandLineOptions.new(ARGV) do option :c option :v option :e end
26 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!27
CommandLineOptions options are true if present are false if absent !
Finished in 0.00206 seconds 2 examples, 0 failures
27 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
Done!
28 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!29
# some_ruby_program -v -efoo !options = CommandLineOptions.new(ARGV) do option :v option :e, :string end !if options.has(:v) # Do something end !if options.has(:e) some_value = options.value(:e) # Do something with the expression end
29 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
describe CommandLineOptions do ! describe "boolean options" do let(:options) { CommandLineOptions.new { option :c } } it "are true if present" do … it "are false if absent" do … end ! describe "string options" do let(:options) { CommandLineOptions.new { option :e, :string } } it "must have content" do … it "can return the value" do … it "return nil if not in argv" do … end end
!30
30 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
CommandLineOptions boolean options are true if present are false if absent string options must have content (FAILED - 1) can return the value (FAILED - 2) return nil if not in argv (FAILED - 3)!Failures:! 1) CommandLineOptions string options must have content Failure/Error: expect(options.valid?).to be_false NoMethodError: undefined method `valid?' for #<CommandLineOptions:0x00000102cc8fa0> # ./spec/command_line_options_spec.rb:26:in `block (3 levels) in <top (required)>'! 2) CommandLineOptions string options can return the value Failure/Error: expect(options.value(:e)).to eq("foo") NoMethodError: undefined method `value' for #<CommandLineOptions:0x00000102cca1c0> # ./spec/command_line_options_spec.rb:31:in `block (3 levels) in <top (required)>'! 3) CommandLineOptions string options return nil if not in argv Failure/Error: expect(options.value(:e)).to be_nil NoMethodError: undefined method `value' for #<CommandLineOptions:0x00000102cc2a38> # ./spec/command_line_options_spec.rb:35:in `block (3 levels) in <top (required)>'!Finished in 0.00205 seconds5 examples, 3 failures
!31
31 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!32
def valid? options.each do |option_flag, option_type| raw_option_value = argv.find { |arg| arg =~ /-#{option_flag}/ } return false if raw_option_value.length < 3 && option_type == :string end end
32 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
CommandLineOptions boolean options are true if present are false if absent string options must have content can return the value (FAILED - 1) return nil if not in argv (FAILED - 2)!Failures:! 1) CommandLineOptions string options can return the value Failure/Error: expect(options.value(:e)).to eq("foo") NoMethodError: undefined method `value' for #<CommandLineOptions:0x00000101cbb6f8> # ./spec/command_line_options_spec.rb:31:in `block (3 levels) in <top (required)>'! 2) CommandLineOptions string options return nil if not in argv Failure/Error: expect(options.value(:e)).to be_nil NoMethodError: undefined method `value' for #<CommandLineOptions:0x00000101cba2a8> # ./spec/command_line_options_spec.rb:35:in `block (3 levels) in <top (required)>'!Finished in 0.00206 seconds5 examples, 2 failures
!33
33 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!34
def value (option_flag) raw_option_value = argv.find { |arg| arg =~ /-#{option_flag}/ } return nil unless raw_option_value option_type = options[option_flag] return raw_option_value[2..-1] if option_type == :string end
34 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!35
CommandLineOptions boolean options are true if present are false if absent string options must have content can return the value return nil if not in argv!
Finished in 0.00236 seconds5 examples, 0 failures
35 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
def valid? options.each do |option_flag, option_type| raw_option_value = argv.find { |arg| arg =~ /-#{option_flag}/ } return false if raw_option_value.length < 3 && option_type == :string end end ! def value (option_flag) raw_option_value = argv.find { |arg| arg =~ /-#{option_flag}/ } return nil unless raw_option_value option_type = options[option_flag] return raw_option_value[2..-1] if option_type == :string end
!36
36 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
Extract Method Refactoringdef print_invoice_for_amount (amount) print_header puts "Name: #{@name}" puts "Amount: #{amount}" end
def print_invoice_for_amount (amount) print_header print_details (amount) end !def print_details (amount) puts "Name: #{@name}" puts "Amount: #{amount}" end
High level of abstraction
Low level of abstraction
Same level of abstraction
Move this to here
37 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!38
def valid? options.each do |option_flag, option_type| return false if option_type == :string && raw_value_for_option(option_flag).length < 3 end end ! def value (option_flag) raw_option_value = raw_value_for_option(option_flag) return nil unless raw_option_value option_type = options[option_flag] return raw_option_value[2..-1] if option_type == :string end ! private def raw_value_for_option (option_flag) argv.find { |arg| arg =~ /-#{option_flag}/ } end
38 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!39
CommandLineOptions boolean options are true if present are false if absent string options must have content can return the value return nil if not in argv!
Finished in 0.00236 seconds5 examples, 0 failures
39 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!40
class CommandLineOptions ! attr_accessor :argv attr_reader :options ! def initialize (argv = [], &block) @options = {} @argv = argv instance_eval &block end ! def has (option_flag) options.include?(option_flag) && argv.include?("-#{option_flag}") end ! def option (option_flag, option_type = :boolean) options[option_flag] = option_type end ! def valid? options.each do |option_flag, option_type| return false if option_type == :string && raw_value_for_option(option_flag).length < 3 end end ! def value (option_flag) raw_option_value = raw_value_for_option(option_flag) return nil unless raw_option_value option_type = options[option_flag] return raw_option_value[2..-1] if option_type == :string end ! private def raw_value_for_option (option_flag) argv.find { |arg| arg =~ /-#{option_flag}/ } end end
40 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!41
def valid? options.each do |option_flag, option_type| return false if option_type == :string && raw_value_for_option(option_flag).length < 3 end end ! def value (option_flag) raw_option_value = raw_value_for_option(option_flag) return nil unless raw_option_value option_type = options[option_flag] return raw_option_value[2..-1] if option_type == :string end ! private def raw_value_for_option (option_flag) argv.find { |arg| arg =~ /-#{option_flag}/ } end
41 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
Done!
42 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!43
# some_ruby_program -v -efoo -i100 !
options = CommandLineOptions.new(ARGV) do option :v option :e, :string option :i, :integer end !
verbose = options.value(:v) expression = options.value(:e) iteration_count = options.value(:i) || 1
43 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!44
describe "integer options" do it "must have content" it "must be an integer" it "can return the value as an integer" it "returns nil if not in argv" end
44 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!45
CommandLineOptions boolean options are true if present are false if absent string options must have content can return the value return nil if not in argv integer options must have content (PENDING: No reason given) must be an integer (PENDING: Not yet implemented) can return the value as an integer (PENDING: Not yet implemented) returns nil if not in argv (PENDING: Not yet implemented)
45 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!46
let(:options) { CommandLineOptions.new { option :i, :integer } } ! it "must have content" do options.argv = [ "-i" ] expect(options.valid?).to be_false end
46 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!47
def valid? options.each do |option_flag, option_type| return false if option_type == :string && raw_value_for_option(option_flag).length < 3 end end
47 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!48
def valid? options.each do |option_flag, option_type| return false if (option_type == :string || option_type == :integer)&& raw_value_for_option(option_flag).length < 3 end end
48 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!49
it "must be an integer" do options.argv = [ "-inot_an_integer" ] expect(options.valid?).to be_false end
49 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!50
def valid? options.each do |option_flag, option_type| return false if (option_type == :string || option_type == :integer) && raw_value_for_option(option_flag).length < 3 return false unless option_type == :integer && (Integer(raw_value_for_option(option_flag)[2..-1]) rescue false) end end
50 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!51
CommandLineOptions boolean options are true if present are false if absent string options must have content can return the value return nil if not in argv integer options must have content must be an integer can return the value as an integer (PENDING: Not yet implemented) returns nil if not in argv (PENDING: Not yet implemented)
51 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!52
class CommandLineOptions ! attr_accessor :argv attr_reader :options ! def initialize (argv = [], &block) @options = {} @argv = argv instance_eval &block end ! def has (option_flag) options.include?(option_flag) && argv.include?("-#{option_flag}") end ! def option (option_flag, option_type = :boolean) options[option_flag] = option_type end ! def valid? options.each do |option_flag, option_type| return false if (option_type == :string || option_type == :integer) && raw_value_for_option(option_flag).length < 3 return false unless option_type == :integer && (Integer(raw_value_for_option(option_flag)[2..-1]) rescue false) end end ! def value (option_flag) raw_option_value = raw_value_for_option(option_flag) return nil unless raw_option_value option_type = options[option_flag] return raw_option_value[2..-1] if option_type == :string end ! private def raw_value_for_option (option_flag) argv.find { |arg| arg =~ /-#{option_flag}/ } end !end
52 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!54
def valid? options.each do |option_flag, option_type| return false if (option_type == :string || option_type == :integer) && raw_value_for_option(option_flag).length < 3 return false unless option_type == :integer && (Integer(raw_value_for_option(option_flag)[2..-1]) rescue false) end end
54 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!55
Integer("foo") rescue false #=> false "foo".to_i #=> 0 !!!!!!!
55 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!56
def valid? options.each do |option_flag, option_type| case(option_type) when :string return false if raw_value_for_option(option_flag).length < 3 when :integer return false if raw_value_for_option(option_flag).length < 3 return false unless (Integer(raw_value_for_option(option_flag)[2..-1]) rescue false) ! end end end
56 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!57
def valid? options.each do |option_flag, option_type| case(option_type) when :string return false if raw_value_for_option(option_flag).length < 3 when :integer return false if raw_value_for_option(option_flag).length < 3 return false unless (Integer(raw_value_for_option(option_flag)[2..-1]) rescue false) when :boolean end end end
57 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!58
CommandLineOptions boolean options are true if present are false if absent string options must have content can return the value return nil if not in argv integer options must have content must be an integer can return the value as an integer (PENDING: Not yet implemented) returns nil if not in argv (PENDING: Not yet implemented)
58 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!59
def valid? options.each do |option_flag, option_type| case(option_type) when :string return false unless string_option_valid?(raw_value_for_option(option_flag)) when :integer return false unless integer_option_valid?(raw_value_for_option(option_flag)) when :boolean end end end ! private def string_option_valid? (raw_value) extract_value_from_raw_value(raw_value).length > 0 end ! private def integer_option_valid? (raw_value) extract_value_from_raw_value(raw_value).length > 0 && (Integer(extract_value_from_raw_value(raw_value)) rescue false) end
59 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!60
private def boolean_option_valid? (raw_value) true end
60 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!61
CommandLineOptions boolean options are true if present are false if absent string options must have content can return the value return nil if not in argv integer options must have content must be an integer can return the value as an integer (PENDING: Not yet implemented) returns nil if not in argv (PENDING: Not yet implemented)
61 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!62
def valid? options.each do |option_flag, option_type| return false unless send("#{option_type}_option_valid?", raw_value_for_option(option_flag)) end end
62 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!63
def valid? options.each do |option_flag, option_type| return false unless option_valid?(option_type, raw_value_for_option(option_flag)) end end ! private def option_valid? (option_type, raw_value) send("#{option_type}_option_valid?", raw_value) end
63 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!64
CommandLineOptions boolean options are true if present are false if absent string options must have content is valid when there is content can return the value return nil if not in argv integer options must have content must be an integer can return the value as an integer returns nil if not in argv (PENDING: Not yet implemented)!Pending: CommandLineOptions integer options returns nil if not in argv # Not yet implemented # ./spec/command_line_options_spec.rb:61!Finished in 0.00291 seconds10 examples, 0 failures, 1 pending
64 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!65
class CommandLineOptions ! attr_accessor :argv attr_reader :options ! def initialize (argv = [], &block) @options = {} @argv = argv instance_eval &block end ! def has (option_flag) options.include?(option_flag) && argv.include?("-#{option_flag}") end ! def valid? options.each do |option_flag, option_type| return false unless option_valid?(option_type, raw_value_for_option(option_flag)) end end ! def value (option_flag) raw_option_value = raw_value_for_option(option_flag) return nil unless raw_option_value option_type = options[option_flag] return extract_value_from_raw_value(raw_option_value) if option_type == :string end ! private def option (option_flag, option_type = :boolean) options[option_flag] = option_type end ! private def option_valid? (option_type, raw_value) send("#{option_type}_option_valid?", raw_value) end ! private def string_option_valid? (raw_value) extract_value_from_raw_value(raw_value).length > 0 end ! private def integer_option_valid? (raw_value) extract_value_from_raw_value(raw_value).length > 0 && (Integer(extract_value_from_raw_value(raw_value)) rescue false) end ! private def boolean_option_valid? (raw_value) true end ! private def extract_value_from_raw_value (raw_value) raw_value[2..-1] end ! private def raw_value_for_option (option_flag) argv.find { |arg| arg =~ /-#{option_flag}/ } end !end
65 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!66
def has (option_flag) options.include?(option_flag) && argv.include?("-#{option_flag}") end ! def valid? options.each do |option_flag, option_type| return false unless option_valid?(option_type, raw_value_for_option(option_flag)) end end ! def value (option_flag) raw_option_value = raw_value_for_option(option_flag) return nil unless raw_option_value option_type = options[option_flag] return extract_value_from_raw_value(raw_option_value) if option_type == :string end
66 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
Classes
67 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
How do we write small classes?
• Write small methods!
• Talk to the class!
• Find a good name!
• Isolate Responsibilities!
• Find cohesive sets of variables/properties!
• Extract Class!
• Move method
68 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
What are the characteristics of a well designed small class?
• Single responsibility!
• Cohesive properties!
• Small public interface (preferably a handful of methods at the most)!
• Implements a single Use Case if possible!
• Primary logic is expressed in a composed method!
• Dependencies are injected
69 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!70
describe StringOption do let(:string_option) { StringOption.new('s', '-sfoo') } ! it "has a flag" it "is valid when it has a value" it "can return it's value when present" it "returns nil if the flag has no raw value" end
70 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!71
CommandLineOptions boolean options are true if present are false if absent string options must have content is valid when there is content can return the value return nil if not in argv integer options must have content must be an integer can return the value as an integer returns nil if not in argv (PENDING: Not yet implemented)!StringOption has a flag (PENDING: No reason given) is valid when it has a value (PENDING: Not yet implemented) can return it's value when present (PENDING: Not yet implemented) returns nil if the flag has no raw value (PENDING: Not yet implemented)
71 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!72
class StringOption !
attr_reader :flag !
def initialize (flag, raw_value) @flag = flag @raw_value = raw_value end !
end
72 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!73
it "is valid when it has a value" do expect(string_option.valid?).to be_true end
73 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!74
class CommandLineOptions ! private def string_option_valid? (raw_value) extract_value_from_raw_value(raw_value).length > 0 end !end
class StringOption ! attr_reader :flag, :raw_value ! def initialize (flag, raw_value) @flag = flag @raw_value = raw_value end ! def valid? extract_value_from_raw_value.length > 0 end ! private def extract_value_from_raw_value raw_value[2..-1] end !end
74 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!75
class StringOption ! attr_reader :flag, :raw_value ! def initialize (flag, raw_value) @flag = flag @raw_value = raw_value end ! def valid? extract_value_from_raw_value.length > 0 end ! private def extract_value_from_raw_value raw_value[2..-1] end !end
75 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!76
private def string_option_valid? (raw_value) StringOption.new("", raw_value).valid? end
76 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!77
class IntegerOption ! attr_reader :flag, :raw_value ! def initialize (flag, raw_value) @flag = flag @raw_value = raw_value end ! def valid? extract_value_from_raw_value.length > 0 && real_value_is_integer? end ! private def extract_value_from_raw_value raw_value[2..-1] end ! private def real_value_is_integer? (Integer(extract_value_from_raw_value) rescue false) end !end
77 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!78
class StringOption ! attr_reader :flag, :raw_value ! def initialize (flag, raw_value) @flag = flag @raw_value = raw_value end ! def valid? extract_value_from_raw_value.length > 0 end ! private def extract_value_from_raw_value raw_value[2..-1] end !end
78 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!79
class OptionWithContent ! attr_reader :flag, :raw_value ! def initialize (flag, raw_value) @flag = flag @raw_value = raw_value end ! def valid? extract_value_from_raw_value.length > 0 end ! private def extract_value_from_raw_value raw_value[2..-1] end !end
79 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!80
class StringOption < OptionWithContent end !class IntegerOption < OptionWithContent ! def valid? super && real_value_is_integer? end ! private def real_value_is_integer? (Integer(extract_value_from_raw_value) rescue false) end !end
80 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!81
CommandLineOptions boolean options are true if present are false if absent string options must have content is valid when there is content can return the value return nil if not in argv integer options must have content must be an integer can return the value as an integer returns nil if not in argv (PENDING: Not yet implemented)!StringOption has a flag is valid when it has a value can return it's value when present (PENDING: Not yet implemented) returns nil if the flag has no raw value (PENDING: Not yet implemented)
81 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
Dependencies
83 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
How do we deal with Dependencies?• Dependency Injection!
• Depend on Abstractions
82 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!84
private def option (option_flag, option_type = :boolean) options[option_flag] = option_type end
private def option (option_flag, option_type = :boolean) options[option_flag] = case (option_type) when :boolean return BooleanOption.new(option_flag, nil) when :string return StringOption.new(option_flag, nil) when :integer return IntegerOption.new(option_flag, nil) end end
private def option (option_flag, option_type = :boolean) option_class = "#{option_type}_option".camelize.constantize options[option_flag] = option_class.new(option_flag, nil) end
84 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
How do we isolate abstractions?
Separate the “what” from the “how”.
85 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!86
CommandLineOptions boolean options are true if present are false if absent string options must have content (FAILED - 1) is valid when there is content (FAILED - 2) can return the value (FAILED - 3) return nil if not in argv integer options must have content (FAILED - 4) must be an integer (FAILED - 5) can return the value as an integer returns nil if not in argv (PENDING: Not yet implemented)!StringOption has a flag is valid when it has a value can return it's value when present (PENDING: Not yet implemented) returns nil if the flag has no raw value (PENDING: Not yet implemented)
86 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!87
def valid? options.each do |option_flag, option_type| return false unless option_valid?(option_type, raw_value_for_option(option_flag)) end end
def valid? options.values.all?(&:valid?) end
87 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!88
CommandLineOptions boolean options are true if present are false if absent string options must have content is valid when there is content can return the value (FAILED - 1) return nil if not in argv integer options must have content must be an integer can return the value as an integer returns nil if not in argv (PENDING: Not yet implemented)!StringOption has a flag is valid when it has a value can return it's value when present (PENDING: Not yet implemented) returns nil if the flag has no raw value (PENDING: Not yet implemented)
88 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!89
def value (option_flag) options[option_flag].value end
def value (option_flag) raw_option_value = argv.find { |arg| arg =~ /-#{option_flag}/ } return nil unless raw_option_value option_type = options[option_flag] return raw_option_value[2..-1] if option_type == :string end
89 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!90
1) CommandLineOptions string options can return the value Failure/Error: expect(options.value(:e)).to eq("foo") NoMethodError: undefined method `value' for #<StringOption:0x00000101b85b30 @flag=:e, @raw_value="-efoo"> # ./lib/command_line_options.rb:26:in `value' # ./spec/command_line_options_spec.rb:36:in `block (3 levels) in <top (required)>'
90 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!91
# OptionWithContent def value return nil if option_unset? valid? ? extract_value_from_raw_value : nil end
# IntegerOption def value return if option_unset? Integer(extract_value_from_raw_value) end
91 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!92
CommandLineOptions boolean options are true if present are false if absent string options must have content is valid when there is content can return the value return nil if not in argv integer options must have content must be an integer can return the value as an integer returns nil if not in argv!OptionWithContent has a flag is valid when it has no raw value is valid when it has a value can return it's value when present returns nil if the flag has no raw value
92 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
Done!
93 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!94
class CommandLineOptions ! def initialize (argv = [], &block) @options = {} @argv = argv instance_eval &block populate_options_with_raw_values end ! def valid? options.values.all?(&:valid?) end ! def value (option_flag) options[option_flag].value end ! private ! attr_reader :options, :argv ! def populate_options_with_raw_values options.each do |option_flag, option| option.raw_value = raw_value_for_option(option_flag) end end ! def option (option_flag, option_type = :boolean) options[option_flag] = get_option_class(option_type).new(option_flag, nil) end ! def get_option_class (option_type) "#{option_type}_option".camelize.constantize end ! def raw_value_for_option (option_flag) argv.find { |arg| arg =~ /-#{option_flag}/ } end !end
94 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!95
!
def valid? options.values.all?(&:valid?) end !
def value (option_flag) options[option_flag].value end
95 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
Then
96 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!97
some_ruby_program -v -efoo -i100 -afoo,bar,baz
97 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!98
describe "array options" do it "can return the value as an array" do expect(CommandLineOptions.new([ "-afoo,bar,baz" ]) { option :a, :array }.value(:a)).to eq(["foo", "bar", "baz"]) end end
class ArrayOption < OptionWithContent def value return nil if option_unset? extract_value_from_raw_value.split(",") end end
98 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard!99
CommandLineOptions boolean options are true if present are false if absent string options must have content is valid when there is content can return the value return nil if not in argv integer options must have content must be an integer can return the value as an integer returns nil if not in argv array options can return the value as an array!OptionWithContent has a flag is valid when it has no raw value is valid when it has a value can return it's value when present returns nil if the flag has no raw value
99 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
Done!
100 WiteSmallThings - February 8, 2014
Enable Labs @mark_menard
Start Todayhttp://www.enablelabs.com/
866-895-8189
Enable Labs@mark_menard
101 WiteSmallThings - February 8, 2014