Strange thing w/ array.map


#1

Hello,

I'm writing a method that builds rss and atom feeds and I've noticed

something strange when I try updating the content array by using a map.

I’m new to ruby and must confess that I don’t know how array maps work
so well.

I am formatting some of the rss content array by updating it’s contents
w/ a map like so:

@entities = Space.find :all

@new_entities = @entities.map do |entity|
  open_closed = entity.complete? ? "[x] " : "[ ] "
  entity.title = open_closed + entity.title
  entity.title += (' due on ' + local_date_time(entity.due_on)) if
entity.due_on
  entity.title += " (#{entity.status_title})"
end if @entities

then when I try to use the @new_entities array in the feed, I get an
error

for entity in @new_entities  #-->>error!
  xml.item do
    xml.title(entity.title)
    xml.description(entity.description)
    xml.pubDate(entity.date.strftime("%a, %d %b %Y %H:%M:%S %z"))
    xml.link("http://www.recentrambles.com/pragmatic/view/" +
entity.id.to_s)
    xml.guid("http://www.recentrambles.com/pragmatic/view/" +
entity.id.to_s)
  end
end

but, if I use the original @entities array it works perfectly

for entity in @entities   #-->>works!
  xml.item do
    xml.title(entity.title)
    xml.description(entity.description)
    xml.pubDate(entity.date.strftime("%a, %d %b %Y %H:%M:%S %z"))
    xml.link("http://www.recentrambles.com/pragmatic/view/" +
entity.id.to_s)
    xml.guid("http://www.recentrambles.com/pragmatic/view/" +
entity.id.to_s)
  end
end

How, exactly, is that @entities array getting updated by the map?

Thanks for your help!


#2

2 things are going on here

  1. i don’t believe you are getting back what you think you are getting
    back from .map
  2. the entity you are working with in your block is the actual entity,
    not a copy. so you are basically overwriting your original array of
    entities

.map returns a new array based on whatever it is you do in within the
block

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

in your case the last statement in the block is

entity.title += " (#{entity.status_title})"

so your block is going to return whatever that evaluates to, not the
entity object you are expecting.

in addition, your block is actually overwriting all the values of the
@entities objects you are passing in. if you were to compare the
titles of the @entities objects before and then after .map is called,
you’re going to see that they are different.

ex:

class Thing
attr_accessor :foo
end

a = Thing.new
a.foo = “abc”
b = Thing.new
b.foo = “xyz”

x = [a,b].map { |c| c.foo += " modified" }
a.foo # => “abc modified”
b.foo # => “xyz modified”
x = [“abc modified”, “xyz modified”] # you expected this to be an
array of Thing objects

if you do want to overwrite the attributes of your objects, just use
.each

@entities.each do |entity|

end


#3

Chris,

Thanks for your help - this is great.

Ruby does things I never expect - in the best of ways.

Cheers!

Chris H. wrote:

2 things are going on here

  1. i don’t believe you are getting back what you think you are getting
    back from .map
  2. the entity you are working with in your block is the actual entity,
    not a copy. so you are basically overwriting your original array of
    entities

.map returns a new array based on whatever it is you do in within the
block

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

in your case the last statement in the block is

entity.title += " (#{entity.status_title})"

so your block is going to return whatever that evaluates to, not the
entity object you are expecting.

in addition, your block is actually overwriting all the values of the
@entities objects you are passing in. if you were to compare the
titles of the @entities objects before and then after .map is called,
you’re going to see that they are different.

ex:

class Thing
attr_accessor :foo
end

a = Thing.new
a.foo = “abc”
b = Thing.new
b.foo = “xyz”

x = [a,b].map { |c| c.foo += " modified" }
a.foo # => “abc modified”
b.foo # => “xyz modified”
x = [“abc modified”, “xyz modified”] # you expected this to be an
array of Thing objects

if you do want to overwrite the attributes of your objects, just use
.each

@entities.each do |entity|

end