Ruby: Default values for define? - ruby-on-rails

I have a question about define my main issue is I am a bit confused on how the parameters work for it.
This is my Methods
def repeat(repeated_word)
#repeated_word = repeated_word
"##repeated_word ##repeated_word"
end
This is my rspec test to make sure my method works.
describe "repeat" do
it "should repeat" do
repeat("hello").should == "hello hello"
end
# Wait a second! How can you make the "repeat" method
# take one *or* two arguments?
#
# Hint: *default values*
it "should repeat a number of times" do
repeat("hello", 3).should == "hello hello hello"
end
end
it passes the first test but fails the second. My confusion is if i add a second parameter meaning def repeat(repeat_word, times_repeated)
the first test then fails because it has the wrong number of arguments. Not sure how to set up default values?

def repeat(repeated_word, repeats=2)
repeats.times.map { repeated_word }.join(' ')
end

Related

How to use CustomHealthCheck with health_check gem in ruby?

From the health_check official site, we know that it can add a config.add_custom_check block in the config file:
https://github.com/ianheggie/health_check
# Add one or more custom checks that return a blank string if ok, or an error message if there is an error
config.add_custom_check do
CustomHealthCheck.perform_check # any code that returns blank on success and non blank string upon failure
end
# Add another custom check with a name, so you can call just specific custom checks. This can also be run using
# the standard 'custom' check.
# You can define multiple tests under the same name - they will be run one after the other.
config.add_custom_check('sometest') do
CustomHealthCheck.perform_another_check # any code that returns blank on success and non blank string upon failure
end
But about the CustomHealthCheck class, how to define it?
For okcomputer gem, it offers a way like this:
https://github.com/sportngin/okcomputer
# config/initializers/okcomputer.rb
class MyCustomCheck < OkComputer::Check
def check
if rand(10).even?
mark_message "Even is great!"
else
mark_failure
mark_message "We don't like odd numbers"
end
end
end
OkComputer::Registry.register "check_for_odds", MyCustomCheck.new
Didn't find the usage about health_check gem.
Update
I have tried:
Add these source in the config/initializers/health_check.rb file:
class CustomHealthCheck
def perform_check
if rand(10).even?
p "Even is great!"
else
p "We don't like odd numbers"
end
end
end
HealthCheck.setup do |config|
...
Run curl -v localhost:3000/health_check.json, got:
{"healthy":false,"message":"health_check failed: undefined method `perform_check' for CustomHealthCheck:Class"}%
Update 2
Edited source in config/initializers/health_check.rb:
class CustomHealthCheck
def self.perform_check
p 'OK'
end
end
HealthCheck.setup do |config|
...
Got:
{"healthy":false,"message":"health_check failed: OK"}%
Success is defined by returning an empty or blank string. Right now your perform_check always returns the string "OK" which will be seen as failure.
Try this to get a passing health check:
class CustomHealthCheck
def self.perform_check
everything_is_good = true # or call some method to do more elaborate checking
return everything_is_good ? "" : "We've got Problems"
end
end

Stubbing key/value pair in ruby on rails ENV

I want to test the effect of the value of an ENV key on my code. I am stubbing this by using
allow(ENV).to receive(:[]).with('ADWORDS_RUN').and_return('No')
This was working until I changed the target code to include accessing another ENV key. The target code now includes the following
def not_local_machine?
!ENV['LOCAL_MACHINE']
end
The test now fails in the above function with the error message
Failure/Error: get 'home'
ENV received :[] with unexpected arguments
expected: ("ADWORDS_RUN")
got: ("LOCAL_MACHINE")
Please stub a default value first if message might be received with other args as well.
It appears that my current method of stubbing is wiping out other ENV keys. How do I stub an ENV key to avoid this problem?
You can use
stub_const 'ENV', ENV.to_h.merge('ADWORDS_RUN' => 'No')
This is how I solved that issue:
before { allow(ENV).to receive(:[]).and_call_original }
context 'ADWORDS_RUN is No' do
before { allow(ENV).to receive(:[]).with('ADWORDS_RUN').and_return('No') }
[example block]
end
(Aside, I recommend using something like 'false' instead of 'No'.)
For modifying ENV's in tests, Thoughtbot's climate_control gem is worth a look.
You wrap your test around ClimateControl block to control temporary changes to ENV values. Using your example:
ClimateControl.modify ADWORDS_RUN: 'No' do
expect(AdwordsTask.new.run?).to eq(false)
end
To use with RSpec, you could define this in your spec:
def with_modified_env(options, &block)
ClimateControl.modify(options, &block)
end
This would allow for more straightforward way to modify/stub environment values:
require 'spec_helper'
describe AdwordsTask, 'name' do
it 'does not run adwords' do
with_modified_env ADWORDS_RUN: 'No' do
expect(AdwordsTask.new.run?).to eq(false)
end
end
def with_modified_env(options, &block)
ClimateControl.modify(options, &block)
end
end
You are overriding/overwriting the [] method of ENV. The original meaning is gone completely.
Check out https://github.com/rspec/rspec-mocks and look for the chapter "Arbitrary Handling". It contains this sample code:
expect(double).to receive(:msg) do |arg|
expect(arg.size).to eq 7
end
You should be able to adopt that for your needs... something along the lines of (untested)
dummy_env = { ADWORDS_RUN: 1, LOCAL_MACHINE: 2 }
allow(ENV).to receive(:[]) do |key|
dummy_env[key] or raise "#{key} not expected"
end
Or if you want to keep all old ENV entries
env_clone = ENV.clone
allow... do|key|
dummy_env[key] or env_clone[key]
end

Readable test names with minitest

I'm using MiniTest on a new Rails project and this is my first time really doing testing. When a test fails the message looks like this
1) Failure:
Category::when created#test_0002_must have a unique name [/home/caleb/workspace/buzz/test/models/category_test.rb:10]:
Expected: true
Actual: false
Can you change #test_0002_ to another string to make the error more readable? I know it's a minor issue, but this seems like something that should be supported.
# Example test
require 'test_helper'
describe Category do
describe 'when created' do
unique = false
it 'must not have a unique name' do
unique.must_equal false
end
it 'must have a unique name' do
unique.must_equal true
end
end
end
Well, there is a lot here to cover, so bear with me.
First, the test names are readable. And they are 100% accurate. When you use the spec DSL you are still creating test classes and test methods. In your case, you class is Category::when created and your test method is test_0002_must have a unique name. The # in between them is a very common Ruby idiom for an instance method on a class, which is what your test method is. When you use class or def you can't create classes or methods with spaces in them, but when you create them programmatically you can. When running your code Ruby doesn't care if there are spaces in them or not.
Second, we can affect the display of test class and method. That text comes from a call to Minitest::Test#to_s. Here is what that looks like:
def to_s # :nodoc:
return location if passed? and not skipped?
failures.map { |failure|
"#{failure.result_label}:\n#{self.location}:\n#{failure.message}\n"
}.join "\n"
end
When the test fails then more info is returned, including the reason for the failure. But the piece we care about is the location. Here is what that looks like:
def location
loc = " [#{self.failure.location}]" unless passed? or error?
"#{self.class}##{self.name}#{loc}"
end
Ah, better. On the last line you can clearly see it is printing the class and the method name. If the test is failing the location also includes the filename where the method is defined. Let's break those values out so they aren't inline:
def location
loc = " [#{self.failure.location}]" unless passed? or error?
test_class = self.class
test_name = self.name
"#{test_class}##{test_name}#{loc}"
end
Okay, a bit clearer. First the test class, then the #, then the test name, then the location if the test is not passing. Now that we have them broken out we can modify them a bit. Let's use / to separate the class namespaces and the test method:
def location
loc = " [#{self.failure.location}]" unless passed? or error?
test_class = self.class.to_s.gsub "::", " / "
test_name = self.name
"#{test_class} / #{test_name}#{loc}"
end
Great. Now let's remove the test_0002_ from the beginning of the test method. That is added by the spec DSL, and by removing it we can make it match the string passed to the it block:
def location
loc = " [#{self.failure.location}]" unless passed? or error?
test_class = self.class.to_s.gsub "::", " / "
test_name = self.name.to_s.gsub /\Atest_\d{4,}_/, ""
"#{test_class} / #{test_name}#{loc}"
end
Now, your test output will look like this:
1) Failure:
Category / when created / must have a unique name [/home/caleb/workspace/buzz/test/models/category_test.rb:10]:
Expected: true
Actual: false
Minitest is no different than any other Ruby library. The spec DSL is simply a thin wrapper for creating test classes and methods. You can alter the behavior of your test objects to work the way you want them to.
TL;DR Add the following to your test/test_helper.rb file:
class Minitest::Test
def location
loc = " [#{self.failure.location}]" unless passed? or error?
test_class = self.class.to_s.gsub "::", " / "
test_name = self.name.to_s.gsub /\Atest_\d{4,}_/, ""
"#{test_class} / #{test_name}#{loc}"
end
end

blocks in silly blocks rspec testing

I had the following tests given to me as an exercise:
require "silly_blocks"
describe "some silly block functions" do
describe "reverser" do
it "reverses the string returned by the default block" do
result = reverser do
"hello"
end
result.should == "olleh"
end
it "reverses each word in the string returned by the default block" do
result = reverser do
"hello dolly"
end
result.should == "olleh yllod"
end
end
describe "adder" do
it "adds one to the value returned by the default block" do
adder do
5
end.should == 6
end
it "adds 3 to the value returned by the default block" do
adder(3) do
5
end.should == 8
end
end
describe "repeater" do
it "executes the default block" do
block_was_executed = false
repeater do
block_was_executed = true
end
block_was_executed.should == true
end
it "executes the default block 3 times" do
n = 0
repeater(3) do
n += 1
end
n.should == 3
end
it "executes the default block 10 times" do
n = 0
repeater(10) do
n += 1
end
n.should == 10
end
end
end
I was able to solve them with the following code:
def reverser
k = []
x = yield.split(" ")
x.each do |y|
n = y.reverse
k.push(n)
end
m = k.join(" ")
m
end
def adder(num=1, &block)
block.call + num
end
def repeater(num=1, &block)
for i in (1..num) do
block.call
end
end
However I some of these concepts I do not understand all that well. For example:
What exactly does the & symbol in the &block parameter mean?
Similarly what is block.call and where is the actual block object I am assuming its calling?
Could I theoretically use another method on block if I wanted to achieve something else?
Also where can I learn a bit more about blocks
This exercise was a bit above my current knowledge.
It means "this is the block parameter". You are not bound to calling it &block, so there needs to be a way to separate it from the other arguments. The same notation is used to pass arguments to a function as block as opposed to normal arguments (see below)
block.call is exactly the same thing as yield. The difference is that you can use block to access the block itself without calling it immediately. For example, you could store the block for later execution. This is a common pattern known as lazy evaluation.
Yes, you can also pass different things than a do/end block as the &block parameter. See below for some examples.
#UriAgassi gave you an excellent link.
Here are some other things you can pass as block argument. First, just a simple method that takes a block for demonstration:
def reverser(&block)
block.call.reverse
end
You can now pass a standard block
reverser do
"hello"
end
#=> "olleh"
Or, in alternative block syntax, used for inline style
reverser { "hello" }
#=> olleh
You can also pass a lambda or proc, which is similar to a block.
By using the &block notation you can pass a variable as block argument:
my_block = lambda { "hello world!" }
reverser(&my_block)
#=> "!dlrow olleh"
Or, in alternative lambda syntax
my_block = -> { "hello world!" }
reverser(&my_block)
#=> "!dlrow olleh"
You can even take an existing method and pass it as block argument
here you can see the great advantage of blocks: They are evaluated
when block.call is executed, not when the code is loaded. Here this
means that the string will change every time accordingly.
def foobar
"foobar at #{Time.now}"
end
reverser(&method(:foobar))
#=> "0020+ 15:42:90 02-50-4102 ta raboof"
#=> "0020+ 31:52:90 02-50-4102 ta raboof"
You can do cool stuff with this, for example:
[1, 2, 3].each(&method(:puts))
1
2
3
#=> [1, 2, 3]
But remember not to overdo it, Ruby is all about expressive and readable code. Use these techniques when they enhance your code, but use simpler ways if possible.
Finally, here is also an example of lazy evaluation:
class LazyReverser
def initialize(&block)
#block = block
end
def reverse
#block.call.reverse
end
end
reverser = LazyReverser.new do
# some very expensive computation going on here,
# maybe we do not even need it, so lets use the
# lazy reverser!
"hello dolly"
end
# now go and do some other stuff
# it is not until later in the program, that we can decide
# whether or not we even need to call the block at all
if some_condition
reverser.reverse
#=> "yllod olleh"
else
# we did not need the result, so we saved ourselves
# the expensive computation in the block altogether!
end

Ruby on Rails RSpec Compare Function Values

I have two function values that I'm trying to compare and make sure one is greater than the other, and I just cannot figure out how to do it in RSpec. One function is "uncompleted_tasks" and the other is "tasks.count", both of which are part of a User model. Here is what I have in RSpec. The subject is an instance of the User model and RSpec gives me the error, "undefined local variable or method 'ut' for # (NameError)", on the line "expect(ut).should be <= tc". What's going on?
describe "uncompleted tasks should be less than or equal to total task count" do
before do
ut = subject.uncompleted_tasks
tc = subject.tasks.count
end
expect(ut).should be <= tc
end
Check out this SO answer for further details, but basically local variables in RSpec are limited to their local scope, including before blocks. So, the variables defined in your before block aren't available in the test. I'd suggest using instance variables for this:
describe "uncompleted tasks" do
before do
#ut = subject.uncompleted_task
#tc = subject.tasks.count
end
it "should be less than or equal to total task count" do
expect(#ut).should be <= #tc
end
end
You need to use instance variables and your expect needs to be inside an it block. Like below:
describe "uncompleted tasks should be less than or equal to total task count" do
before do
#ut = subject.uncompleted_tasks
#tc = subject.tasks.count
end
it "something" do
expect(#ut).should be <= #tc
end
end

Resources