iterate over an array and delete elements conditionally - ruby

I want to iterate over an array of URLs, and remove elements from it if there is a timeout with the HTTP request for the given URL. It has been implemented in the following way:
#urls.delete_if do |url|
begin
doc = perform_request(some_params)
break
rescue TimeoutError
Rails.logger.warn("URL #{url} times out, will be removed from list")
true
end
end
Anyone for a cleaner solution?

There are a lot more things that can go wrong than timeout, and it's better to ask the affirmative than the negative. That is, does the site respond in the way I want, rather than the does the site not respond in the way I want.
Furthermore, I would encourage practicing immutability, that is, not changing your data in place, but rather creating new versions from the old. My version would look like:
#urls = %w[www.google.com www.example.com]
valid_urls = #urls.select{ |url| up?(url)} #=> [www.google.com]
def up?(url)
Net::HTTP.new(url).head('/').kind_of? Net::HTTPOK
end

Everything seems reasonable, except the break in the cycle. I also like the cleanness of the solution.

Related

Recursively check if nested elements exist

Just to give you a background, I'm using Ruby for creating automated tests along with Selenium, Cucumber, Capybara and SitePrism. I have some tests that need to check the text of a certain element on the page, for example:
def get_section_id
return section.top.course.section_id.text
end
However, I would like to check if all the parent elements exist before calling .text on the nested course_and_section_id element. For example, to check the text of this particular element I would do:
if(has_section? && section.has_top? && section.top.has_course? && section.top.course.has_section_id?)
return section.top.course.section_id.text
end
Is there any way to recursively check if something exists in Ruby like this? Something that could be called like: has_text?(section.top.course.section_id) maybe?
There is nothing builtin to ruby that would do this because the methods you're calling return the element, or raise an exception. If they returned the element or nil then the suggestion of Cary Swoveland to use &. would be the answer.
The critical thing to remember here is what you're actually trying to do. Since you're writing automated tests, you're (most likely) not trying to check whether or not the elements exist (tests should be predictable and repeatable so you should know the elements are going to exist) but rather just wait for the elements to exist before getting the text. This means what you really want is probably more like
def get_section_id
wait_until_section_visible
section.wait_until_top_visible
section.top.wait_until_course_visible
section.top.course.wait_until_section_id_visible
return section.top.course.section_id.text
end
You can write a helper method to make that easier, something like
def get_text_from_nested_element(*args)
args.reduce(self) do |scope, arg|
scope.send("wait_until_#{arg}_visible")
scope.send(arg)
end.text
end
which could be called as
def get_section_id
get_text_from_nested_element(:section, :top, :course, :section_id)
end
It sounds like you may want something like the following.
arr = [section, :top, :course, :section_id, :text]
arr.reduce { |e,m| e && e.respond_to?(m) && e.public_send(m) }
Because reduce has no argument the initial value of the memo e is section. If e becomes nil or false it will remain that value.
Whilst this is a bit outdated, the fact that &. won't work here when it is the most elegant perhaps gives rise for this being a useful feature
If you can raise it on GH with a sample page where this would be useful then we could look at getting it introduced
Luke

Delete from NodeSet during map iteration?

Is it safe to delete a Node from a NodeSet during iteration? I'm pulling some links out of a bunch of a tags but want to remove the tags from the set altogether if the link is invalid.
def get_links(nodeset)
links = nodeset.map do |node|
begin
URI.join(node.document.url, node.get_attribute('href'))
rescue URI::InvalidURIError
nodeset.delete(node) # Is this safe?
nil
end
end
links.compact
end
In your example code I think you're not separating your actions well. Don't manipulate your nodeset array inside the map; It's not that you can't do it, it's that you shouldn't for clarity and ease of maintenance. "Map" the URLs separately from removing the bad ones.
At a minimum I'd do something more like:
def get_valid_links(nodeset)
doc_url = nodeset.first.document.url
links = nodeset.map { |node|
begin
URI.join(doc_url, node['href'])
rescue URI::InvalidURIError
nil
end
end
links.compact
end
nodeset = get_valid_links(nodeset)
Doing it that way doesn't alter nodeset unless you explicitly say so, by assigning the compacted/mapped value returned from get_links. That keeps the purpose of the method very clear, and it has no side effects.
I think this is one of those cases where "POLS" ("Principle Of Least Surprise") would kick in. Having the side-effect of munging nodeset inside the method could be very surprising to someone who's trying to maintain the code or use it in a library, and it'd be hard to work around.
From experience, I'll recommend being very careful throwing the contents of href attributes onto the end of a URL and expecting it to be good or useful. Remember that it's possible for the href to be a JavaScript link, which will make an ugly URL.

How enum#feed works in ruby?

Enum#feed
Sets the value to be returned by the next yield inside e.If
the value is not set, the yield returns nil.This value is cleared after
being yielded.
I tried one example but it is not the one I think to understand the
#feed method.
a = [1,2,3,4].to_enum
p a.next #=> 1
a.feed 'foo'
p a.next #=> 2 , I expected here 'foo'
Can anyone give me a good example to understand how the #feed method
works?
Based on the documentation you linked to, it's up to the Enumerable object in question to decide what to do with your feed call. In this case, the default behavior is to ignore it. If you write your own custom Enumerable class, you could accept that and treat it as you will.
I have never seen feed used, but then again, it's the sort of thing that's put there for those occasions when you might need it. I can't think of a use for this thing at all, to be honest.
You'll have to test various things for support, but I'd be surprised if you find any objects that use it as you presume.

Should the return value of "save" always be checked when using ActiveRecord?

In general, should the return value of save always be checked when using ActiveRecord?
For example, I've come across some code like this:
def foo(active_record_instance)
active_record_instance.field_1 = 'a'
active_record_instance.field_2 = 'b'
# ...15 more lines...
active_record_instance.save # <==
baz = bar(active_record_instance.id)
# ...15 more lines that use baz...
baz
end
def bar(id)
instance = ActiveRecordSubclass.find(id)
instance.field_3 = instance.field_1 + instance.field_2
instance
end
That's slightly contrived, but it's a fairly realistic example for the codebase I'm working on. (This isn't an isolated case of this pattern.)
Given that the validation for ActiveRecordSubclass is in flux and change is possible in the near future (or even a year from now), my thought is that the return value of active_record_instance.save should be checked. Another alternative would be to use active_record_instance.save!.
Is it appropriate to check whether or not the record saved? Or is the foo method micromanaging something that should not be its concern, given that the current validation does not fail?
Maybe you could use save! method, catch exception and put your logic inside .
begin
foo.save!
rescue ActiveRecord::RecordInvalid
# handle logic here
end
The real answer is, `do you care about the data in question?`
If you care about the data, then yes, you should return something false or throw an exception, and somehow communicate why it failed validation.
If you really don't care if it saves, and it will be tried in 10 seconds, at which point you expect it to work, then ignore the error.
As a personal preference and experience, I would rather have something fail fast and dramatically, than spend hours or days hunting down a bug because something 50 steps before didn't actually save.

How can I refactor this piece of Ruby code to remove duplication?

I do not have problem as such but I am quite new to Ruby. I have the following 3 repeatable bits of code in one method and I would like to know how a true Rubyist would first of all remove the duplication and secondly make it more reusable.
Here is the code in question:
file = File.new( destination)
doc = REXML::Document.new file
doc.elements.each("configuration/continuity2/plans") do |element|
element.attributes["storebasedir"] = "#{TEST_OUTPUT_DIRECTORY}"
end
doc.elements.each("configuration/add").each do |database|
database.raw_attributes = database.attributes.merge("connectionstring" => "#{TEST_CONNECTION_STRING}")
end
doc.elements.each("configuration/connectionStrings/plans") do |connectionString|
connectionString.raw_attributes = connectionString.attributes.merge("connectionString" => "#{TEST_CONNECTION_STRING}")
end
Any advice appreciated.
The last two blocks could be replaced with
["add", "connectionStrings/plans"].each do |elt_name|
doc.elements.each("configuration/#{elt_name}").do |elt|
elt.raw_attributes = elt.attributes.merge("connectionString" => "#{TEST_CONNECTION_STRING}")
end
end
I assume the case difference between "connectionstring" and "connectionString" was accidental. If so, that exactly illustrates the benefit of removing duplication.
Also, you can probably replace "#{TEST_CONNECTION_STRING}" with TEST_CONNECTION_STRING.
You could try to add a method that tries to be as generic as possible to avoid this, but they are significantly different for me... You risk having complex code just to be able to wrap these lines into a single method.
I don't see the duplication. The collections you are iterating over and substantially different and the operation you are performing on each element is also very different. I agree with Olivier that any attempt at removing the duplication will simply result in more complex code.

Resources