Ruby Map Method Syntax Question

What does map(&:name) mean in Ruby?

It's shorthand for tags.map(&:name.to_proc).join(' ')

If foo is an object with a to_proc method, then you can pass it to a method as &foo, which will call foo.to_proc and use that as the method's block.

The Symbol#to_proc method was originally added by ActiveSupport but has been integrated into Ruby 1.8.7. This is its implementation:

class Symbol
def to_proc
Proc.new do |obj, *args|
obj.send self, *args
end
end
end

Can you supply arguments to the map(&:method) syntax in Ruby?

You can create a simple patch on Symbol like this:

class Symbol
def with(*args, &block)
->(caller, *rest) { caller.send(self, *rest, *args, &block) }
end
end

Which will enable you to do not only this:

a = [1,3,5,7,9]
a.map(&:+.with(2))
# => [3, 5, 7, 9, 11]

But also a lot of other cool stuff, like passing multiple parameters:

arr = ["abc", "babc", "great", "fruit"]
arr.map(&:center.with(20, '*'))
# => ["********abc*********", "********babc********", "*******great********", "*******fruit********"]
arr.map(&:[].with(1, 3))
# => ["bc", "abc", "rea", "rui"]
arr.map(&:[].with(/a(.*)/))
# => ["abc", "abc", "at", nil]
arr.map(&:[].with(/a(.*)/, 1))
# => ["bc", "bc", "t", nil]

And even work with inject, which passes two arguments to the block:

%w(abecd ab cd).inject(&:gsub.with('cde'))
# => "cdeeecde"

Or something super cool as passing [shorthand] blocks to the shorthand block:

[['0', '1'], ['2', '3']].map(&:map.with(&:to_i))
# => [[0, 1], [2, 3]]
[%w(a b), %w(c d)].map(&:inject.with(&:+))
# => ["ab", "cd"]
[(1..5), (6..10)].map(&:map.with(&:*.with(2)))
# => [[2, 4, 6, 8, 10], [12, 14, 16, 18, 20]]

Here is a conversation I had with @ArupRakshit explaining it further:

Can you supply arguments to the map(&:method) syntax in Ruby?


As @amcaplan suggested in the comment below, you could create a shorter syntax, if you rename the with method to call. In this case, ruby has a built in shortcut for this special method .().

So you could use the above like this:

class Symbol
def call(*args, &block)
->(caller, *rest) { caller.send(self, *rest, *args, &block) }
end
end

a = [1,3,5,7,9]
a.map(&:+.(2))
# => [3, 5, 7, 9, 11]

[(1..5), (6..10)].map(&:map.(&:*.(2)))
# => [[2, 4, 6, 8, 10], [12, 14, 16, 18, 20]]

Here is a version using Refinements (which is less hacky than globally monkey patching Symbol):

module AmpWithArguments

refine Symbol do
def call(*args, &block)
->(caller, *rest) { caller.send(self, *rest, *args, &block) }
end
end

end

using AmpWithArguments

a = [1,3,5,7,9]
a.map(&:+.(2))
# => [3, 5, 7, 9, 11]

[(1..5), (6..10)].map(&:map.(&:*.(2)))
# => [[2, 4, 6, 8, 10], [12, 14, 16, 18, 20]]

What does the map method do in Ruby?

The map method takes an enumerable object and a block, and runs the block for each element, outputting each returned value from the block (the original object is unchanged unless you use map!):

[1, 2, 3].map { |n| n * n } #=> [1, 4, 9]

Array and Range are enumerable types. map with a block returns an Array. map! mutates the original array.

Where is this helpful, and what is the difference between map! and each? Here is an example:

names = ['danil', 'edmund']

# here we map one array to another, convert each element by some rule
names.map! {|name| name.capitalize } # now names contains ['Danil', 'Edmund']

names.each { |name| puts name + ' is a programmer' } # here we just do something with each element

The output:

Danil is a programmer
Edmund is a programmer

Emulating the each and map method without using each and map. Explanation on the mechanics of the code needed

I think your explanation is correct and you do have the right idea.

The &proc and proc.call syntax is fine, but you could alternately write it using yield like so:

def my_each
i = 0
while i < self.count
yield self[i]
i += 1
end
self
end

def my_map
result = []
my_each { |el| result << yield(el) }
result
end

Should this use the map method?

Whether to use map or each depends totally on whether or not you want the operation to return a list of the results of the application of the method (the send) or not.

`return` in Ruby Array#map

You don't need the variable. Return value of the block is value of last expression evaluated in it. In this case, the if.

def some_method(array)
array.map do |x|
if x > 10
x+1
else
x-1
end
end
end

Ternary operator would look nicer, I think. More expression-ish.

def some_method(array)
array.map do |x|
(x > 10) ? x+1 : x-1
end
end

If you insist on using return, then you could use lambdas. In lambdas, return behaves like in normal methods.

def some_method(array)
logic = ->(x) {
if x > 10
return x + 1
else
return x - 1
end
}
array.map(&logic)
end

This form is rarely seen, though. If your code is short, it surely can be rewritten as expressions. If your code is long and complicated enough to warrant multiple exit points, then probably you should try simplifying it.

In Ruby, is there an Array method that combines 'select' and 'map'?

I usually use map and compact together along with my selection criteria as a postfix if. compact gets rid of the nils.

jruby-1.5.0 > [1,1,1,2,3,4].map{|n| n*3 if n==1}    
=> [3, 3, 3, nil, nil, nil]

jruby-1.5.0 > [1,1,1,2,3,4].map{|n| n*3 if n==1}.compact
=> [3, 3, 3]

What is map(&:id) in Rails?

1 )
You should read some tutorials on map to get acquainted.
https://www.rubyguides.com/2018/10/ruby-map-method

But the short answer is that running user.cnae_classifications.map(&:id) will loop over all cnae_classifications and extract the id from them and put them all into an array. The & character allows for map shorthand to avoid passing an entire block.

From the link above:
Ruby Map Method Diagram

2 )
The #create method can accept a key-value hash of known attributes (known to the class in question, in this case that is UserCnaeClassification) to assign upon creation. So you're basically right, they are key-value pairs but they are specific to this class/object. Those same keys might not work on another class/object.
Additional reading: https://guides.rubyonrails.org/active_record_basics.html#create

Passing custom method from custom class to .map

First of all: you can use array.sample instead of array[rand(x)] to get a random element from an array.

Furthermore, you can replace (1..n).map by n.times.map:

(2.times.map { rand_letters } + 3.times.map { rand_numbers }).join

But using map feels counter intuitive to me. Wouldn't it be much easier if we could simply grab 3 random letters?

This can be achieved by returning an Enumerator instead of a single value. The plural naming already suggest that the rand_ methods are supposed to return multiple values:

def rand_letters
return enum_for(__method__) unless block_given?
loop { yield ('A'..'Z').to_a.sample }
end

def rand_numbers
return enum_for(__method__) unless block_given?
loop { yield (0..9).to_a.sample }
end

This allows us to call Enumerable#take:

(rand_letters.take(2) + rand_numbers.take(3)).join
#=> "ZJ010"

Or using the [...] literal:

[rand_letters.take(2), rand_numbers.take(3)].join


Related Topics



Leave a reply



Submit