Conditionally defining functions in Ruby - ruby

I have some code that is run in one of a few different locations: as a command line tool with debug output, as part of a larger program that doesn't take any output, and in a rails environment.
There are occasions where I need to make slight changes to the code based on its location, and I realized the following style seems to work:
print "Testing nested functions defined\n"
CLI = true
if CLI
def test_print
print "Command Line Version\n"
end
else
def test_print
print "Release Version\n"
end
end
test_print()
This results in:
Testing nested functions defined
Command Line Version
I've never come across functions that are defined conditionally in Ruby. Is this safe to do?
This isn't how I'm structuring most of my code, but there are a few functions that require complete rewrites per-system.

I don't think that is a clean way.
My suggestion is to define the same sets of methods (with different definition bodies) in different modules, and conditionally include the relevant module into the class/module you are going to call the methods from.
module CLI
def test_print
... # definition for CLI
end
end
module SomeOtherMode
def test_print
... # definition for some other mode
end
end
class Foo
include some_condition ? CLI : SomeOtherMode
end
Foo.new.test_print
If you are only going to use only one mode per run, and think that it is a waste to define the modules that end up not being used, then you can take a further step; define respective modules (CLI, SomeOtherMode, ...) in separate files, and use autoload.
autoload :CLI, "path/to/CLI"
autoload :SomeOtherMode, "path/to/SomeOtherMode"

It's a form of meta-programming and is generally safe. The real risk is not if it will work as expected, but in testing all the variations you create.
The example you've given here makes it impossible to execute the alternate version. To properly exercise both methods you need a way to force the injection of one or the other.

Related

RSpec - how to test if a different class' method was called?

I want to monkey patch a String#select method, and want to create a test suite that checks that it doesn't use Array#select.
I tried creating a bunch of tests using to_not receive, using both to_not receive(Array:select) and just to_not receive(:select). I also tried using an array (string.chars) instead of the string . Google and stack overflow did not bring an answer.
describe "String#select" do
it "should not use built-in Array#select" do
string = "HELLOworld".chars
expect(string).to_not receive(Array:select)
end
end
Expected: a working test suite that checks that Array#method has not been used in the whole method.
Actual output: I'm getting an error that not enough arguments have been used. Output log below:
1) RECAP EXERCISE 3 Proc Problems: String#select should not use built-in Array#select
Failure/Error: expect(string).to_not receive(Array:select)
ArgumentError:
wrong number of arguments (given 0, expected 1..4)
# ./spec/problems_spec.rb:166:in `select'
# ./spec/problems_spec.rb:166:in `block (4 levels) in <top (required)>'
First of all: tests are supposed to check the results of methods called, not the way they are implemented. Relying to much on this would get you in trouble.
But there might be a legit reasons to do it, but think hard of you can test it other way:
Let's say that String#select uses Array#select internally, and the latter is buggy under some circumstances. It's better to make a test, setting up the universe in a way that would trigger the bug and check that the buggy behavior is not present . Then patch the String#select and have the test green. It's much better approach, because the test now tells everyone why you're not supposed to use Array#select internally. And if the bug is removed, it's the easiest thing under the sun to remove patch and check if the spec is still green.
That being said, if you still need that you can use expect_any_instance_of to acomplish that, for example this spec would fail:
class String
def select(&block)
split.select(&block) # remove this to make the spec pass
end
end
specify do
expect_any_instance_of(Array).not_to receive(:select)
'foo'.select
end
If you don't want to use expect_any_instance_of (because reasons), you can temporarily overwrite a method in a class to fail:
class String
def select(&block)
#split.select(&block)
end
end
before do
class Array
alias :backup_select :select
def select(*)
raise 'No'
end
end
end
after do
class Array
alias :select :backup_select # bring the original implementation back
end
end
specify do
expect { 'foo'.select }.not_to raise_error
end
Aliasing is needed to bring back the original implementation, so you don't mess up the specs that are run after this one.
But you can see how involved and messy this approach is.
Anyway - what you're trying to achieve is most probably a design issue, but it's hard to tell without more details.

How to test directory reading and addressing method in ruby?

I have a ruby class to read directory and do something for each file. But I am not sure how to test it.
class Scanner
def scan(dir)
Dir.glob(File.join(dir, '**', '*')).each do |path|
verify(path)
end
end
private
def verify(path)
do something for the file...
end
end
I want to have a unit-testing for scan method.
Should I mock something?
Should I provide a test directory in spec/fixtures?
What's the expectation?
Cheers
There are few ways to approach that. One (proposed in the comments) is to separate the concerns, e.g.
class Scanner
def scan(dir)
Dir.glob(File.join(dir, '**', '*')).each do |path|
verify(path)
end
end
private
def verify(path)
Veryfier.new.verify(File.read(path))
# you can test veryfier easily by passing different strings into `verify` method
end
end
And go on with your life, as Scanner#scan is not that complicated anymore, and one would not expect many bugs in there.
But, if for whatever reason, you want it tested as well, you can do one of the following:
set up a dir structure under test/features and tread different directories like different cases. This has one drawback: you have to inspect directories plus read the tests to understand the tests. But it's simple to do.
use Dir.tmpdir and File.tmpfile to set up the dir structure inside your tests. This is a bit more involved, but on the plus side you have the structure and the tests for behavior of the Scanner in a single test file.

How do I change this case statement to an if statement?

I would like to check for the value of a node attribute. This case statement is what I have so far, and it works:
case node[:languages][:ruby][:host_cpu]
when "x86_64"
...
when "i686"
...
end
What I would like to do is use an if statement instead. This is what I tried:
if node[:languages][:ruby][:host_cpu]?("X86_64")
...
end
This is based on the following, Which worked.
if platform?("ubuntu")
...
end
However, my try didn't work. it gave a syntax error on the if line saying that there was an unexpected \n and $end was expected.
I found that there are two kinds of ways of performing an if. The first being the one I demonstrated above, which (apparently) only works with resources, and if_only, which works with nodes. like so
if_only {node[:languages]}
which seems to work only for checking the presence of nodes, and within a do context.
How do I check the value of a node using an if statement? One method does check values, but only of resources, the other checks nodes, but only for their presence, and not their values.
You are mixing up way to many different variants for conditionals, most of which are part of Chef, not Ruby. Let me try to describe the different options one by one.
Generally, a case is roughly comparable to a series of if and elsif statements. Your case above
case node[:languages][:ruby][:host_cpu]
when "x86_64"
...
when "i686"
...
end
is thus roughly equivalent to
if node[:languages][:ruby][:host_cpu] == "x86_64"
...
elsif node[:languages][:ruby][:host_cpu] == "i686"
...
end
As a side remark, case actually uses the === operator which is often not commutative but more powerful. For simple comparisons it works the same as == though. Both these variants are part of the Ruby language, in which you write your cookbooks.
The other options you mentioned are actually part of the API which Chef defined on top of Ruby. This is often called the Chef DSL (which stands for Domain Specific Language, i.e. an extension or adaption of a language, in this case Ruby for a specific usage domain, in this case configuration management.)
The platform? method is a method defined by Chef that checks whether the curent platform is one of the passed values. You can read more about that (and similar methods, e.g. the now recommended platform_family? method at the Chef docs for recipes in general and some often used ruby idioms.
As a side-remark: you might be surprised by the fact that Ruby allows the ? and ! characters to appear at the end of method names, which makes Ruby rather unique among similar languages in this regard. These characters are simply part of the method name and have no special meaning to the language. They are only used by convention to programmers to better identify the purpose of a method. If a method has a ? at the end, it is to be used to check some condition and is expected to return either a truthy or falsy value. Methods with a ! at the end often perform some potentially dangerous operation, e.g. change object in place, delete stuff, ... Again, this is only a convention and is not interpreted by the language.
The last option you mentioned, the only_if and by extension not_if are used to define conditionals on Chef resources to make sure they are only executed when a certain condition is true (or when using not_if, if it is false). As these attributes are used on Chef resources only, they are naturally also defined by Chef.
To understand why they are useful it is necessary to understand how a Chef run works. The details can be found at the description of the Anatomy of a Chef Run. What is important there is, that you basically have two execution phases: Resource Compilation and Convergence. In the first step, the actual code to define the resources is executed. Here, also the code in your case statement would be run. After all the recipes have been loaded and all the resources have been defined, Chef enters the second phase, the Convergence phase. There, the actual implementation of the resources which performs the changes (create files and directories, in stall packages, ...) is run. Only in this phase, the only_if and not_if conditions are checked.
In fact, you can observe the difference between
file "/tmp/helloworld"
action :create
content "hello world"
end
if File.exist?("/tmp/helloworld")
file "/tmp/foobar"
action :create
content "foobar"
end
end
and
file "/tmp/helloworld"
action :create
content "hello world"
end
file "/tmp/foobar"
action :create
content "foobar"
only_if{ File.exist?("/tmp/helloworld") }
end
In the first variant, the condition whether /tmp/foobar exists is checked during resource compilation. At this time, the code to actually create the /tmp/helloworld file has not been run, as it does that only in the Conversion step. Thus, during your first run, the /tmp/foobar file would not be created.
In the second variant however, the check is done with only_if which is evaluated during conversion. Here you will notice that both files get created in the first run.
If you want to read a bit more on how the definition of the conditionals works in terms of Ruby (and you definitely should), you can read about Ruby Blocks which are more or less pieces of code that can be passed around for later execution.

Is there an acceptable way of putting multiple module declarations on the same line?

I am working on a code base that has many modules nested 4 or 5 deep. Right now, this results in our code being heavily indented from the beginning.
Is there an acceptable way of putting multiple module declarations on the same line?
For example,
module A
module B
#do stuff
end
end
Is there a way to make it something like this?
module A::B
#do stuff
end
Though the previous block doesn't work, I was able to get this next one to work, however I am not sure if this is considered acceptable code construction.
module A module B
#do stuff
end end
You can use ; instead of \n safely in Ruby source files. Newlines before end are not important.
module A ; module B
#do stuff
end end
Or for example:
def sqr x ; x*x end
etc.
I think you've answered it yourself - your third segment looks pretty bad to my eye.
But, more to the point, if you did write module A::B and module A had never been defined before, you would be implicitly defining an (empty) module A, which doesn't seem very useful. And, once you've defined module A once, you're welcome to write module A::B to define module B. So it seems actively good to me that you can't use your second example.
Do this, though it's a bit naughty:
class Module
def const_missing(m)
const_set(m, Module.new)
end
end
module A::B
def self.hello_my_friend
:hello_my_friend
end
end
A::B.hello_my_friend #=> :hello_my_friend
Ruby doesn't use significant whitespace in the same way as Python does (though it can provide you with warnings if the indentation seems wonky), so you can simply not indent the code if you don't want to do so. It'd just make the code harder to read.
Incidentally, you may want to check whether four or five levels of modules indicates a code smell.

is it OK to use begin/end in Ruby the way I would use #region in C#?

I've recently moved from C# to Ruby, and I find myself missing the ability to make collapsible, labelled regions of code. It just occurred to me that it ought to be OK to do this sort of thing:
class Example
begin # a group of methods
def method1
..
end
def method2
..
end
end
def method3
..
end
end
...but is it actually OK to do that? Do method1 and method2 end up being the same kind of thing as method3? Or is there some Ruby idiom for doing this that I haven't seen yet?
As others have said this doesn't change the method definition.
However, if you want to label groups of methods, why not use Ruby semantics to label them? You could use modules to split your code into chunks that belong together. This is good practice for large classes, even if you don't reuse the modules. When the code grows, it will be easy to split the modules into separate files for better organisation.
class Example
module ExampleGroup
def method1
# ..
end
def method2
# ..
end
end
include ExampleGroup
def method3
# ..
end
end
Adding arbitrary Ruby code in order to please your editor doesn't seem reasonable to me at all. I think you should try to get a decent text editor, one that would be able to configure to use some keyword you put in your comments to enable code folding there, for example:
# region start AAA
some(:code, :here)
# region end AAA
And if you allow me to be sarcastic: you should know that Ruby is lot more expressive, elegant and pretty than C#, and chances are that there isn't any Ruby code ought to be hidden in the first place! ;)
I know this might be a bit old but I ran into this issue and found a killer solution. If you're using "Rubymine" you can do #regions.
This is where they talk about navigating regions
https://www.jetbrains.com/ruby/help/navigating-to-custom-region.html
#region Description
Your code goes here...
#endregion
So with the above in your code you will see the below:
Rubymine code editor - With a sample method using regions un-folded
Rubymine code editor - With a sample method using regions folded
I think it should be OK. I haven't seen it a lot though. Another way you could accomplish the same thing, while using less indentation, is to close and reopen the class, like so:
class Example
def method1
..
end
def method2
..
end
end
# optionally put some other code here
class Example
def method3
..
end
end
I think this is often used so that you can put other code in the middle, such as when there's a constant defined in the first class Example block, which is used to define a constant in the middle code, which is used to define a constant in the second class Example block. That way you never have to refer to a constant before it's defined.
Yes, it is okay to do that (i.e. it doesn't change the semantics of your code).
However this isn't a common pattern and it might be confusing to readers of your code who wouldn't understand what the purpose of the begin ... end block is.

Resources