Class: Chieftain::Command
- Inherits:
-
Object
- Object
- Chieftain::Command
- Defined in:
- lib/chieftain/command.rb
Overview
An implementation of the Command design pattern that aims to take some advantage of Ruby’s enhanced capabilities.
Defined Under Namespace
Constant Summary collapse
- @@convertors =
{self => {}}
- @@parameters =
{self => {}}
- @@validators =
{self => {}}
Instance Attribute Summary collapse
-
#convertors ⇒ Object
readonly
Returns the value of attribute convertors.
-
#errors ⇒ Object
readonly
Returns the value of attribute errors.
-
#parameters ⇒ Object
readonly
Returns the value of attribute parameters.
-
#settings ⇒ Object
readonly
Returns the value of attribute settings.
-
#validators ⇒ Object
readonly
Returns the value of attribute validators.
Class Method Summary collapse
-
.add_convertor(name, convertor_class) ⇒ Object
Registers a convertor for a Command class.
-
.add_validator(name, &block) ⇒ Object
Registers a validator for a Command class.
-
.convertors_for(command_class) ⇒ Object
This method scans the class hierarchy for a Command instance and assembles a list of the registered convertors for it.
-
.optional(name, settings = {}, &block) ⇒ Object
Registers an optional parameter for the command.
-
.parameter(name, settings = {}, &block) ⇒ Object
Register a new parameter for a Command class.
-
.parameters(command_class) ⇒ Object
Fetches the parameter list registered for a specific Command class instance.
-
.required(name, settings = {}, &block) ⇒ Object
Registers an optional parameter for the command.
-
.validate(name, &block) ⇒ Object
A synomym for the #add_validator() method that is intended for use with a validator that matches a parameter name.
-
.validators_for(command_class) ⇒ Object
This method scans the class hierarchy for a Command instance and assembles a list of the registered validators for it.
Instance Method Summary collapse
-
#convertible?(name, value) ⇒ Boolean
Test whether a given value is convertible for a named parameter.
-
#error(message, code = nil) ⇒ Object
Register an error with the execution of the current Command.
-
#execute ⇒ Object
Invokes the #perform() method if and only if the Command instance tests as valid.
-
#expected_parameter_names ⇒ Object
Returns a list of the expected parameters configured for a Command instance.
-
#expects?(parameter) ⇒ Boolean
Tests whether a parameter name is among the parameters specified for the Command instance.
-
#get_convertor(type) ⇒ Object
Fetches a name convertor from the list for the Command instance, raises an exception if one cannot be found.
-
#get_parameter_value(name) ⇒ Object
Retrieve the value for a named parameter.
-
#get_raw_parameter_value(name) ⇒ Object
Fetches the raw, unaltered value specified for a name parameter to the Command instance.
-
#has_convertor?(name) ⇒ Boolean
This method tests whether a named convertor is available to a Command instance.
-
#initialize(parameters = {}) ⇒ Command
constructor
A new instance of Command.
-
#method_missing(name, *arguments, &block) ⇒ Object
An implementation of the #method_missing method for the Command class that checks whether a parameter is being requested and, if so, returns it’s value or delegates handling to the parent class implementation.
-
#optional_parameter_names ⇒ Object
Returns a list of the names of the commands optional parameters.
-
#parameter_names ⇒ Object
Returns a list of the names of the parameters specified to the Command instance.
-
#perform ⇒ Object
Derived command classes should override this method to do the work for the command.
-
#provided?(name) ⇒ Boolean
This method checks whether a name parameter is among those provided to a Command instance.
-
#required_parameter_names ⇒ Object
Returns a list of the names of the commands required parameters.
-
#settings_for(name) ⇒ Object
Retrieves the parameter settings for a named parameter.
-
#valid? ⇒ Boolean
Invokes the validate command and then checks that there are no errors registered for the command.
-
#validate ⇒ Object
Performs validation of the parameters passed to a command.
-
#validations_for(name) ⇒ Object
Returns a list of the validators that apply to a named parameter.
Constructor Details
#initialize(parameters = {}) ⇒ Command
Returns a new instance of Command.
50 51 52 53 54 55 56 |
# File 'lib/chieftain/command.rb', line 50 def initialize(parameters={}) @convertors = Command.convertors_for(self.class) @errors = [] @parameters = {}.merge(parameters).inject({}) {|t,v| t[v[0].to_s.to_sym] = v[1]; t} @settings = Command.parameters(self.class) @validators = Command.validators_for(self.class) end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(name, *arguments, &block) ⇒ Object
An implementation of the #method_missing method for the Command class that checks whether a parameter is being requested and, if so, returns it’s value or delegates handling to the parent class implementation.
178 179 180 181 182 183 184 |
# File 'lib/chieftain/command.rb', line 178 def method_missing(name, *arguments, &block) if expects?(name) get_parameter_value(name) else super end end |
Instance Attribute Details
#convertors ⇒ Object (readonly)
Returns the value of attribute convertors.
57 58 59 |
# File 'lib/chieftain/command.rb', line 57 def convertors @convertors end |
#errors ⇒ Object (readonly)
Returns the value of attribute errors.
57 58 59 |
# File 'lib/chieftain/command.rb', line 57 def errors @errors end |
#parameters ⇒ Object (readonly)
Returns the value of attribute parameters.
57 58 59 |
# File 'lib/chieftain/command.rb', line 57 def parameters @parameters end |
#settings ⇒ Object (readonly)
Returns the value of attribute settings.
57 58 59 |
# File 'lib/chieftain/command.rb', line 57 def settings @settings end |
#validators ⇒ Object (readonly)
Returns the value of attribute validators.
57 58 59 |
# File 'lib/chieftain/command.rb', line 57 def validators @validators end |
Class Method Details
.add_convertor(name, convertor_class) ⇒ Object
Registers a convertor for a Command class. A convertor is any class that can be constructed using a default constructor and responds to the #convertible?() and #convert() methods. Both of these methods take a single parameter which is the value to undergo conversion. The #convertible?() method returns true if it’s possible to convert the value to the convertors output type. The #convert() method performs the actual conversion, returning the result.
297 298 299 300 301 302 303 304 |
# File 'lib/chieftain/command.rb', line 297 def self.add_convertor(name, convertor_class) @@convertors[self] = {} if !@@convertors.include?(self) if @@convertors[self].include?(name) raise CommandError.new("Duplicate convertor '#{name}' specified for the #{self.name} class.") end @@convertors[self][name] = convertor_class end |
.add_validator(name, &block) ⇒ Object
Registers a validator for a Command class. A validator has to be registered with a block that will be invoked for the relevant parameters. This block should take 3 parameters. The first is the command object being executed. The second is the name of the parameter being validated. The third is the value of the parameter being validated. Validators can register errors by invoking the #error() method on the command they are passed.
312 313 314 315 316 317 318 319 320 321 322 323 |
# File 'lib/chieftain/command.rb', line 312 def self.add_validator(name, &block) @@validators[self] = {} if !@@validators.include?(self) if @@validators[self].include?(name) raise CommandError.new("Duplicate validator '#{name}' specified for the #{self.name} class.") end if !block raise CommandError.new("No block specified for the '#{name}' validator in the #{self.name} class.") end @@validators[self][name] = block end |
.convertors_for(command_class) ⇒ Object
This method scans the class hierarchy for a Command instance and assembles a list of the registered convertors for it. Convertors registered in classes lower in the hierarchy (i.e. derived classes) override those registered in parent classes.
329 330 331 332 333 334 335 336 337 338 339 340 |
# File 'lib/chieftain/command.rb', line 329 def self.convertors_for(command_class) hierarchy = [command_class] while !hierarchy.last.superclass.nil? hierarchy << hierarchy.last.superclass end convertors = {} hierarchy.reverse.each do |c| convertors.merge!(@@convertors[c]) if @@convertors.include?(c) end convertors.inject({}) {|list, entry| list[entry[0]] = entry[1].new; list} end |
.optional(name, settings = {}, &block) ⇒ Object
Registers an optional parameter for the command. See the #parameter() method for details of the parameters this method accepts.
344 345 346 |
# File 'lib/chieftain/command.rb', line 344 def self.optional(name, settings={}, &block) parameter(name, {}.merge(settings, {required: false}), &block) end |
.parameter(name, settings = {}, &block) ⇒ Object
Register a new parameter for a Command class. The first method parameter specifies the new parameters name. This can be followed by a Hash of settings value for the parameter. All keys in this Hash should be symbols and the following keys are currently recognised - :required, :types and :validators. You can also register a block for a parameter. This block will be invoked with the raw parameter value and the return value from this block will become the actual parameter value used.
355 356 357 358 359 360 361 |
# File 'lib/chieftain/command.rb', line 355 def self.parameter(name, settings={}, &block) if self.method_defined?(name) raise ParameterError.new("The '#{name}' parameter clashes with an existing class method.", name) end @@parameters[self] = {} if !@@parameters.include?(self) @@parameters[self][name] = OpenStruct.new({}.merge(settings, {name: name, block: block})) end |
.parameters(command_class) ⇒ Object
Fetches the parameter list registered for a specific Command class instance.
365 366 367 |
# File 'lib/chieftain/command.rb', line 365 def self.parameters(command_class) @@parameters[command_class] || {} end |
.required(name, settings = {}, &block) ⇒ Object
Registers an optional parameter for the command. See the #parameter() method for details of the parameters this method accepts.
371 372 373 |
# File 'lib/chieftain/command.rb', line 371 def self.required(name, settings={}, &block) parameter(name, {}.merge(settings, {required: true}), &block) end |
.validate(name, &block) ⇒ Object
A synomym for the #add_validator() method that is intended for use with a validator that matches a parameter name.
377 378 379 |
# File 'lib/chieftain/command.rb', line 377 def self.validate(name, &block) add_validator(name, &block) end |
.validators_for(command_class) ⇒ Object
This method scans the class hierarchy for a Command instance and assembles a list of the registered validators for it. Validators registered in classes lower in the hierarchy (i.e. derived classes) override those registered in parent classes.
385 386 387 388 389 390 391 392 393 394 395 396 |
# File 'lib/chieftain/command.rb', line 385 def self.validators_for(command_class) hierarchy = [command_class] while !hierarchy.last.superclass.nil? hierarchy << hierarchy.last.superclass end validators = {} hierarchy.reverse.each do |c| validators.merge!(@@validators[c]) if @@validators.include?(c) end validators end |
Instance Method Details
#convertible?(name, value) ⇒ Boolean
Test whether a given value is convertible for a named parameter. This will return true if the parameter is expected and either has no type specified or the value given can be converted to the parameters specified type.
62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/chieftain/command.rb', line 62 def convertible?(name, value) result = false if expects?(name) result = true settings = @settings[name] if settings.type result = get_convertor(settings.type).convertible?(value) end end result end |
#error(message, code = nil) ⇒ Object
Register an error with the execution of the current Command.
75 76 77 |
# File 'lib/chieftain/command.rb', line 75 def error(, code=nil) @errors << Error.new(, code) end |
#execute ⇒ Object
Invokes the #perform() method if and only if the Command instance tests as valid. This method should be the one invoked to run a Command instance.
81 82 83 84 85 86 |
# File 'lib/chieftain/command.rb', line 81 def execute @errors = [] value = nil value = perform if valid? Result.new(value, errors) end |
#expected_parameter_names ⇒ Object
Returns a list of the expected parameters configured for a Command instance.
90 91 92 |
# File 'lib/chieftain/command.rb', line 90 def expected_parameter_names @settings ? @settings.values.map(&:name) : [] end |
#expects?(parameter) ⇒ Boolean
Tests whether a parameter name is among the parameters specified for the Command instance.
96 97 98 |
# File 'lib/chieftain/command.rb', line 96 def expects?(parameter) expected_parameter_names.include?(parameter) end |
#get_convertor(type) ⇒ Object
Fetches a name convertor from the list for the Command instance, raises an exception if one cannot be found.
153 154 155 156 157 158 |
# File 'lib/chieftain/command.rb', line 153 def get_convertor(type) if !has_convertor?(type) raise CommandError.new("Unable to locate the '#{type}' parameter convertor.") end @convertors[type] end |
#get_parameter_value(name) ⇒ Object
Retrieve the value for a named parameter. The value will be run through an applicable converted prior to being returned. An exception will be raised if conversion fails. If the parameter is optional and has not be specified then conversion will not be attempted and nil will be returned.
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/chieftain/command.rb', line 104 def get_parameter_value(name) if expects?(name) settings = settings_for(name) if settings[:required] && !provided?(name) raise ParameterError.new("A value has not been provided for the '#{name}' parameter.", name) end if settings[:required] raw_value = get_raw_parameter_value(name) if settings[:type] convertor = get_convertor(settings.type) if !convertor.convertible?(raw_value) raise ParameterError.new("The value of the '#{name}' parameter cannot be converted to the '#{settings.type}' type.", name) end convertor.convert(raw_value) else raw_value end else raw_value = nil if provided?(name) raw_value = get_raw_parameter_value(name) raw_value = settings[:default] if raw_value.nil? else raw_value = settings[:default] end if provided?(name) if settings[:type] convertor = get_convertor(settings.type) if !convertor.convertible?(raw_value) raise ParameterError.new("The value of the '#{name}' parameter cannot be converted to the '#{settings.type}' type.", name) end convertor.convert(raw_value) else raw_value end else raw_value end end else raise ParameterError.new("Unknown parameter '#{name}' requested from a '#{self.class.name}' command instance.") end end |
#get_raw_parameter_value(name) ⇒ Object
Fetches the raw, unaltered value specified for a name parameter to the Command instance. Returns nil if the specified parameter has not been given an explicit value. Raises an exception if an unknown parameter is specified.
164 165 166 167 |
# File 'lib/chieftain/command.rb', line 164 def get_raw_parameter_value(name) raise ParameterError.new("Unknown parameter '#{name}' requested in command.", name) if !expects?(name) @parameters[name] end |
#has_convertor?(name) ⇒ Boolean
This method tests whether a named convertor is available to a Command instance.
171 172 173 |
# File 'lib/chieftain/command.rb', line 171 def has_convertor?(name) @convertors.include?(name) end |
#optional_parameter_names ⇒ Object
Returns a list of the names of the commands optional parameters.
187 188 189 |
# File 'lib/chieftain/command.rb', line 187 def optional_parameter_names settings.values.filter {|p| !p.required}.map(&:name) end |
#parameter_names ⇒ Object
Returns a list of the names of the parameters specified to the Command instance.
193 194 195 |
# File 'lib/chieftain/command.rb', line 193 def parameter_names @settings.keys end |
#perform ⇒ Object
Derived command classes should override this method to do the work for the command. This method will only get invoked if the command is valid. This default implementation raises an exception.
200 201 202 |
# File 'lib/chieftain/command.rb', line 200 def perform raise CommandError.new("The #{self.class.name} command class has not overridden the #perform() method.") end |
#provided?(name) ⇒ Boolean
This method checks whether a name parameter is among those provided to a Command instance.
206 207 208 |
# File 'lib/chieftain/command.rb', line 206 def provided?(name) @parameters.include?(name) end |
#required_parameter_names ⇒ Object
Returns a list of the names of the commands required parameters. Note a required parameter must have a value specified for it when the command is executed.
213 214 215 |
# File 'lib/chieftain/command.rb', line 213 def required_parameter_names settings.values.filter {|p| p.required}.map(&:name) end |
#settings_for(name) ⇒ Object
Retrieves the parameter settings for a named parameter. Raises an exception if an unknown parameter is specified.
219 220 221 222 223 |
# File 'lib/chieftain/command.rb', line 219 def settings_for(name) raise ParameterError("Unknown parameter '#{name}' requested in command.", name) if !expects?(name) entry = @settings.find {|entry| entry[1].name == name} entry ? entry[1] : nil end |
#valid? ⇒ Boolean
Invokes the validate command and then checks that there are no errors registered for the command.
262 263 264 265 266 |
# File 'lib/chieftain/command.rb', line 262 def valid? @errors = [] validate @errors.empty? end |
#validate ⇒ Object
Performs validation of the parameters passed to a command. Deriving classes should ensure this method is invoked in any custom #validate method their class provides.
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 |
# File 'lib/chieftain/command.rb', line 228 def validate @settings.values.each do |parameter| if provided?(parameter.name) if parameter.type # Check conversion. if has_convertor?(parameter.type) convertor = get_convertor(parameter.type) if !convertor.convertible?(get_raw_parameter_value(parameter.name)) error("The value of the '#{parameter.name}' parameter cannot be converted to the '#{parameter.type}' type.") end else error("Invalid type '#{parameter.type}' specified for the '#{parameter.name}' parameter.") end end # Run validations. if convertible?(parameter.name, get_raw_parameter_value(parameter.name)) value = get_parameter_value(parameter.name) validations_for(parameter.name).each do |validation| self.instance_exec(parameter.name, value, &validation) end else error("The value of the '#{parameter.name}' parameter cannot be converted to the '#{parameter.type}' type.") end else if parameter.required error("No value specified for the '#{parameter.name}' required parameter.") end end end end |
#validations_for(name) ⇒ Object
Returns a list of the validators that apply to a named parameter. This will be a combination of validators explicitly declared on the parameter and class validators with the same name as the parameter. The method raises an exception if given the name of a parameter that the Command instance does not expect. It can also raise an exception if a parameter has an unknown validator specified for it.
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 |
# File 'lib/chieftain/command.rb', line 274 def validations_for(name) if !expects?(name) raise ParameterError.new("Validators requested for unknown parameter '#{name}'.", name) end settings = @settings[name] names = [] names << name if @validators.include?(name) names = names.concat(settings.validations) if settings.validations names.uniq.map do |key| if !@validators.include?(key) raise ParameterError.new("Unknown validation '#{key}' requested for the '#{name}' parameter.", name) end @validators[key] end end |