<?xml version="1.0" encoding="UTF-8" standalone="yes"?><oembed><version><![CDATA[1.0]]></version><provider_name><![CDATA[CloudForms Now]]></provider_name><provider_url><![CDATA[http://cloudformsblog.redhat.com]]></provider_url><author_name><![CDATA[Victor Estival Lopez]]></author_name><author_url><![CDATA[https://cloudformsblog.redhat.com/author/vestival271017/]]></author_url><title><![CDATA[Embedded Methods]]></title><type><![CDATA[link]]></type><html><![CDATA[<p><span style="font-weight:400;">One of the exciting new features in </span><a href="https://cloudformsblog.redhat.com/2018/03/01/announcing-general-availability-of-red-hat-cloudforms-4-6/"><span style="font-weight:400;">CloudForms 4.6</span></a><span style="font-weight:400;"> within Automate is Embedded Methods. That is, one can store reusable, directly callable, ruby code within Automate and access from other Automate Methods.</span></p>
<p><!--more--></p>
<p><span style="font-weight:400;">(In an attempt to maintain some sanity, I&#8217;m capitalizing words like Method when in the Automate sense, and leaving them lowercase when referring to the wholly unrelated ruby use).</span></p>
<p><span style="font-weight:400;">In my experimentation with </span><a href="https://github.com/jeffwarnica/manageiq-content-sample-tests"><span style="font-weight:400;">Unit Testing</span></a><span style="font-weight:400;">, I&#8217;ve developed a reasonable and, I think, future proof process for doing this. The implementation is very flexible, allows for multiple Methods to be embedded in the caller. Otherwise, I think, ad-hoc conventions could cause a lot of trouble if not used with some rigor.</span></p>
<p><span style="font-weight:400;">From the Calling Method, there is a new area in the UI to load in additional Methods. You may embed multiple Methods, but the embedding is not recursive; that is, if the target Method is configured to embed Methods, those will not be loaded.</span></p>
<p><span style="font-weight:400;">The method-to-be-Embedded, which I&#8217;ll call the Library Method needs no special configuration, though my strategy does require both methods to be coded in the </span><a href="https://github.com/ManageIQ/manageiq-content/issues/8"><span style="font-weight:400;">new style using classes</span></a><span style="font-weight:400;">.  The Library Method </span><i><span style="font-weight:400;">could</span></i><span style="font-weight:400;"> have &#8220;bare code&#8221;, that is, code not even in a ruby method, but that would almost always be a bad idea, as it would run unconditionally and be nigh impossible to troubleshoot.</span></p>
<p><span style="font-weight:400;">The ruby module/module/module namespaces match the Automate Domain/Namespace/Namespace/Class names. The Library Method&#8217;s name is not relevant. It is probably a good idea too, when configuring the calling Method, to always include the Domain Prefix in the method.</span></p>
<p><span style="font-weight:400;">I give you a sample (and use case #1) Library method:</span></p>
<pre><span style="font-weight:400;">#</span>
<span style="font-weight:400;"># Description: Core StdLib</span>
<span style="font-weight:400;">#</span>
<span style="font-weight:400;">module Cflab</span>
<span style="font-weight:400;">  module StdLib</span>
<span style="font-weight:400;">   module Core</span>
<span style="font-weight:400;">  </span> <span style="font-weight:400;">def initialize(handle = $evm)</span>
<span style="font-weight:400;">    </span> <span style="font-weight:400;">@handle = handle</span>
<span style="font-weight:400;">  </span> <span style="font-weight:400;">end</span>
<span style="font-weight:400;">  </span> <span style="font-weight:400;">def log(level, msg, update_message = false)</span>
<span style="font-weight:400;">    </span> <span style="font-weight:400;">@handle.log(level, "#{msg}")</span>
<span style="font-weight:400;">    </span> <span style="font-weight:400;">@handle.task.message = msg if @task &amp;&amp; (update_message || level == 'error')</span>
<span style="font-weight:400;">  </span> <span style="font-weight:400;">end</span>
<span style="font-weight:400;">  </span> <span style="font-weight:400;">def dump_root()</span>
<span style="font-weight:400;">    </span> <span style="font-weight:400;">log(:info, "Begin @handle.root.attributes")</span>
<span style="font-weight:400;">    </span> <span style="font-weight:400;">@handle.root.attributes.sort.each {|k, v| log(:info, "\t Attribute: #{k} = #{v}")}</span>
<span style="font-weight:400;">    </span> <span style="font-weight:400;">log(:info, "End @handle.root.attributes")</span>
<span style="font-weight:400;">    </span> <span style="font-weight:400;">log(:info, "")</span>
<span style="font-weight:400;">  </span> <span style="font-weight:400;">end</span>
<span style="font-weight:400;">   end</span>
<span style="font-weight:400;">  end</span>
<span style="font-weight:400;">end</span></pre>
<p><span style="font-weight:400;">Note significantly, that the ruby methods are not part of a class, but a module. This makes the module, in ruby speak, a </span><i><span style="font-weight:400;">mixin</span></i><span style="font-weight:400;">.  You don&#8217;t need the </span><i><span style="font-weight:400;">initialize</span></i><span style="font-weight:400;">() method unless you also plan on unit testing the code.</span></p>
<p><span style="font-weight:400;">In the Calling Method&#8217;s class, you must </span><i><span style="font-weight:400;">include</span></i><span style="font-weight:400;"> the ruby Module above, which brings in the ruby methods into the same namespace, e.g. </span><i><span style="font-weight:400;">include Cflab::StdLib::Core</span></i><span style="font-weight:400;"> per below.</span></p>
<pre><span style="font-weight:400;">module Cflab</span>
<span style="font-weight:400;">  module DynamicDialogs</span>
<span style="font-weight:400;">    module Methods
</span><span style="font-weight:400;">   </span> <span style="font-weight:400;">#</span>
<span style="font-weight:400;">  </span>  <span style="font-weight:400;"># Method for listing AWS flavors for a drop down.</span>
<span style="font-weight:400;">  </span>  <span style="font-weight:400;">#</span>
<span style="font-weight:400;">  </span>  <span style="font-weight:400;">class List_flavors</span>
<span style="font-weight:400;">    </span><span style="font-weight:400;">include Cflab::StdLib::Core</span>
<span style="font-weight:400;">    </span><span style="font-weight:400;">def initialize(handle = $evm)</span>
<span style="font-weight:400;">    </span><span style="font-weight:400;">@handle = handle</span>
<span style="font-weight:400;">   </span> <span style="font-weight:400;">end
</span>
<span style="font-weight:400;">   </span> <span style="font-weight:400;">#</span>
<span style="font-weight:400;">   </span> <span style="font-weight:400;"># Actual entry point the bare script will call into. Outputs into @handle</span>
<span style="font-weight:400;">   </span> <span style="font-weight:400;">#</span>
<span style="font-weight:400;">   </span> <span style="font-weight:400;">def main</span>
<span style="font-weight:400;">   </span> <span style="font-weight:400;">@user = @handle.root['user']</span>
<span style="font-weight:400;">   </span> <span style="font-weight:400;">dialog_hash = {}</span>
<span style="font-weight:400;">   </span> <span style="font-weight:400;">dump_root()</span>
<span style="font-weight:400;">   </span> <span style="font-weight:400;">@handle.vmdb(:ManageIQ_Providers_Amazon_CloudManager_Flavor).all.each do |flavor|</span>
<span style="font-weight:400;">        </span> <span style="font-weight:400;">next unless flavor.ext_management_system || flavor.enabled</span>
<span style="font-weight:400;">        </span> <span style="font-weight:400;">next unless object_eligible?(flavor)</span>
<span style="font-weight:400;">        </span> <span style="font-weight:400;">@handle.log(:info, "Inspecting flavor: [#{flavor}]")</span>
<span style="font-weight:400;">        </span> <span style="font-weight:400;">dialog_hash[flavor.id] = "#{flavor.name} on #{flavor.ext_management_system.name}"</span>
<span style="font-weight:400;">   </span> <span style="font-weight:400;">end</span>
<span style="font-weight:400;">   </span> <span style="font-weight:400;">if dialog_hash.blank?</span>
<span style="font-weight:400;">        </span> <span style="font-weight:400;">dialog_hash[''] = "&lt; no flavors found &gt;"</span>
<span style="font-weight:400;">        </span> <span style="font-weight:400;">@handle.object['default_value'] = '&lt; no flavors found &gt;'</span>
<span style="font-weight:400;">   </span> <span style="font-weight:400;">else</span>
<span style="font-weight:400;">        </span> <span style="font-weight:400;">@handle.object['default_value'] = dialog_hash.first[0]</span>
<span style="font-weight:400;">   </span> <span style="font-weight:400;">end</span>
<span style="font-weight:400;">   </span> <span style="font-weight:400;">@handle.object["values"] = dialog_hash</span>
<span style="font-weight:400;">   </span> <span style="font-weight:400;">end</span>
<span style="font-weight:400;">   </span> <span style="font-weight:400;">end</span>
<span style="font-weight:400;">    end</span>
<span style="font-weight:400;">  end</span>
<span style="font-weight:400;">end</span>

<span style="font-weight:400;">if __FILE__ == $PROGRAM_NAME</span>
<span style="font-weight:400;">  Cflab::DynamicDialogs::Methods::List_flavors.new.main</span>
<span style="font-weight:400;">end</span></pre>
<p><span style="font-weight:400;">You are free to not include the Library module into your main class, but then would need to call the methods with their full path, </span></p>
<pre><span style="font-weight:400;">Cflab::StdLib::Core::log(:info, 'stuff happened')</span></pre>
<p><span style="font-weight:400;">The possibilities with Embedded Methods are endless, and I look forward to building up some truly reusable, generic, well tested, and well documented collection of useful utilities.</span></p>
<p>&nbsp;</p>
]]></html><thumbnail_url><![CDATA[https://cloudformsredhat.files.wordpress.com/2018/04/table-2584957_640.jpg?fit=440%2C330]]></thumbnail_url><thumbnail_width><![CDATA[440]]></thumbnail_width><thumbnail_height><![CDATA[293]]></thumbnail_height></oembed>