Ross Attrill
Ross Attrill

Reputation: 2702

Pushing to an array not working as expected

When I execute the code below, my array 'tasks' ends up with the same last row from the dbi call repeated for each row in the database.

require 'dbi'
require 'PP'

dbh = DBI.connect('DBI:SQLite3:test', 'test', '')

dbh.do("DROP TABLE IF EXISTS TASK;")
dbh.do("CREATE TABLE TASK(ID INT, NAME VARCHAR(20))")   

# Insert two rows
1.upto(2) do |i|
    sql = "INSERT INTO TASK (ID, NAME) VALUES (?, ?)"
    dbh.do(sql, i, "Task #{i}")
end

sth = dbh.prepare('select * from TASK')
sth.execute

tasks = Array.new

while row=sth.fetch do
    p row
    p row.object_id
    tasks.push(row)
end

pp(tasks)

sth.finish

So if I have two rows in my TASK table, then instead of getting this in the tasks array:

[[1, "Task 1"], [2, "Task 2"]]

I get this

[[2, "Task 2"], [2, "Task 2"]]

The full output looks like this:

[1, "Task 1"]
19877028
[2, "Task 2"]
19876728
[[2, "Task 2"], [2, "Task 2"]]

What am I doing wrong?

Upvotes: 2

Views: 1683

Answers (4)

Ismael
Ismael

Reputation: 16720

It seems there are some strange behavior in row objects wich seems to be some kind of singleton, and that's why dup method wont solve it.

Jumping into the source code it seems that the to_a method will duplicate the inner row elements and that's why it works so the answer is to use to_a on the row object or if you want you can also transform it into a Hash to preserve meta.

while row=sth.fetch do
  tasks.push(row.to_a)
end

But I recommend the more ruby way

sth.fetch do |row|
  tasks << row.to_a
end

Upvotes: 2

m_x
m_x

Reputation: 12554

Are you sure you have copied your code exactly as it is ? AFAIK the code you have written shouldn't work at all... You mix two constructs that are not intended to be used that way.

Am i wrong to assume that you come from a C or Java background ? Iteration in ruby is very different, let me try to explain.

A while loop in ruby has this structure :

while condition
  # code to be executed as long as condition is true
end

A method with a block has this structure :

sth.fetch do |element|
  # code to be executed once per element in the sth collection
end

Now there something really important to understand : fetch, or any other method of this kind in ruby, is not an iterator as you would encounter in C for example - you do not have to call it again an again until the iterator hits the end of the collection.

You just call it once, and give it a block as argument, which is a kind of anonymous function (as in javascript). The fetch method will then pass ("yield") each element of the collection, one after another, to this block.

So the correct syntax in your case should be :

sth.fetch do |row|
  p row
  tasks.push row
end

which could be otherwise written like this, in a more "old school" fashion :

# define a function
# = this is your block
def process( row )
  p row
  tasks.push row
end

# pass each element of a collection to this function
# = this is done inside the fetch method
for row in sth
  process row
end

I would advise you to read more on blocks / procs / lambdas, because they are all over the place in ruby, and IMHO are one of the reasons this language is so awesome. Iterators is just the beginning, you can do a LOT more with these...If you need good reference docs, the pickaxe is considered one of the best sources among rubyists, and i can tell you more if you want.

Upvotes: 1

Ismael
Ismael

Reputation: 16720

There are so many things that can be happening but try this.

First ensuring the block is passed to the while using parens.

while (row=sth.fetch) do
    p row
    tasks.push(row)
end

Then the idiomatic ruby way

sth.fetch do |row|
    p row
    tasks << row # same as push
end

Upvotes: 0

sawa
sawa

Reputation: 168081

I don't know how your code works entirely, but I guess if you change tasks.push(row) into tasks.push(row.dup), then it shall work. If that is the case, then sth.fetch keeps giving you the same array (same object id) each time even if its content is renewed, and you are pushing the same array into tasks repeatedly.

Upvotes: 0

Related Questions