how subject work in rspec in ruby on rails - ruby-on-rails

I recently learn about rspec test in rails and as following the link https://relishapp.com/rspec/rspec-core/v/3-6/docs/subject/explicit-subject that said "The subject is memoized within an example but not across examples " with the code below:
RSpec.describe Array do
# This uses a context local variable. As you can see from the
# specs, it can mutate across examples. Use with caution.
element_list = [1, 2, 3]
subject { element_list.pop }
it "is memoized across calls (i.e. the block is invoked once)" do
expect {
3.times { subject }
}.to change{ element_list }.from([1, 2, 3]).to([1, 2])
expect(subject).to eq(3)
end
it "is not memoized across examples" do
expect{ subject }.to change{ element_list }.from([1, 2]).to([1])
expect(subject).to eq(2)
end
end
Can anyone explain to me:
why 3.times { subject } exec only once element_list.pop
the sentence 'it "is not memoized across examples"' meant element_list still [1, 2, 3] but in this example, it is only [1, 2] ?
Thank you.

First, by way of clarification, the RSpec subject helper is nothing more than a special case of RSpec's let helper. Using subject { element_list.pop } is equivalent to let(:subject) { element_list.pop }.
Like any other let, subject is evaluated once per example. If subject has a value, that value is returned without re-evaluation. The general term for this is "memoization."
Ruby's ||= operator does the same thing. It says, "if a value exists in that variable, return the value, otherwise evaluate the expression, assign it to the variable, and return the value." You can see this concept in action in the console with this example:
>> element_list = [1, 2, 3]
>> subject ||= element_list.pop
=> 3
>> element_list
=> [1, 2]
>> subject ||= element_list.pop
=> 3
>> element_list
=> [1, 2]
>> subject
=> 3
The fact that subject is not memoized across examples means that its value is reset when a new example is executed. So for your next it block, subject will start out unassigned and its expression will be re-evaluated when it is first used in that next it block.

Related

Class-level memoization vs Constants. What is the difference? Which one to use?

I am working on a rails app, where I have a class with a large constant. The reviewer asked me use a self method instead, stating that having a large constant will increase the application startup time. But I don't see the difference or the advantage.
Can you please help me understand the difference?
class Test
X = [1, 2, 3]
def self.x
#_x ||= [1, 2, 3]
end
end
Test::X
Test.x
When Ruby encounters a class keyword it evaluates the class body right away (same for module). You might want to try this in IRB:
class Foo
puts 'I got evaluated'
end
# I got evaluated
That's why your constant assignment is evaluated the moment you load the class file.
Methods on the other hand have to be called explicitly:
class Foo
def self.bar
puts 'I got evaluated'
end
end
Foo.bar
# I got evaluated
Conditionally assigning your array to an instance variable in a method defers the creation until you actually need (i.e. call) it.
Your example with debug output added:
class Test
X = (puts 'X assigned' ; [1, 2, 3])
def self.x
#_x ||= (puts '#_x assigned' ; [1, 2, 3])
end
end
# X assigned <-- constant from class body
Test::X
#=> [1, 2, 3]
Test.x
# #_x assigned <-- instance variable from method
#=> [1, 2, 3]
Test.x
#=> [1, 2, 3]

How to calculate the highest word frequency in Ruby

I have been working on this assignment for a Coursera Intro to Rails course. We have been tasked to write a program that calculates maximum word frequency in a text file. We have been instructed to create a method which:
Calculates the maximum number of times a single word appears in the given content and store in highest_wf_count.
Identify the words that were used the maximum number of times and store that in highest_wf_words.
When I run the rspec tests that were given to us, one test is failing. I printed my output to see what the problem is but haven't been able to fix it.
Here is my code, the rspec test, and what I get:
class LineAnalyzer
attr_accessor :highest_wf_count
attr_accessor :highest_wf_words
attr_accessor :content
attr_accessor :line_number
def initialize(content, line_number)
#content = content
#line_number = line_number
#highest_wf_count = 0
#highest_wf_words = highest_wf_words
calculate_word_frequency
end
def calculate_word_frequency()
#highest_wf_words = Hash.new(0)
#content.split.each do |word|
#highest_wf_words[word.downcase!] += 1
if #highest_wf_words.has_key?(word)
#highest_wf_words[word] += 1
else
#highest_wf_words[word] = 1
end
#highest_wf_words.sort_by{|word, count| count}
#highest_wf_count = #highest_wf_words.max_by {|word, count| count}
end
end
def highest_wf_count()
p #highest_wf_count
end
end
This is the rspec code:
require 'rspec'
describe LineAnalyzer do
subject(:lineAnalyzer) { LineAnalyzer.new("test", 1) }
it "has accessor for highest_wf_count" do
is_expected.to respond_to(:highest_wf_count)
end
it "has accessor for highest_wf_words" do
is_expected.to respond_to(:highest_wf_words)
end
it "has accessor for content" do
is_expected.to respond_to(:content)
end
it "has accessor for line_number" do
is_expected.to respond_to(:line_number)
end
it "has method calculate_word_frequency" do
is_expected.to respond_to(:calculate_word_frequency)
end
context "attributes and values" do
it "has attributes content and line_number" do
is_expected.to have_attributes(content: "test", line_number: 1)
end
it "content attribute should have value \"test\"" do
expect(lineAnalyzer.content).to eq("test")
end
it "line_number attribute should have value 1" do
expect(lineAnalyzer.line_number).to eq(1)
end
end
it "calls calculate_word_frequency when created" do
expect_any_instance_of(LineAnalyzer).to receive(:calculate_word_frequency)
LineAnalyzer.new("", 1)
end
context "#calculate_word_frequency" do
subject(:lineAnalyzer) { LineAnalyzer.new("This is a really really really cool cool you you you", 2) }
it "highest_wf_count value is 3" do
expect(lineAnalyzer.highest_wf_count).to eq(3)
end
it "highest_wf_words will include \"really\" and \"you\"" do
expect(lineAnalyzer.highest_wf_words).to include 'really', 'you'
end
it "content attribute will have value \"This is a really really really cool cool you you you\"" do
expect(lineAnalyzer.content).to eq("This is a really really really cool cool you you you")
end
it "line_number attribute will have value 2" do
expect(lineAnalyzer.line_number).to eq(2)
end
end
end
This is the rspec output:
13 examples, 1 failure
Failed examples:
rspec ./course01/module02/assignment-Calc-Max-Word-Freq/spec/line_analyzer_spec.rb:42 # LineAnalyzer#calculate_word_frequency highest_wf_count value is 3
My output:
#<LineAnalyzer:0x00007fc7f9018858 #content="This is a really really really cool cool you you you", #line_number=2, #highest_wf_count=[nil, 10], #highest_wf_words={"this"=>2, nil=>10, "is"=>1, "a"=>1, "really"=>3, "cool"=>2, "you"=>3}>
Based on the test string, the word counts aren't correct.
"nil" is being included in the hash.
The hash is not being sorted by the value (count) like it should.
I tried several things to fix these problems and nothing has worked. I went through the lecture material again, but can't find anything that would help and the discussion boards are not often monitored for questions from students.
Accoriding to Ruby documentation:
downcase!(*args) public
Downcases the contents of str, returning nil if no changes were made.
Due to this unexpected behavior of .downcase! method, if the word is already all lowercase, you're incrementing occurrences of nil in this line:
#highest_wf_words[word.downcase!] += 1
Tests are also failing because #highest_wf_words.max_by {|word, count| count} returns an array containing the count and a word, while we want to get only the count.
A simplified calculate_word_frequency method passing the tests would look like this:
def calculate_word_frequency()
#highest_wf_words = Hash.new(0)
#content.split.each do |word|
# we don't have to check if the word existed before
# because we set 0 as default value in #highest_wf_words hash
# use .downcase instead of .downcase!
#highest_wf_words[word.downcase] += 1
# extract only the count, and then get the max
#highest_wf_count = #highest_wf_words.map {|word, count| count}.max
end
end
Nil:
The nil is from downcase!
This modifies the String inplace and returns nil if nothing has changed.
If you say "this is weird", then you are right (IMHO).
# just use the non destructive variant
word.downcase
Sorting:
sort_by returns a new object (Hash, Array, ...) and does not modify the receiver of the method. You either need to re-assign or to use sort_by!
unsorted = [3, 1, 2]
sorted = unsorted.sort
p unsorted # => [3, 1, 2]
p sorted # => [1, 2, 3]
unsorted.sort!
p unsorted # => [1, 2, 3]
Faulty word count:
Once you corrected those two mistakes it should look better. Be aware, that the method does not return a single integer but a two-element array with the word and count, so it should look something like this: ["really", 6]
Simplifiying things:
If you can use ruby 2.7, then there is the handy Enumerable#tally method!
%w(foo foo bar foo baz foo).tally
=> {"foo"=>4, "bar"=>1, "baz"=>1}
Example taken from
https://medium.com/#baweaver/ruby-2-7-enumerable-tally-a706a5fb11ea

How can I call a class method in Ruby with the method name stored in an array?

I am currently working on a Poker game in Ruby. Instead of using numerous if-else statements to check the value of the player's hand, I decided to do the following:
#calculate the players score
def score
POSS.map {|check|
if (check[1].call())
#score = check[0]
puts #score
return check[0]
else
false
end
}
end
POSS = [
[10, :royal_flush?],
[9, :straight_flush?],
[8, :four_kind?],
[7, :full_house?],
[6, :flush?],
[5, :straight?],
[4, :three_kind?],
[3, :two_pairs?],
[2, :pair?]
]
The second item in each item of 'POSS' is a method I created to check whether the player has that hand. I am attempting to call the method with .call(), but get the following error:
Player.rb:43:in `block in score': undefined method `call' for
:royal_flush?:Symbol (NoMethodError) from Player.rb:42:in `map' from
Player.rb:42:in `score' from Player.rb:102:in `get_score' from
Player.rb:242:in `<main>'
http://ruby-doc.org/core-2.2.2/Object.html Object#send is the method you are looking for.
Since you are wanting a 'class method', then Object should be self when declaring instance methods of the class that contains the 'class methods'
Try this code
#calculate the players score
def score
POSS.map do |check|
if self.send check[1]
#score = check[0]
puts #score
return check[0]
else
false
end
end
end
POSS = [
[10, :royal_flush?],
[9, :straight_flush?],
[8, :four_kind?],
[7, :full_house?],
[6, :flush?],
[5, :straight?],
[4, :three_kind?],
[3, :two_pairs?],
[2, :pair?]
]
Styles vary from person to person, however, I think when using multi line blocks, it would be best to use 'do,end' pair instead of '{ }'
https://github.com/bbatsov/ruby-style-guide
I think some confusion may come from code that looks like this
foobar = ->(foo,bar){puts "Passed in #{foo}, #{bar}"}
foobar.call("one","two")
If the first line was abstracted into other parts of the program you may have thought that foobar was a method, but its really a lambda. Procs and Lambdas are just like methods but better.. in their own way.. Check out this article on Procs, Blocks and Lambdas.
http://www.reactive.io/tips/2008/12/21/understanding-ruby-blocks-procs-and-lambdas/
But also if interested check out https://www.codecademy.com/forums/ruby-beginner-en-L3ZCI for more detailed hands on with PBLS
You can use send.
class Foo
def self.bar
"bar"
end
def baz
"baz"
end
end
Foo.send(:bar) # => "bar"
f = Foo.new
f.send(:baz) # => "baz"
Though I'm not sure this is the best approach for the app you're writing. When you get it working you can post it on CodeReview.
No method is no method; and there is no Symbol#call which causes the immediate error.
The (or rather, a) way to call is via Object#__send__, supplying the name and arguments. One could also resolve a method and then call that; but __send__ is the most direct route as Ruby is based on message passing.
That is, instead of symbol.call(..), use obj.__send__(symbol, ..). (In this case the object would probably be self.)
See Understanding Ruby symbol as method call

Ruby (Monkey Patching Array)

Back for more help on my coursework at Bloc. Decided to bring you guys in on a problem I'm having with Monkey Patching the Array Class. This assignment had 8 specs to be met and I'm stuck now with one left and I'm not sure what to do.
I'm only going to give you the RSpecs and written requirements for the part that I'm having trouble w/ since everything else seems to be passing. Also I'll include the starter scaffolding that they gave me to begin with so there's no confusion or useless additions to the code.
Here are the written requirements for the Array Class Monkey Patch:
Write a new new_map method that is called on an instance of the Array class. It should use the array it's called on as an implicit (self) argument, but otherwise behave identically. (INCOMPLETE)
Write a new_select! method that behaves like the select, but mutates the array on which it's called. It can use Ruby's built-in collection select method. (COMPLETE)
Here are the RSpecs that need to be met regarding the Array class:
Note: "returns an array with updated values" Is the only Spec not passing.
describe Array do
describe '#new_map' do
it "returns an array with updated values" do
array = [1,2,3,4]
expect( array.new_map(&:to_s) ).to eq( %w{1 2 3 4} )
expect( array.new_map{ |e| e + 2 } ).to eq( [3, 4, 5, 6] )
end
it "does not call #map" do
array = [1,2,3,4]
array.stub(:map) { '' }
expect( array.new_map(&:to_s) ).to eq( %w{1 2 3 4} )
end
it "does not change the original array" do
array = [1,2,3,4]
expect( array.new_map(&:to_s) ).to eq( %w{1 2 3 4} )
expect( array ).to eq([1,2,3,4])
end
end
describe '#new_select!' do
it "selects according to the block instructions" do
expect( [1,2,3,4].new_select!{ |e| e > 2 } ).to eq( [3,4] )
expect( [1,2,3,4].new_select!{ |e| e < 2 } ).to eq( [1] )
end
it "mutates the original collection" do
array = [1,2,3,4]
array.new_select!(&:even?)
expect(array).to eq([2,4])
end
end
end
Here is the scaffolding they started me with:
class Array
def new_map
end
def new_select!(&block)
end
end
Lastly, here is my code:
class Array
def new_map
new_array = []
self.each do |num|
new_array << num.to_s
end
new_array
end
def new_select!(&block)
self.select!(&block)
end
end
Ruby Array Class:
map { |item| block } → new_ary
A block is like a method, and you specify a block after a method call, for example:
[1, 2, 3].map() {|x| x*2} #<---block
^
|
method call(usually written without the trailing parentheses)
The block is implicitly sent to the method, and inside the method you can call the block with yield.
yield -> calls the block specified after a method call. In ruby, yield is equivalent to yield(), which is conceptually equivalent to calling the block like this: block().
yield(x) -> calls the block specified after a method call sending it the argument x, which is conceptually equivalent to calling the block like this: block(x).
So, here is how you can implement new_map():
class Array
def new_map
result = []
each do |item|
result << yield(item)
end
result
end
end
arr = [1, 2, 3].new_map {|x| x*2}
p arr
--output:--
[2, 4, 6]
This comment is a bit advanced, but you don't actually have to write self.each() to call the each() method inside new_map(). All methods are called by some object, i.e. the object to the left of the dot, which is called the receiver. For instance, when you write:
self.each {....}
self is the receiver of the method call each().
If you do not specify a receiver and just write:
each {....}
...then for the receiver ruby uses whatever object is assigned to the self variable at that moment. Inside new_map() above, ruby will assign the Array that calls the new_map() method to self, so each() will step through the items in that Array.
You have to be a little bit careful with the self variable because ruby constantly changes the value of the self variable without telling you. So, you have to know what ruby has assigned to the self variable at any particular point in your code--which comes with experience. Although, if you ever want to know what object ruby has assigned to self at some particular point in your code, you can simply write:
puts self
Experienced rubyists will crow and cluck their tongues if they see you write self.each {...} inside new_map(), but in my opinion code clarity trumps code trickiness, and because it makes more sense to beginners to write self there, go ahead and do it. When you get a little more experience and want to show off, then you can eliminate explicit receivers when they aren't required. It's sort of the same situation with explicit returns:
def some_method
...
return result
end
and implicit returns:
def some_method
...
result
end
Note that you could write new_map() like this:
class Array
def new_map(&my_block) #capture the block in a variable
result = []
each do |item|
result << my_block.call(item) #call the block
end
result
end
end
arr = [1, 2, 3].new_map {|x| x*2}
p arr
--output:--
[2, 4, 6]
Compare that to the example that uses yield(). When you use yield(), it's as if ruby creates a parameter variable named yield for you in order to capture the block. However, with yield you use a different syntax to call the block, namely (), or if their are no arguments for the block, you can eliminate the parentheses--just like you can when you call a method. On the other hand, when you create your own parameter variable to capture a block, e.g. def new_map(&my_block), you have to use a different syntax to call the block:
my_block.call(arg1, ...)
or:
myblock[arg1, ...]
Note that #2 is just like the syntax for calling a method--except that you substitute [] in place of ().
Once again, experienced rubyists will use yield to call the block instead of capturing the block in a parameter variable. However, there are situations where you will need to capture the block in a parameter variable, e.g. if you want to pass the block to yet another method.
Looking at the spec here:
it "returns an array with updated values" do
array = [1,2,3,4]
expect( array.new_map(&:to_s) ).to eq( %w{1 2 3 4} )
expect( array.new_map{ |e| e + 2 } ).to eq( [3, 4, 5, 6] )
end
It looks like they just want you to re-write Array.map so that it will work with any given block. In your implementation you are telling the method to work in a very specific way, namely to call .to_s on all of the array elements. But you don't want it to always stringify the array elements. You want it to do to each element whatever block is provided when the method is called. Try this:
class Array
def new_map
new_array = []
each do |num|
new_array << yield(num)
end
new_array
end
end
Notice in my example that the method definition doesn't specify any particular operation to be performed on each element of self. It simply loops over each element of the array, yields the element (num) to whatever block was passed when .new_map was called, and shovels the result into the new_array variable.
With that implementation of .new_map and given array = [1,2,3,4], you can call either array.new_map(&:to_s) (the block is where the arbitrary operation to be performed on each element of the array is specified) and get ["1","2","3","4"] or you can call array.new_map { |e| e + 2 } and get [3,4,5,6].
I would like to say a few words about new_select!.
select vs select!
You have:
class Array
def new_select!(&block)
self.select!(&block)
end
end
which you could instead write:
class Array
def new_select!
self.select! { |e| yield(e) }
end
end
This uses the method Array#select!. You said Array#select could be used, but made no mention of select!. If you cannot use select!, you must do something like this:
class Array
def new_select!(&block)
replace(select(&block))
end
end
Let's try it:
a = [1,2,3,4,5]
a.new_select! { |n| n.odd? }
#=> [1, 3, 5]
a #=> [1, 3, 5]
Explicit vs implicit receivers
Notice that I have written this without any explicit receivers for the methods Array#replace and Array#select. When there is no explicit receiver, Ruby assumes that it is self, which is the a. Therefore, Ruby evaluates replace(select(&block)) as though it were written with explicit receivers:
self.replace(self.select(&block))
It's up to you to decide if you want to include self.. Some Rubiests do, some don't. You'll notice that self. is not included in Ruby built-in methods implemented in Ruby. One other thing: self. is required in some situations to avoid ambiguity. For example, if taco= is the setter for an instance variable #taco, you must write self.taco = 7 to tell Ruby you are referring to the setter method. If you write taco = 7, Ruby will assume you want to create a local variable taco and set its value to 7.
Two forms of select!
Is your method new_select! a direct replacement for select!? That is, are the two methods functionally equivalent? If you look at the docs for Array#select!, you will see it has two forms, the one you have mimicked, and another that you have not implemented.
If select! is not given a block, it returns an enumerator. Now why would you want to do that? Suppose you wish to write:
a = [1,2,3,4,5]
a.select!.with_index { |n,i| i < 2 }
#=> [1, 2]
Has select! been given a block? No, so it must return an enumerator, which becomes the receiver of the method Enumerator#with_index.
Let's try it:
enum0 = a.select!
#=> #<Enumerator: [1, 2, 3, 4, 5]:select!>
Now:
enum1 = enum0.with_index
#=> #<Enumerator: #<Enumerator: [1, 2, 3, 4, 5]:select!>:with_index>
You can think of enum1 as a "compound enumerator". Look carefully at the description (above) of the object Ruby returns when you define enum1.
We can see the elements of the enumerator by converting it to an array:
enum1.to_a
# => [[1, 0], [2, 1], [3, 2], [4, 3], [5, 4]]
Each of these five elements is passed to the block and assigned to the block variables by Enumerator#each (which calls Array#each).
Enough of that, but the point is that having methods return enumerators when no block is given is what allows us to chain methods.
You do not have a test that ensures new_select! returns an enumerator when no block is given, so maybe that's not expected, but why not give it a go?
class Array
def new_select!(&block)
if block_given?
replace(select(&block))
else
to_enum(:new_select!)
end
end
end
Try it:
a = [1,2,3,4,5]
a.new_select! { |n| n.odd? }
#=> [1, 3, 5]
a #=> [1, 3, 5]
a = [1,2,3,4,5]
enum2 = a.new_select!
#=> #<Enumerator: [1, 2, 3, 4, 5]:new_select!>
enum2.each { |n| n.odd? }
#=> [1, 3, 5]
a #=> [1, 3, 5]
a = [1,2,3,4,5]
enum2 = a.new_select!
#=> #<Enumerator: [1, 2, 3, 4, 5]:new_select!>
enum2.with_index.each { |n,i| i>2 }
#=> [4, 5]
a #=> [4, 5]

Match multiple yields in any order

I want to test an iterator using rspec. It seems to me that the only possible yield matcher is yield_successive_args (according to https://www.relishapp.com/rspec/rspec-expectations/v/3-0/docs/built-in-matchers/yield-matchers). The other matchers are used only for single yielding.
But yield_successive_args fails if the yielding is in other order than specified.
Is there any method or nice workaround for testing iterator that yields in any order?
Something like the following:
expect { |b| array.each(&b) }.to yield_multiple_args_in_any_order(1, 2, 3)
Here is the matcher I came up for this problem, it's fairly simple, and should work with a good degree of efficiency.
require 'set'
RSpec::Matchers.define :yield_in_any_order do |*values|
expected_yields = Set[*values]
actual_yields = Set[]
match do |blk|
blk[->(x){ actual_yields << x }] # ***
expected_yields == actual_yields # ***
end
failure_message do |actual|
"expected to receive #{surface_descriptions_in expected_yields} "\
"but #{surface_descriptions_in actual_yields} were yielded."
end
failure_message_when_negated do |actual|
"expected not to have all of "\
"#{surface_descriptions_in expected_yields} yielded."
end
def supports_block_expectations?
true
end
end
I've highlighted the lines containing most of the important logic with # ***. It's a pretty straightforward implementation.
Usage
Just put it in a file, under spec/support/matchers/, and make sure you require it from the specs that need it. Most of the time, people just add a line like this:
Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each {|f| require f}
to their spec_helper.rb but if you have a lot of support files, and they aren't all needed everywhere, this can get a bit much, so you may want to only include it where it is used.
Then, in the specs themselves, the usage is like that of any other yielding matcher:
class Iterator
def custom_iterator
(1..10).to_a.shuffle.each { |x| yield x }
end
end
describe "Matcher" do
it "works" do
iter = Iterator.new
expect { |b| iter.custom_iterator(&b) }.to yield_in_any_order(*(1..10))
end
end
This can be solved in plain Ruby using a set intersection of arrays:
array1 = [3, 2, 4]
array2 = [4, 3, 2]
expect(array1).to eq (array1 & array2)
# for an enumerator:
enumerator = array1.each
expect(enumerator.to_a).to eq (enumerator.to_a & array2)
The intersection (&) will return items that are present in both collections, keeping the order of the first argument.

Resources