My question is: Why does map change array? it should only create new.
map doesn't change the Array. But << changes the Strings in the Array.
See the documentation for String#<<:
str << obj → str
Append—Concatenates the given object to str.
Although it isn't mentioned explicitly, the code example clearly shows that << mutates its receiver:
a = "hello "
a << "world" #=> "hello world"
a.concat(33) #=> "hello world!"
It's strange, because when I'm using + operator in the block insted of << it works as expected.
+ doesn't change the Strings in the Array.
See the documentation for String#+:
str + other_str → new_str
Concatenation—Returns a new String containing other_str concatenated to str.
Note how it says "new String" and also the return value is given as new_str.
And my second question: Does Array#each change array itself or it only iterate over array and return itself?
Array#each does not change the Array. But of course, the block passed to Array#each may or may not change individual elements of the Array:
arr = %w[a b c]
arr.map(&:object_id) #=> an array of three large numbers
arr.each {|item| item <<'1' } #=> ['a1', 'b1', 'c1']
arr.map(&:object_id) #=> an array of the same three large numbers
As you can see, Array#each did not change the Array: it is still the same Array with the same three elements.