Call Fastlane actions from a ruby module - ruby

I'm trying to make a ruby module with some helper functions that I use in the Fastfile. It looks as follows:
lane :a do |options|
Utils.foo
end
module Utils
def self.foo
get_info_plist_value(...)
end
end
When I try to run the lane I get this error: undefined method 'get_info_plist_value' for Utils:Module.
I've tried the following ways to solve this problem:
adding extend Utils after the module definition
including Fastlane or Fastlane::Actions into the module
These didn't help me.
Are there any other ways to solve the problem?

I've gotten a util like this to work using dependency injection:
lane :a do |options|
Utils.call(self)
end
module Utils
def initialize(lane)
#lane = lane
end
def self.call(lane)
new.execute(lane)
end
def execute
#lane.get_info_plist_value(...)
end
end
If you look at the anatomy of a declared "lane" (:a, for example), each action (like get_info_plist_value) is executed within Fastlane::Lane's block, which was instantiated by the Fastfile.
Although possible, writing utils that call Fastlane actions should be used sparingly. This seems fairly out of scope from intended usages of Fastlane. I think the "right" way to approach something like this is to actually write a custom action (a bit more verbose, but likely the more maintainable option).
As the saying goes, "Don't fight the framework!"

Related

Chef NoMethodError when using library module method with registry_key in recipe

I receive the error
NoMethodError
-------------
undefined method `registry_key' for HashOperations:Module
when converging a Chef cookbook.
This is the short version of the code from libraries/hash_operations.rb:
module HashOperations
# Tried: require 'chef/mixin/shell_out'
# Tried: include Chef::Mixin::ShellOut
def self.create_reg_keys(item_hash)
item_hash.each do |item_name, item_props|
# (...) construct the item_keys array
registry_key "HKEY_LOCAL_MACHINE\\SOFTWARE\\#{item_name}" do
recursive true
values item_keys
action :create
end
end
end
def self.generate_reg_keys_for_item_key(...)
# some hash operations here
create_reg_keys(item_hash)
end
end unless defined?(HashOperations)
class Chef::Resource
# Tried: Chef::Resource.send(:include, HashOperations)
# Tried: include HashOperations
extend HashOperations
end
and here is recipes/default.rb:
Chef::Resource.send(:include, HashOperations)
ruby_block "test" do
block do
# Tried: Chef::Recipe.send(:include, HashOperations)
items_keys.each do |item_key|
HashOperations.generate_reg_keys_for_item_key(..., item_key)
end
end
end
I guess the main problem comes from trying to use a Chef resource, registry_key, inside a method inside a module, which in turn is called from the recipe.
I have a working version if I'm not using a module, but I need a module if I want to test the code with ChefSpec, as several articles pointed (like this one: Stubbing library class methods in ChefSpec )
The link mentioned above is the reason for which I use end unless defined?(HashOperations) inside the module.
I've tried using include statements, it can be seen in the comments, or in the recipe's first line, as several StackOverflow posts suggested, with no luck. One post was discussing the usage of LWRP, but I really don't think it's the case here, as the code is strictly related to this recipe and wouldn't be used in some other cookbook.
As a note: I'm using self. in order for the defs to be visible to one another, otherwise I receive an error about generate_reg_keys_for_item_key not being available.
So, taking into account the fact that I've spent quite some hours searching for solutions on this, including the ones suggested by StackOverflow, the questions: what's the best approach to solve this error and have a simple solution that can be tested with ChefSpec (I'm not entirely excluding LWRPs though), and WHAT and HOW should I include for the registry_key to be visible on converge operation ?
You can't use the recipe DSL from helpers like this unless the helper is itself set up as a DSL extension. Check out https://coderanger.net/chef-tips/#3 to see how to do that.

Multiple classes of the same name in Ruby

I have an existing codebase of unit tests where the same classes are defined for each test, and a program that iterates over them. Something like this:
test_files.each do |tf|
load "tests/#{tf}/"+tf
test= ::Kernel.const_get("my_class")
Test::Unit::UI::Console::TestRunner.run( test )
While working on these tests, I've realized that ruby allows you to require classes with the same name from different files, and it merges the methods of the two. This leads to problems as soon as the class hierarchy is not the same: superclass mismatch for class ...
Unfortunately, simply unloading the class is not enough, as there are many other classes in the hierarchy that remain loaded.
Is there a way to execute each test in a different global namespace?
While I figure that using modules would be the way to go, I'm not thrilled with idea of changing the hundreds of existing files by hand.
--EDIT--
Following Cary Swoveland's suggestion, I've moved the test running code to a separate .rb file and am running it using backticks. While this seems to work well, loading the ruby interpreter and requireing all the libraries again has a considerable overhead. Also, cross-platform compatibility requires some extra work.
first I wanted to propose the following solution:
#main.rb
modules = []
Dir.foreach('include') do |file|
if file =~ /\w/
new_module = Module.new
load "include/#{file}"
new_module.const_set("StandardClass", StandardClass)
Object.send(:remove_const, :StandardClass)
modules << new_module
end
end
modules.each do |my_module|
my_module::StandardClass.standard_method
end
#include/first.rb
class StandardClass
def self.standard_method
puts "first method"
end
end
#include/second.rb
class StandardClass
def self.standard_method
puts "second method"
end
end
this wraps each class with it's own module and calls the class inside it's module:
$ruby main.rb
second method
first method
But as you answered there are references to other classes so modules can break them.
I checked this by calling the third class from one of the wrapped classes and got:
include/second.rb:5:in `standard_method': uninitialized constant StandardClass::Independent (NameError)
so probably using backticks is better solution but I hope my answer will be helpful for some other similar situations.

referring to module level variables from within module

I'm having some difficulty with referring to module-level variables in ruby. Say I have a situation like this, where I'm referring to M.a internally:
module M
##a=1
def self.a
##a
end
class A
def x
M.a
end
end
end
Now, this example works fine for me but it is failing in a slightly more complicated context (where the module is spread over a number of files installed in a local gem - but my understanding is that that should not effect the way the code is executed) with an error like this: undefined method `a' for M::M (NoMethodError).
So, is this the correct way to refer to module level variables in context? is there a simpler/more idiomatic way?
If the module is spread out over other files, you need to ensure that your initialization is run before the method is called. If they are in the same file, this should be as much as guaranteed, but if you somehow split them there could be trouble.
I've found you can usually get away with this:
module M
def self.a
#a ||= 1
end
end
If this variable is subject to change, you will need a mutator method. Rails provides mattr_accessor that basically does what you want, part of ActiveSupport.

Ruby Plugin Architecture

I'd like a very basic example of a tiny base program, that reads in two plugins and registers them. These two plugins hook into the base program in the same way in a unconflicting manner.
I'm very new to metaprogramming in any programming language for that matter, I'm not sure where to start.
i've been working on this very problem for a while now. i've tried a number of different ways to go about doing it and sought advice from a lot of people on it. i'm still not sure if what i have is 'the right way', but it works well and is easy to do.
in my case, i'm specifically looking at configuration and bringing in configuration plugins, but the principle is the same even if the terminology for mine is specific to cnfiguration.
at a very basic level, i have a Configuration class with nothing in it - it's empty. I also have a Configure method which returns the configuration class and lets you call methods on it:
# config.rb
class Configuration
end
class MySystem
def self.configure
#config ||= Configuration.new
yield(#config) if block_given?
#config
end
Dir.glob("plugins/**/*.rb").each{|f| require f}
end
MySystem.configure do |config|
config.some_method
config.some_value = "whatever"
config.test = "that thing"
end
puts "some value is: #{MySystem.configure.some_value}"
puts "test #{MySystem.configure.test}"
to get the some_method and some_value on the configuration class, I have the plugins extend the configuration object via modules:
# plugins/myconfig.rb
module MyConfiguration
attr_accessor :some_value
def some_method
puts "do stuff, here"
end
end
class Configuration
include MyConfiguration
end
and
# plugins/another.rb
module AnotherConfiguration
attr_accessor :test
end
class Configuration
include AnotherConfiguration
end
to load up the plugins, you only need one of code to look for the .rb files in a specific folder and 'require' them. this code can live anywhere as long as it's run right away when the file that contains it is loaded... i would probably put it in the class definition for MySystem or something like that to start with. maybe move it somewhere else when makes sense.
Dir.glob("plugins/**/*.rb").each{|f| require f}
run config.rb and you'll get output that looks like this:
do stuff, here
some value is: whatever
test that thing
there are a lot of options for implementing the various parts of this, but this should get you down the path.
It looks like this project may help: https://github.com/eadz/plugman
I haven't however found anything that will handle embedded (gem) dependencies however. Including Ruby files is straightforward but once your plugins start requiring other libraries then this simple model falls apart (either all the deps have to be installed with your application, or you need some other mechanism to get the plugin dependency gems into the process).

Test modules with Test::Unit

I encountered a problem when trying to test a module with Test::Unit. What I used to do is this:
my_module.rb:
class MyModule
def my_func
5 # return some value
end
end
test_my_module.rb:
require 'test/unit'
require 'my_module'
class TestMyModule < Unit::Test::TestCase
include MyModule
def test_my_func
assert_equal(5, my_func) # test the output value given the input params
end
end
Now the problem is, if my_module declares an initialize method, it gets included in the test class and this causes a bunch of problems since Test::Unit seems to override/generate an initialize method. So I'm wondering what is the best way to test a module?
I'm also wondering wether my module should become a class at this point since the initialize method is made for initializing the state of something. Opinions?
Thanks in advance !
Including an initialize method in a module feels very wrong to me, so I'd rethink that at the very least.
To answer your question about testing this as a module more directly, though, I would create a new, empty class, include your module in it, create an instance of that class, and then test against that instance:
class TestClass
include MyModule
end
class TestMyModule < Unit::Test::TestCase
def setup
#instance = TestClass.new
end
def test_my_func
assert_equal(5, #instance.my_func) # test the output value given the input params
end
end
Yeah, your initialize should definitely suggest that you're going towards a class. A module in ruby often feels like an interface in other languages, as long as you implement some basic things when you include the module you'll get a lot for free.
Enumerable is a great example, as long as you define [] and each when you include Enumerable you suddenly get pop, push, etc.
So my gut feeling about testing modules is that you should probably be testing classes that include the module rather than testing the module itself unless the module is designed to not be included in anything, it's simply a code storage mechanism.

Resources