:toc: macro :toclevels: 5 :figure-caption!: = Marameters Marameters is short for method parameters (i.e. `[m]ethod + p[arameters] = marameters`) which is designed to provide additional insight and diagnostics to method parameters. For context, the difference between a method's parameters and arguments is: * *Parameters*: Represents the _expected_ values to be passed to a method when messaged as defined when the method is implemented. Example: `def demo(one, two: nil)`. * *Arguments*: Represents the _actual_ values passed to the method when messaged. Example: `demo 1, two: 2`. This gem will help you debug methods or -- more importantly -- aid your workflow when metaprogramming, as used in the link:https://www.alchemists.io/projects/auto_injector[Auto Injector] gem, when architecting more sophisticated applications. toc::[] == Features * Provides a core object as a primary interface. * Provides specialized objects for keyword, positional, and splatted parameters. == Requirements . link:https://www.ruby-lang.org[Ruby]. == Setup To install, run: [source,bash] ---- gem install marameters ---- Add the following to your Gemfile file: [source,ruby] ---- gem "marameters" ---- == Usage There are two main objects you'll want to interact with: * *Analyzer*: Allows you to analyze a method's parameters. * *Signature*: Allows you to dynamically build a method signature. Both of these objects are meant to serve as building blocks to more complex architectures. === Analyzer To understand how to _analyze_ a method's parameters, consider the following demonstration class: [source,ruby] ---- class Demo def initialize logger: Logger.new(STDOUT) @logger = logger end def all one, two = nil, *three, four:, five: nil, **six, &seven logger.debug [one, two, three, four, five, six, seven] end def none = logger.debug "Nothing to see here." private attr_reader :logger end ---- You can then analyze the `#all` method's parameters as follows: [source,ruby] ---- analyzer = Marameters::Analyzer.new Demo.instance_method(:all).parameters analyzer.block # :seven analyzer.block? # true analyzer.empty? # false analyzer.keywords # [:four, :five] analyzer.keywords? # true analyzer.kind?(:keyrest) # true analyzer.kinds # [:req, :opt, :rest, :keyreq, :key, :keyrest, :block] analyzer.name?(:three) # true analyzer.names # [:one, :two, :three, :four, :five, :six, :seven] analyzer.only_bare_splats? # false analyzer.only_double_splats? # false analyzer.only_single_splats? # false analyzer.positionals # [:one, :two] analyzer.positionals? # true analyzer.splats # [:three, :six] analyzer.splats? # true analyzer.to_a # [[:req, :one], [:opt, :two], [:rest, :three], [:keyreq, :four], [:key, :five], [:keyrest, :six], [:block, :seven]] analyzer.to_h # {:req=>:one, :opt=>:two, :rest=>:three, :keyreq=>:four, :key=>:five, :keyrest=>:six, :block=>:seven} ---- In contrast the above, we can also analyze the `#none` method which has no parameters for a completely different result: [source,ruby] ---- analyzer = Marameters::Analyzer.new Demo.instance_method(:none).parameters analyzer.block # nil analyzer.block? # false analyzer.empty? # true analyzer.keywords # [] analyzer.keywords? # false analyzer.kind?(:req) # true analyzer.kinds # [] analyzer.name?(:three) # false analyzer.names # [] analyzer.only_bare_splats? # false analyzer.only_double_splats? # false analyzer.only_single_splats? # false analyzer.positionals # [] analyzer.positionals? # false analyzer.splats # [] analyzer.splats? # false analyzer.to_a # [] analyzer.to_h # {} ---- === Signature The signature class is the opposite of the analyzer in that you want to feed it parameters for turning into a method signature. This is useful when dynamically building method signatures or using the same signature for multiple methods when metaprogramming. The following demonstrates how you might construct a method signature with all possible parameters: [source,ruby] ---- signature = Marameters::Signature.new( { req: :one, opt: [:two, 2], rest: :three, keyreq: :four, key: [:five, 5], keyrest: :six, block: :seven } ) puts signature # one, two = 2, *three, four:, five: 5, **six, &seven ---- You'll notice that the parameters is a hash _and_ some values can be tuples. The reason is that it's easier to write a hash than a double nested array as normally produced by the analyzer or directly from `Method#parameters`. The optional positional and keyword parameters use tuples because you might want to supply a default value and this provides a way for you to do that with minimal syntax. This can be demonstrated further by using optional keywords (same applies for optional positionals): [source,ruby] ---- # With no default puts Marameters::Signature.new({key: :demo}) # demo: nil # With explicit nil as default puts Marameters::Signature.new({key: [:demo, nil]}) # demo: nil # With string as default puts Marameters::Signature.new({key: [:demo, "test"]}) # demo: "test" # With symbol as default puts Marameters::Signature.new({key: [:demo, :test]}) # demo: :test # With object(dependency) as default puts Marameters::Signature.new({key: [:demo, "*Object.new"]}) # demo: Object.new ---- In the case of object dependencies you need to wrap these in a string _and_ prefix them with a star (`*`) so the signature builder won't confuse these as a normal string. There are two reasons why this is important: * The star (`*`) signifies that you want the object to be passed through without any further processing while also not being confused normal strings. * Objects wrapped as strings allows your dependency to be lazy loaded. Otherwise, if `Object.new` was pass directly, you'd be passing the evaluated instance (i.e. `#`) which is not what you want until much later when your method is defined. When you put all of this together, you can dynamically build a method as follows: [source,ruby] ---- signature = Marameters::Signature.new({opt: [:text, "This is a test."]}) Example = Module.new do module_eval <<~DEFINITION, __FILE__, __LINE__ + 1 def self.say #{signature} text end DEFINITION end puts Example.say # This is a test. puts Example.say "Hello" # Hello ---- == Development You can also use the IRB console for direct access to all objects: [source,bash] ---- bin/console ---- == Tests To test, run: [source,bash] ---- bundle exec rake ---- == link:https://www.alchemists.io/policies/license[License] == link:https://www.alchemists.io/policies/security[Security] == link:https://www.alchemists.io/policies/code_of_conduct[Code of Conduct] == link:https://www.alchemists.io/policies/contributions[Contributions] == link:https://www.alchemists.io/projects/marameters/versions[Versions] == link:https://www.alchemists.io/community[Community] == Credits * Built with link:https://www.alchemists.io/projects/gemsmith[Gemsmith]. * Engineered by link:https://www.alchemists.io/team/brooke_kuhlmann[Brooke Kuhlmann].