Different behavior between multi-line vs. in-line block - ruby-on-rails

I am curious why these are producing different outputs. The desired output is an array of ActiveRecord records.
When using an in-line block, it appears to be doing the ActiveRecord lookup, but subsequently nests the original array (minus the rejected elements) inside of another array and appends the nested arrays to the variable.
When using a multi-line block, the expected behavior occurs, i.e. the ActiveRecord lookup occurs and the identified records are appended to the variable.
x = []
y = ["", "1353", "1155"]
x << y.reject!(&:empty?).each { |i| User.find(i) }
# => ["1353", "1155"]
x
#=> [["1353", "1155"]]
vs
x = []
y = ["", "1353", "1155"]
y.reject!(&:empty?)
y.each do |i|
x << User.find(i)
end
# => ["1353", "1155"]
x
#=> [#<User:0x00007fc7fbacc928
# id: 1353,
# login: nil,
...

The difference is that .each returns the array itself, so
[1,2,3].each { nil }
# [1,2,3]
so you are appending the rejected Y numbers in X, and your .each is being useless on the first example (that's also why it is an array of array, because '<<' inserts the element array inside your array, and not all the individual elements)
In your second example, your 'each' is in fact appending the elements to your array inside the block, so no "return" dependency. If you want to do it on single line, you would have to do
y.reject(&:empty?).each { |i| x << User.find(i) }
so it would do the calculation inside de block and if you want each loop to return the value inside the block you should use 'map' instead of 'each', and the 'concat' or '+' to sum the arrays (so it sums or concats each individual elements), so
x.concat(y.reject(&:empty?).map{ |i| User.find(i) })
OBS: As #tadman suggested on comment, I also removed the "bangs" for reject, as the 'reject!' method does not expect to always return all the values, check his comment for more information

Related

Getting data out of MatchData got as a result from match function

I have an array of custom objects in it. Those objects have a parameter called name which is a concatenation of 2 strings having a delimiter in between. Eg: name could be Some#Data where 'Some' is first string and 'Data' is another and # is a delimiter.
My intention is update the name parameter for all the objects inside the array such that the param would only have 'Data' (i.e. remove 'Some#') and store the objects inside another array after updating. Below is the code:
final_array = array1.select do |object|
object.name = object.name.match(/#(.*?)$/)
end
When I print object.name.match(/#(.*?)$/) this gives me output as:
#<MatchData "#Data" 1:"Data">
Out of this output, how do I get "Data" from this MatchData. I tried object.name.match(/#(.*?)$/)[1] but it didn't work. Or do I need to change my regex?
I would use #each and #gsub methods:
array.each do |object|
object.name = object.name.gsub(/^.+#/, '')
end

find_all elements in an array that match a condition?

I've an array of hash entries, and want to filter based on a paramater passed into the function.
If there are three values in the hash, A, B, and C, I want to do something similar to:
data = [{A:'a1', B:'b1', C:'c1'},
{A:'a1', B:'b2', C:'c1'},
{A:'a1', B:'b2', C:'c2'},
{A:'a2', B:'b1', C:'c1'},
{A:'a2', B:'b2', C:'c1'}]
data.find_all{ |d| d[:A].include?params[:A] }
.find_all{ |d| d[:B].include?params[:B] }
.find_all{ |d| d[:C].include?params[:C] }
find all where A == 'a1' AND B='b2'
so for above I get:
{A:'a1', B:'b2', C:'c1'} and {A:'a1', B:'b2', C:'c2'}
* put if / else statements, like params.has_key? 'B' then do something.
* modify my code each time a new type of value is added to the Hash map (say now I have 'D').
Note: The key of the hash is a symbol, but the value is a string and I want to do a "contains" not "equals".
I think of it as SQL Select statement with where zero or more '==' clause
If I understand your question correctly, then I believe that the select method of Array class may be helpful to you.
select takes a block of code which is intended to be a test condition on each element in your array. Any elements within your array which satisfy that test condition will be selected, and the result is an array of those selected elements.
For example:
arr = [ 4, 8, 15, 16, 23, 42 ]
result = arr.select { |element| element > 20 }
puts result # prints out [23, 42]
In your example, you have an array of hashes, which makes it only slightly more complicated than my example with a simple array of numbers. In your example, we have:
data = [{A:'a1', B:'b1', C:'c1'},
{A:'a1', B:'b2', C:'c1'},
{A:'a1', B:'b2', C:'c2'},
{A:'a2', B:'b1', C:'c1'},
{A:'a2', B:'b2', C:'c1'}]
I believe what you want your code to do is something like: Select from my data array all of the hashes where, within the hash, :A equals some value AND :B equals some other value.
Let's say you want to find all of the hashes where :A == 'a1' and :B == 'b2'. You would do that like this:
data.select { |hash_element| hash_element[:A] == 'a1' && hash_element[:B] == 'b2' }
This line returns to you an array with those hashes from your original data array which satisfy the condition we provided in the block - that is, those hashes where :A == 'a1' and :B == 'b2'. Hope that that helps shed some light on your problem!
More information about the select method here:
http://www.ruby-doc.org/core-2.1.0/Array.html#method-i-select
edited - below is an addition to original answer
To follow up on your later question about if/else clauses and the addition of new parameters... the block of code that you pass to select can, of course, be much more complicated than what I've written in the example. You just need to keep this in mind: If the last line of the block of code evaluates to true, then the element will be selected into the result array. Otherwise, it won't be selected.
So, that means you could define a function of your own, and call that function within the condition block passed to select. For example, something like this:
def condition_test(hash_element, key_values)
result = true
key_values.each do |pair|
if hash_element[pair[:key]] != pair[:value]
result = false
end
end
return result
end
# An example of the key-value pairs you might require to satisfy your select condition.
requirements = [ {:key => :A, :value => 'a1'},
{:key => :B, :value => 'b2'} ]
data.select { |hash_element| condition_test(hash_element, requirements) }
This makes your condition block a bit more dynamic, rather than hard-wired for :A == 'a1' and :B == 'b2' like we did in the earlier example. You can tweak requirements on the fly based on the keys and values that you need to look for. You could also modify condition_test to do more than just check to see if the hash value at some key equals some value. You can add in your if/else clauses in condition_test to test for things like the presence of some key, etc.
I think you want to use .values_at(key)
http://www.ruby-doc.org/core-2.1.0/Hash.html#method-i-values_atvalues_at method

What is the proper way to use methods that return an enumerator in Ruby array?

Many methods in Ruby array return an enumerator when invoked without parameters or blocks (index, keep_if, each, drop_while and many more).
When is it appropriate to use methods in this form, as opposed to calling them with a block?
From the docs to Enumerator:
Most methods have two forms: a block form where the contents are
evaluated for each item in the enumeration, and a non-block form which
returns a new Enumerator wrapping the iteration.
This allows you to chain Enumerators together. For example, you can
map a list’s elements to strings containing the index and the element
as a string via:
puts %w[foo bar baz].map.with_index {|w,i| "#{i}:#{w}" }
# => ["0:foo", "1:bar", "2:baz"]
An Enumerator can also be used as an external iterator. For example,
Enumerator#next returns the next value of the iterator or raises
StopIteration if the Enumerator is at the end.
e = [1,2,3].each # returns an enumerator object.
puts e.next # => 1
puts e.next # => 2
puts e.next # => 3
puts e.next # raises StopIteration
I'm sorry for copy-paste, but I couldn't explain better.
The main original reason for the Enumerator class to exist is method chaining:
array.each.with_object [[], []] { |element, memo| ... }
So basically, you don't need to worry about that.

In Ruby, can I pass each element of an array individually to a method that accepts *args?

Given a method that returns an array and another accepts an arbitrary number of arguments, is there a way to call the second method with each element of the array as an argument?
For example:
def arr
["a", "b", "c"]
end
def bar(*args)
args.each {|a| puts a}
end
I want to call
bar "a", "b" , "c"
Of course this is a simplified example, in reality arr could return an array of any size (say if it's an ActiveRecord find, and I want to pass all the results to bar), hence my problem.
You can do this:
my_array = ['a', 'b', 'c']
bar(*my_array)
This will flatten out the array into it's individual elements and pass them to the method as separate arguments. You could do this to any kind of method, not only ones that accept *args.
So in your case:
bar *arr
Use * also when you give an array as an argument:
bar(*arr)

What does the * (asterisk) symbol do near a function argument and how to use that in others scenarios?

I am using Ruby on Rails 3 and I would like to know what means the presence of a * operator near a function argument and to understand its usages in others scenarios.
Example scenario (this method was from the Ruby on Rails 3 framework):
def find(*args)
return to_a.find { |*block_args| yield(*block_args) } if block_given?
options = args.extract_options!
if options.present?
apply_finder_options(options).find(*args)
else
case args.first
when :first, :last, :all
send(args.first)
else
find_with_ids(*args)
end
end
end
This is the splat operator, which comes from ruby (and is thus not rails specific). It can be applied in two ways depending on where it is used:
to "pack" a number of arguments into an array
to split up an array into an argument list
In your function, you see the splat operator used in the function definition. The result is that the function accepts any number of arguments. The complete argument list will be put into args as an array.
def foo(*args)
args.each_with_index{ |arg, i| puts "#{i+1}. #{arg}" }
end
foo("a", "b", "c")
# 1. a <== this is the output
# 2. b
# 3. c
The second variant would be when you consider the following method:
def bar(a, b, c)
a + b + c
end
It requires exactly three arguments. You can now call this method like follows
my_array = [1, 2, 3]
bar(*my_array)
# returns 6
The splat applied in this case to the array will split it and pass each element of the array as an individual parameter to the method. You could do the same even by calling foo:
foo(*my_array)
# 1. 1 <== this is the output
# 2. 2
# 3. 3
As you can see in your example method, these rules do apply to block parameters in the same way.
This is a splat argument, which basically means that any 'extra' arguments passed to the method will all be assigned to *args.

Resources