Nested blocks and ERB - ruby

For the life of me, I can't figure out why this doesn't work as expected.
Code:
require 'erb'
def say_hello(name)
"Nice to see you, #{ name }!"
end
def greetings
template = <<-TEMPLATE
Hello!
<%= yield %>
Goodbye!
TEMPLATE
ERB.new(template).result(binding)
end
people = ['Aaron', 'Bob', 'Tim', 'Juan']
t = greetings do
people.each do |p|
say_hello(p)
end
end
puts t
(a bit contrived, I know, but it'll serve the point.)
What I Expect:
Hello!
Nice to see you, Aaron!
Nice to see you, Bob!
Nice to see you, Tim!
Nice to see you, Juan!
Goodbye!
What I Get:
Hello!
['Aaron', 'Bob', 'Tim', 'Juan']
Goodbye!
Thoughts:
I'm guessing this is happening because the interior block (beginning with people.each) gets coerced into a string before the block executes. Perhaps ERB doesn't like how I'm trying to inject a new block of constructed text into its template.
What's going on here?

The return value of each is the array itself, not the return value of the block:
people.each do |p|
say_hello(p)
end
# => ['Aaron', 'Bob', 'Tim', 'Juan']
You should use map, which returns the array of return values from the block:
people.map do |p|
say_hello(p)
end
# => ["Nice to see you, Aaron!", "Nice to see you, Bob!", "Nice to see you, Tim!", "Nice to see you, Juan!"]
You will also need to concatenate the array to render it properly:
t = greetings do
people.map do |p|
say_hello(p)
end.join("\n")
end

Related

How can I handle using multiple variables depending on which element of an iterator I am using? Aka, how can I DRY up this ruby code?

I have some variables that look like this:
top_script_path = "path/to/top"
bottom_script_path = "path/to/bottom"
script_names = ["top", "bottom"]
and I'd like to call each of the scripts
`#{top_script_path} "top"`
puts "top script successful"
`#{bottom_script_path} "bottom"`
puts "bottom script successful"
This solution, however, doesn't feel DRY enough to me. I'd like to be able to do something like
script_names.each do |name|
`#{#{name}_script_path} #{name}`
puts "#{name} script successful"
end
Obviously, it isn't possible to put a #{expression} inside of a #{expression} as above, but is there any other way to dry up this code with a loop?
Use hashes:
script_paths = {
:top => 'path/to/top',
:bottom => 'path/to/bottom',
}
script_names = script_paths.keys
script_names.each do |name|
# `...`
puts "#{script_paths[name]} #{name}"
end
Run:
$ ruby qq.rb
path/to/top top
path/to/bottom bottom
script_names.each do |name|
`#{eval("#{name}_script_path")} #{name}`
puts "#{name} script successful"
end
I would refactor the variables into one structure, something like:
scripts = {
'top' => 'path/to/top',
'bottom' => 'path/to/bottom'
}
scripts.each do |name, path|
`#{path} #{name}`
puts "#{name} script successful"
end
If you are writing some kind of build script, consider using Rake.

Better way to assert that all user.name in an array of user start with a prefix using rspec?

Here is what I have. And that kind of work.
it "should filter by name" do
users = users.search(:name => "s")
users.each {|u|
u.name.should be_starts_with("s")
}
end
However, the error message returned by rspec is really poor...
expected starts_with?("s") to return true, got false
Is there a way to get a more precise message, showing the element that failed, or at least its index?
In a binary test like this, I would create two users, one that starts with an s, the other without. I would then check that only the expected element was returned.
like
set up a user(:name => "Sam") and user(:name => "Fred")
filtered_users.map(&:name).should =~ ["Sam"]
In the case of failure, you will see something like
expected ["Sam"]
got ["Fred", "Sam"]
This is much more explicit about what you are doing
The reason you are only getting expected true but got false is because the starts_with methods returns true or false and not the actual value.
I'm not sure that this is the best way, but you can output it yourself.
users.each {|u|
p u.name if !u.name.starts_with?("s")
u.name.should be_starts_with("s")
}
Here is the way I used few times in cases like this:
describe 'user' do
before :each do
#users = users.search(:name => "s")
end
#users.each do |u|
it "should filter user with name '#{u.name}'" do
u.name.should be_starts_with("s")
end
end
end
You will have failed user name in you example description.
I found here an interesting extension to the matchers from Rspec concerning each :
http://xtargets.com/2011/08/12/rspec-meta-expectations-over-collections
So I sticked that helper into my spec_helper
RSpec::Matchers.define :each do |meta|
match do |actual|
actual.each_with_index do |i, j|
#elem = j
i.should meta
end
end
failure_message_for_should do |actual|
"at[#{#elem}] #{meta.failure_message_for_should}"
end
that allows me to write
users.should each satisfy {|u| u.name.should be_starts_with 's'}
and the error message is then :
at[1] expected #User to satisfy block
which give me the first index of failure.
With some addition to the error message, I'm sure I could output the details of that object that didn't match, and that seem a pretty good solution.
Any thoughts? I'm not a rubyist, just getting started with rails. Would be nice to get more input from
This should provide you with far better failure messages
it "should filter by name" do
users = users.search(:name => "s")
users.each do |u|
u.name.should match /^s/
end
end
I agree with Corey that calling "be_starts_with" is rough. RSpec expectations are intended to be read fluidly as a sentence. They don't all have to use "be".

Ruby, better way to implement conditional iteration than this?

I have an array #cities = ["Vienna", "Barcelona", "Paris"];
and I am trying to display the individual items with a spacer in between. However it is possible that there is only 1 element in the array, in which case I do not want to display the spacer. And also the array could be empty, in which case I want to display nothing.
For the above array I want the following output:
Vienna
-----
Barcelona
-----
Paris
I use an erb template cityview to apply formatting, css, etc before actually printing the city names. Simplified, it looks like this:
<p><%= #cities[#city_id] %></p>
I have implemented it as follows...
unless #array.empty?
#city_id = 0;
erb :cityview
end
unless #array[1..-1].nil?
#array[1..-1].each_index do |i|
#city_id = i+1;
puts "<p>-------</p>";
erb :cityview
end
end
Is there a better way?
#cities.join("<p>--------</p>")
Edit to address the template
Here I'm assuming that there's an erbs method that returns the rendered template without doing a puts. Returning the string allows easier manipulation and reuse.
#cities.map { |c| #city = c; erb :cityview }.join("<p>--------</p>")
I'd prefer:
erb:
<p><%= #city %></p>
and loop
#array.each_with_index do |e, i|
#city = e
erb :cityview
puts "<p>-------</p>" if i < #array.length - 1
end
I assume you have split the erb, bit because you want to customize it.
If you want to mix HTML with your city names then you'll need to worry about HTML encoding things before you mix in your HTML. Using just the standard library:
require 'cgi'
html = #cities.map { |c| CGI.escapeHTML(c) }.join('<p>-----</p>')
If you're in Rails, then you can use html_escape from ERB::Util and mark the result as safe-for-HTML with html_safe to avoid having to worry about the encoding in your view:
include ERB::Util
html = #cities.map { |c| html_escape(c) }.join('<p>-----</p>').html_safe
The simpler solution would be to use a spacer template.
http://guides.rubyonrails.org/layouts_and_rendering.html#spacer-templates

DRY the SUT up - RSpec and Mocking question

n the .net world, my specs would follow the Arrange, Act, Assert pattern. I'm having trouble replicating that in rspec, because there doesn't appear to be an ability to selectively verify your mocks after the SUT has taken it's action. That, coupled with the fact that EVERY expectation is evaluated at the end of each 'It' block, is causing me to repeat myself in a lot of my specs.
Here's an example of what I'm talking about:
describe 'AmazonImporter' do
before(:each) do
Kernel.**stubs**(:sleep).with(1)
end
# iterates through the amazon categories, and for each one, loads ideas with
# the right response group, upserting ideas as it goes
# then goes through, and fleshes out all of the ideas that only have asins.
describe "find_new_ideas" do
before(:all) do
#xml = File.open(File.expand_path('../amazon_ideas_in_category.xml', __FILE__), 'r') {|f| f.read }
end
before(:each) do
#category = AmazonCategory.new(:name => "name", :amazon_id => 1036682)
#response = Amazon::Ecs::Response.new(#xml)
#response_group = "MostGifted"
#asin = 'B002EL2WQI'
#request_hash = {:operation => "BrowseNodeLookup", :browse_node_id => #category.amazon_id,
:response_group => #response_group}
Amazon::Ecs.**expects**(:send_request).with(has_entries(#request_hash)).returns(#response)
GiftIdea.expects(:first).with(has_entries({:site_key => #asin})).returns(nil)
GiftIdea.any_instance.expects(:save)
end
it "sleeps for 1 second after each amazon request" do
Kernel.**expects**(:sleep).with(1)
AmazonImporter.new.find_new_ideas(#category, #response_group)
end
it "loads the ideas for the given response group from amazon" do
Amazon::Ecs.**expects**(:send_request).
with(has_entries(#request_hash)).
returns(#response)
**AmazonImporter.new.find_new_ideas(#category, #response_group)**
end
it "tries to load those ideas from repository" do
GiftIdea.expects(:first).with(has_entries({:site_key => #asin}))
**AmazonImporter.new.find_new_ideas(#category, #response_group)**
end
In this partial example, I'm testing the find_new_ideas method. But I have to call it for each spec (the full spec has 9 assertion blocks). I further have to duplicate the mock setup so that it's stubbed in the before block, but individually expected in the it/assertion block. I'm duplicating or nearly duplicating a ton of code here. I think it's even worse than the highlighting indicates, because a lot of those globals are only defined separately so that they can be consumed by an 'expects' test later on. Is there a better way I'm not seeing yet?
(SUT = System Under Test. Not sure if that's what everyone calls it, or just alt.net folks)
You can use shared example groups to reduce duplication:
shared_examples_for "any pizza" do
it "tastes really good" do
#pizza.should taste_really_good
end
it "is available by the slice" do
#pizza.should be_available_by_the_slice
end
end
describe "New York style thin crust pizza" do
before(:each) do
#pizza = Pizza.new(:region => 'New York' , :style => 'thin crust' )
end
it_behaves_like "any pizza"
it "has a really great sauce" do
#pizza.should have_a_really_great_sauce
end
end
Another technique is to use macros, which is handy if you need similar specs in different classes.
Note: the example above is borrowed from The RSpec Book, Chapter 12.
you can separate them using "context" if that helps...
https://github.com/dchelimsky/rspec/wiki/faq

RSpec mocking an :each block

I want to use RSpec mocks to provide canned input to a block.
Ruby:
class Parser
attr_accessor :extracted
def parse(fname)
File.open(fname).each do |line|
extracted = line if line =~ /^RCS file: (.*),v$/
end
end
end
RSpec:
describe Parser
before do
#parser = Parser.new
#lines = mock("lines")
#lines.stub!(:each)
File.stub!(:open).and_return(#lines)
end
it "should extract a filename into extracted" do
linetext = [ "RCS file: hello,v\n", "bla bla bla\n" ]
# HELP ME HERE ...
# the :each should be fed with 'linetext'
#lines.should_receive(:each)
#parser.should_receive('extracted=')
#parser.parse("somefile.txt")
end
end
It's a way to test that the internals of the block work correctly by passing fixtured data into it. But I can't figure out how to do the actual feeding with RSpec mocking mechanism.
update: looks like the problem was not with the linetext, but with the:
#parser.should_receive('extracted=')
it's not the way it's called, replacing it in the ruby code with self.extracted= helps a bit, but feels wrong somehow.
To flesh out the how 'and_yield' works: I don't think 'and_return' is really what you want here. That will set the return value of the File.open block, not the lines yielded to its block. To change the example slightly, say you have this:
Ruby
def parse(fname)
lines = []
File.open(fname){ |line| lines << line*2 }
end
Rspec
describe Parser do
it 'should yield each line' do
File.stub(:open).and_yield('first').and_yield('second')
parse('nofile.txt').should eq(['firstfirst','secondsecond'])
end
end
Will pass. If you replaced that line with an 'and_return' like
File.stub(:open).and_return(['first','second'])
It will fail because the block is being bypassed:
expected: ["firstfirst", "secondsecond"]
got: ["first", "second"]
So bottom line is use 'and_yield' to mock the input to 'each' type blocks. Use 'and_return' to mock the output of those blocks.
I don't have a computer with Ruby & RSpec available to check this, but I suspect you need to add a call to and_yields call [1] on the end of the should_receive(:each). However, you might find it simpler not to use mocks in this case e.g. you could return a StringIO instance containing linetext from the File.open stub.
[1] http://rspec.rubyforge.org/rspec/1.1.11/classes/Spec/Mocks/BaseExpectation.src/M000104.html
I would go with the idea of stubbing the File.open call
lines = "RCS file: hello,v\n", "bla bla bla\n"
File.stub!(:open).and_return(lines)
This should be good enough to test the code inside the loop.
This should do the trick:
describe Parser
before do
#parser = Parser.new
end
it "should extract a filename into extracted" do
linetext = [ "RCS file: hello,v\n", "bla bla bla\n" ]
File.should_receive(:open).with("somefile.txt").and_return(linetext)
#parser.parse("somefile.txt")
#parser.extracted.should == "hello"
end
end
There are some bugs in the Parser class (it won't pass the test), but that's how I'd write the test.

Resources