Can Rspec use instance variables in looping construct? - ruby

I'm trying to use Rspec to loop through a series of very similar tests, but it's not working out for me right now. I have a hash of states and I want to test all of them, but I don't know the order they'll be in, so I've set it up like this:
before(:all) do
#hash = {
state_1 => {'item1' => 'a', 'item2' => 'b', 'item3' => 'c'},
state_2 => {'item1' => 'd', 'item2' => 'e', 'item3' => 'f'},
state_3 => {'item1' => 'g', 'item2' => 'h', 'item3' => 'i'}
}
end
until #hash.empty? do
context 'code does some stuff' do
before(:all) do
#state = <result of state determining function>
#item1 = #hash[#state]['item1']
#item2 = #hash[#state]['item2']
#item3 = #hash[#state]['item3']
end
it 'does some stuff' do
...
end
after(:all) do
#hash.delete(#state)
end
...
end
When I run my rspec tests, however, I get an error that there is no method 'empty?' for nil:NilClass. So questions are, what am I doing wrong, and what is the preferred way to do something of this sort?
Thanks!

it looks like you're looking to use let, which recreates the variable for each context

Related

How do I match deep into a hash with Rspec and get reasonable matching errors?

I'm in a situation where I'm writing Rspec expectations that look like this:
expect(result['data']['register']['registered']).to be(false), result.inspect
The problem is that most often, the error I get when something goes wrong is:
undefined method `[]' for nil:NilClass
I found this better way of writing it:
expect(result.dig('data', 'register', 'registered')).to be(false), result.inspect
which at least shows the result of result.inspect, but I imagine there would be something better, such as:
expect(result).to dig('data', 'register', 'registered').and be(false), result.inspect
I thought about using hash_including but I couldn't make it work. I tried:
expect(result).to eq(hash_including("data" => {"register" => {'registered' => be(false)}}))
or
expect(result).to eq(hash_including("data" => {"register" => {'registered' => false}}))
or
expect(result).to eq(hash_including("data" => hash_including("register" => hash_including('registered' => be(false)))))
or
expect(result).to eq(hash_including("data" => hash_including("register" => hash_including('registered' => false))))
or includes by doing:
expect(result).to include('data' => {'register' => {'registered' => false}})
or
expect(result['data']).to include('register' => {'registered' => false})
None of which work and the second is horribly verbose... even the first one is not as good as dig in my opinion, but I digress.
Have you looked at hash_including?
a = {a: 2}
c = {b: 4}
expect(a).to eq(hash_including(c))
Here's the output:
expected: hash_including(:b=>4)
got: {:a=>2}

Rubocop Offense: Use the return of the conditional for variable assignment and comparison

My Rubocop offense is telling me I need to 'Use the return of the conditional for variable assignment and comparison'
While I tried fixing it, it gave me another offense that my 'method line is too long'.
I've tried refactoring to another method but my code broke.
How do I shorten or refactor this code ?
HSH = { 'a' => 'z', 'b' => 'y', 'c' => 'x', 'd' => 'w', 'e' => 'v', \
'f' => 'u', 'g' => 't', 'h' => 's', \
'i' => 'r', 'j' => 'q', 'k' => 'p', 'l' => 'o', 'm' => 'n' }.freeze
def encoder(str)
encoded_string = ''
str.chars.each do |char|
encoded_string = if HSH.key?(char) then encoded_string += HSH[char]
elsif HSH.invert.key?(char) then encoded_string += HSH.invert[char]
else encoded_string += char
end
end
encoded_string
end
When I ran my test suite, everything was ok.
But the rubocop offense gave me method line is too long.
No hash:
ALPHABET = ("a".."z").to_a.join
def encoder(str)
str.tr(ALPHABET, ALPHABET.reverse)
end
HSH = {
'a' => 'z', 'b' => 'y', 'c' => 'x',
'd' => 'w', 'e' => 'v', 'f' => 'u',
'g' => 't', 'h' => 's', 'i' => 'r',
'j' => 'q', 'k' => 'p', 'l' => 'o',
'm' => 'n'
}.freeze
def encoder(str)
str.chars.map { |char| HSH[char] || HSH.invert[char] || char }.join
end
NB! Do not use this answer! the proper way to go is provided by #steenslag here.
Use the hash with all the letters explicitly mapped and the default proc:
HSH =
(?a..?z).zip((?a..?z).to_a.reverse).to_h.
tap { |h| h.default_proc = ->(_, k) { k }}.
freeze
def encoder(str)
str.chars.map(&HSH.method(:[])).join
end
Several of us suggested better ways to implement your encoder method. But all of us (myself included) didn't actually answer your question, or see a central problem in your code:
My Rubocop offense is telling me I need to 'Use the return of the conditional for variable assignment and comparison'
While I tried fixing it, it gave me another offense that my 'method line is too long'.
encoded_string = if HSH.key?(char) then encoded_string += HSH[char]
elsif HSH.invert.key?(char) then encoded_string += HSH.invert[char]
else encoded_string += char
end
You sort of followed Rubocop's advice...you assigned a conditional result to a value...but I think you missed the mark. I'm not even sure that's the conditional result it was referring to. I'm assuming you added the encoded_string = ... assignment.
That is a uselesss assignment, as you already appended the character to encoded_string within the if blocks. You don't have to assign it again.
Rewinding to what I'm guessing was your version 1 code, here's a more efficient way to follow Rubocop's advice. Don't make an assignment within each condition...only do one assignment, with the result of the conditionals:
encoded_string += if HSH.key?(char) then HSH[char]
elsif HSH.invert.key?(char) then HSH.invert[char]
else char
end
That ends up with less code, and matches your original coding style and approach. It might even make Rubocop happy. The next step to lovely code would be to eliminate the excessive key? tests:
encoded_string += if HSH[char] then HSH[char]
elsif HSH.invert[char] then HSH.invert[char]
else char
end
From there, it's a small step to eliminate the if/elsif blocks with ||'s. And while we're at it, we'll change += to << to avoid, "produce a gazillion of intermediate unnecessary String instances." (Thanks for the suggestion, #Aleksei Matiushkin )
encoded_string << HSH[char] || HSH.invert[char] || char
For your approach to this problem, that's about the minimal level of conciseness and readability that production code should strive for. Anyone can understand it without thinking real hard or hitting Stack Overflow.
As with #Steenslag's answer, there's no need to convert the string to an array, map each element of the array and join the result back into a string. The following is defined to be efficient, by avoiding the need for a linear search for each letter.
def encode_decode(str)
rng = 'a'..'z'
str.gsub(/./) { |c| rng.cover?(c) ? (219-c.ord).chr : c }
end
plain_text = "The launch code is 'Bal3De8Rd0asH'."
#=> "Tsv ozfmxs xlwv rh 'Bzo3Dv8Rw0zhH'."
coded_text = encode_decode(plain_text)
#=> "Tsv ozfmxs xlwv rh 'Bzo3Dv8Rw0zhH'."
encode_decode(coded_text)
#=> "The launch code is 'Bal3De8Rd0asH'."
I would go ahead and expand your hash to all 26 letters, so you can avoid the inverse lookup. That simplifies your code by removing one case, which will may appease Rubocop... But more importantly, you'll be using the hash index for greater efficiency and performance. Inverse hash lookups are expensive, as it must read (up to) every value.
Consider encoding "1+2”. It will do three quick index scans, then three full array scans, just to return the original string.
With a fully populated hash, it would only take three quick scans.
Here is your original code with minimal changes to meet your goal: (There are shorter ways to do this (hint: tr or map), but shorter is not as important as easy and comfortable to the programmer using the code.)
translation = {
'a' => 'z', 'b' => 'y', 'c' => 'x', 'd' => 'w', 'e' => 'v', 'f' => 'u', 'g' => 't',
'h' => 's', 'i' => 'r', 'j' => 'q', 'k' => 'p', 'l' => 'o', 'm' => 'n', 'n' => 'm',
'o' => 'l', 'p' => 'k', 'q' => 'j', 'r' => 'i', 's' => 'h', 't' => 'g', 'u' => 'f',
'v' => 'e', 'w' => 'd', 'x' => 'c', 'y' => 'b', 'z' => 'a'
}.freeze
def encoder(str)
encoded_string = ''
str.chars.each do |char|
encoded_string << translation[char] || char
end
encoded_string
end
You might even consider expanding the hash to upper and lower case letters, or even all 256 char values, depending on what problem you're solving. But let's agree to ignore Unicode chars!
Back to Rubocop... The easiest, sure-fire solution to any sort of "too long/too complex" warning is to pull code out to a new method. Write def charswap and use that as the body of your loop. That will make writing tests easier, to boot. But, by expanding your translation array to all 26 letters, the code gets so simple that refactoring isn't really needed.

How do you define element uniqueness by multiple keys/attributes?

I have queried my database which gave me an array of hashes, where the keys in the hash are the column names. I want to keep only the hashes(array elements), that are unique according to multiple (3 columns). I have tried:
array.uniq { |item| item[:col1], item[:col2], item[:col3] }
as well as
array = array.inject([{}]) do |res, item|
if !res.any? { |h| h[:col1] == item[:col1] &&
h[:col2] == item[:col2] &&
h[:col3] == item[:col3] }
res << item
end
end
Does anyone have any ideas as to what's wrong or another way of going about this?
Thanks
It's unclear to me what you're asking for. My best guess is that given the array of single-association Hashes:
array = [{:col1 => 'aaa'}, {:col2 => 'bbb'}, {:col3 => 'aaa'}]
You'd like to have only one Hash per hash value; that is, remove the last Hash because both it and the first one have 'aaa' as their value. If so, then this:
array.uniq{|item| item.values.first}
# => [{:col1=>"aaa"}, {:col2=>"bbb"}]
Does what you want.
The other possibility I'm imagining is that given an array like this:
array2 = [{:col1 => 'a', :col2 => 'b', :col3 => 'c', :col4 => 'x'},
{:col1 => 'd', :col2 => 'b', :col3 => 'c', :col4 => 'y'},
{:col1 => 'a', :col2 => 'b', :col3 => 'c', :col4 => 'z'}]
You'd like to exclude the last Hash for having the same values for :col1, :col2, and :col3 as the first Hash. If so, then this:
array2.uniq{|item| [item[:col1], item[:col2], item[:col3]]}
# => [{:col1=>"a", :col2=>"b", :col3=>"c", :col4=>"x"},
# {:col1=>"d", :col2=>"b", :col3=>"c", :col4=>"y"}]
Does what you want.
If neither of those guesses are really want you're looking for, you'll need to clarify what you're asking for, preferably including some sample input and desired output.
I'll also point out that it's quite possible that you can accomplish what you want at the database query level, depending on many factors not presented.
If the no. of column is constant i.e. 3 you are better off creating a 3 level hash something like below
where whatever value you want to store is at 3rd level.
out_hash = Hash.new
array.each do |value|
if value[:col1].nil?
out_hash[value[:col1]] = Hash.new
out_hash[value[:col1]][value[:col2]] = Hash.new
out_hash[value[:col1]][value[:col2]][value[:col3]] = value
else if value[:col1][:col2].nil?
out_hash[value[:col1]][value[:col2]] = Hash.new
out_hash[value[:col1]][value[:col2]][value[:col3]] = value
else if value[:col1][:col2][:col3].nil?
out_hash[value[:col1]][value[:col2]][value[:col3]] = value
end
end
I have not tested the code above its for giving you a idea...

Local Variables at Intermediate Steps

Hi I was wondering if someone could explain to me why the map function written in the below code is written in the way its written. Specifically why do we need to do
results = letters.map do |letter| encrypted_letter = encrypt_letter(letter)
instead of just doing
results = letters.map do |letter| encrypt_letter(letter)
class Encryptor
def cipher
{"a" => "n", "b" => "o", 'c' => 'p', 'd' => 'q',
'e' => 'r', 'f' => 's', 'g' => 't', 'h' => 'u',
'i' => 'v', 'j' => 'w', 'k' => 'x', 'l' => 'y',
'm' => 'z', 'n' => 'a', 'o' => 'b', 'p' => 'c',
'q' => 'd', 'r' => 'e', 's' => 'f', 't' => 'g',
'u' => 'h', 'v' => 'i', 'w' => 'j', 'x' => 'k',
'y' => 'l', 'z' => 'm'}
end
def encrypt_letter(letter)
lowercase_letter = letter.downcase
cipher[lowercase_letter]
end
def encrypt(string)
letters = string.split("")
results = letters.map do |letter|
encrypted_letter = encrypt_letter(letter)
end
results.join
end
def decrypt_letter(letter)
lowercase_letter = letter.downcase
cipher.key(lowercase_letter)
end
def decrypt(string)
letters = string.split("")
results = letters.map do |letter|
decrypted_letter = decrypt_letter(letter)
end
results.join
end
end
No reason; the variable is immediately discarded.
I'd argue it's misleading and uncommunicative on top of it.
Most of the code seems a bit verbose, for example:
def encrypt(string)
letters = string.split("")
results = letters.map do |letter|
encrypted_letter = encrypt_letter(letter)
end
results.join
end
IMO this would be more Ruby-esque as something closer to:
def encrypt(str)
str.chars.collect { |c| encrypt(c) }.join
end
It could be tighter than that, or written in other ways, although some of it is a matter of preference. For example, each_with_object could be used with the shovel operator, but that's less "functional".
(I prefer collect over map when collecting; a preference I find more communicative, if longer.)
Spreading functionality over more lines doesn't make things readable, but it depends on context. People new to Ruby or method chaining might be confused by the (IMO more canonical) one-liner.
As others say, it has no reason. It is obviously a code written by a beginner. In addition to Dave Newton's point, it is a bad habit to define a constant hash as a method cipher. Each time that code is called, a new hash is created. And this has to be done for each letter. That is a huge waste of resource.
Using the hash, you can simply do this:
h = {"a" => "n", "b" => "o", 'c' => 'p', 'd' => 'q',
'e' => 'r', 'f' => 's', 'g' => 't', 'h' => 'u',
'i' => 'v', 'j' => 'w', 'k' => 'x', 'l' => 'y',
'm' => 'z', 'n' => 'a', 'o' => 'b', 'p' => 'c',
'q' => 'd', 'r' => 'e', 's' => 'f', 't' => 'g',
'u' => 'h', 'v' => 'i', 'w' => 'j', 'x' => 'k',
'y' => 'l', 'z' => 'm'}
h.default_proc = ->x{x}
"hello world".gsub(/./, h)
# => "uryyb jbeyq"
But I would rather go with this:
from = "abcdefghijklmnopqrstuvwxyz"
to = "nopqrstuvwxyzabcdefghijklm"
"hello world".tr(from, to)
# => "uryyb jbeyq"
There is no functional reason for it. Sometimes programmers feel more comfortable having an explicit variable destination for their results. Maybe this is one of those cases. Same with the decrypted_letter case.

Ruby loops. Hash into string

I am working with Ruby. I need to grab each key/value and put it into a string.
So far I have:
values = ['first' => '1', 'second' => '2']
#thelink = values.collect do | key, value |
"#{key}=#{value}&"
end
When I print #thelink I see:
first1second2=&
But Really what I want is
first=1&second=2
Could anybody help/explain please?
There is something subtle you are missing here {} vs [].
See the below taken from IRB tests:
irb(main):002:0> {'first' => 1, 'second' => 2}
=> {"second"=>2, "first"=>1}
irb(main):003:0> ['first' => 1, 'second' => 2]
=> [{"second"=>2, "first"=>1}]
irb(main):004:0> {'first' => 1, 'second' => 2}.class
=> Hash
irb(main):005:0> ['first' => 1, 'second' => 2].class
=> Array
Similar to this:
irb(main):006:0> {'first' => 1, 'second' => 2}.collect { |key,value| puts "#{key}:#{value}" }
second:2
first:1
=> [nil, nil]
irb(main):007:0> ['first' => 1, 'second' => 2].collect { |key,value| puts "#{key}:#{value}" }
second2first1:
=> [nil]
The array has a single element (a hash) that, as a string, is everything concatenated. This is the important thing to note here.
On the other hand, the hash iterates by handing you the key/value pairs that you are expecting.
Hope that helps.
I think your code has a typo (a hash is delimited by {} not by []). Try this
values = {'first' => '1', 'second' => '2'}
r = values.map{|k,v| "#{k}=#{v}"}.join('&')
puts r
#shows: first=1&second=2

Resources