Arrays with records as objects

I am completely new to Ruby.
I have been reading through the tutorials and trying to build an example
set.

One example I saw was a basic todo list. I have then taken that example
and tried to modify it.

What I want is a file with two fields, action no, and text.
That file is read, and from each entry in the file, an 'item' object is
created that contains a record structure of status, action no and text.

That 'item' object is then loaded into a 'todo list' and the list
displayed on screen for the user to mark the completed actions.

I have then two objects - the 'todo list' and the 'todo item'. The item
has an initializer that takes the delimited file line (format action
no<<>>text) and splits that line into a structure that also has a
status.

My todo list has a list array. I want to load the list array with the
item objects and am using the << operator as:

@list << item_rec

This seems to work ok, and my array goes from 0 length to 1. But, I had
hoped then to access the record fields via 'list', ie:
list[0].action
but when I try to access the 'list[n].action' I seem to have nil in the
field(s),although looking at 'list' in total seems to show the data.
List is ([#<TodoItem:0x8379f30 @item_rec=#<OpenStruct done=false,
action="1", name="hgafafgafgagaf">>])

Is it because I have an array of 'objects' - ie 'list' now contains a
single element of class 'TodoItem'? If so, how do I access the
'item_rec' sub object of 'list'?

Does any of that make sense?

···

--
Posted via http://www.ruby-forum.com/.

My todo list has a list array. I want to load the list array with the
item objects and am using the << operator as:

@list << item_rec

This seems to work ok, and my array goes from 0 length to 1. But, I had
hoped then to access the record fields via 'list', ie:
list[0].action
but when I try to access the 'list[n].action' I seem to have nil in the
field(s),although looking at 'list' in total seems to show the data.
List is ([#<TodoItem:0x8379f30 @item_rec=#<OpenStruct done=false,
action="1", name="hgafafgafgagaf">>])

With that structure you rather need to do

list[0].item_rec.action

don't you? Can you share the code of class TodoItem?

Is it because I have an array of 'objects' - ie 'list' now contains a
single element of class 'TodoItem'? If so, how do I access the
'item_rec' sub object of 'list'?

See above.

Kind regards

robert

···

On Thu, Nov 22, 2012 at 1:52 PM, Steve Tucknott <lists@ruby-forum.com> wrote:

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

Robert

The TodoItem class is as attached.

I'm a complete novice, so I'm not sure how structures are used in Ruby -
I had tried a simple case of '..build record...put in array' in another
example where I built a local record and appended that to a local array,
and then I accessed the array fields by a simple array[n].field1. So I
stupidly assumed that the similar '@list << item_rec' would append the
structure of item_rec in the list array.

Attachments:
http://www.ruby-forum.com/attachment/7888/TodoItem.rb

···

--
Posted via http://www.ruby-forum.com/.

Robert,
Thanks for the informative replies - very much appreciated.

I am 'playing'/'learning' at the moment - so what I have been doing is
taking base examples and then adding how I could use them in the 'real
world'.

In an earlier array example, I had used the record Openstruct structure
as:
            # -- This is my test to see how arrays can be indexed.....a
five element array
            # -- for names and address/post code
            addr = []

            for i in 0..5
                addr_rec = OpenStruct.new
                addr_rec.name = "Cust#{i+1}"
                addr_rec.line1 = "C#{i+1}Line1"
                addr_rec.line2 = "C#{i+1}Line2"
                addr_rec.line3 = "C#{i+1}Line3"
                addr_rec.line4 = "C#{i+1}Line4"
                addr_rec.pcode = "C#{i+1}PCode"
                addr[i] = addr_rec
            end

            for i in 0..(addr.length-1)
                `zenity --info --title="My Array" --text="Each Array
Element:\n#{addr[i].name}\n#{addr[i].line1}\n#{addr[i].line2}\n#{addr[i].line3}\n#{addr[i].line4}\n#{addr[i].pcode}"`
            end

So, because that seemed to work ok, I then wanted to try to represent
the TodoList file line contents as a 'record' (in general, that is how
I'm used to handling file data). Hence then getting the 'line' record
into the array.

In between postings though, I did remove the record structure, and made
the item three separate instance variables again.
So I think I'll create two versions of this - the single variables and
record structure options.

All the STDOUTs are my debug lines (as well as the main zenity system
calls) to see what is going on.

The 'toggle' on the status is driven by a zenity window - a checkbox
just marks 'Done' - but I see what you mean.

Thanks for the info on the ternary operator - and I'll look into logger
calls and create an example for that!

Is there a separate forum that you know of for complete Ruby newbies
like me?

Thanks again for your help.

···

--
Posted via http://www.ruby-forum.com/.

Thanks for all the advice.

Bear in mind though, that in some cases (not all!) the poor coding was
simply because I cut and pasted a line (ie the build of addr_rec) - I
wouldn't write code like that in working code. I'll make notes though to
the best practice in my test examples (actually it's now one example as
I thought I'd bolt all the examples into one module and use arguments
and a case to drive it - just to see how arguments and case worked! -
once I know how to create libraries, I'll split them back out again!)

What I've found odd so far is that Ruby appears to have multiple ways of
doing the same thing - so as you say, I could write:
line[i] = some_rec
or
line << some_rec
(assuming the index points to the next empty slot)

..and the examples don't appear (so far) to explain the why's and
where's and then best practices.

Ditto for iteration.

All this is my fault , as I was up to array handling, and then googled
something I didn't quite follow - and ended up at a site with a 'todo'
example that I thought would be good as a mini test module. So I jumped
ahead of myself and started faffing around with that and then hit
records in arrays etc.

Such is the learning curve.

Thanks for your patience.

···

--
Posted via http://www.ruby-forum.com/.

So far so good.
I now have my 'Tutorial' test module working from a looping case, with
sub options (based mainly on the examples at rubylearning.com, and my To
Do list).

The ToDo list (that now has a action number (as a reversed date) and
text) stored in a file - with the actions being updateable.

I did have one further issue with my list array - when selecting from a
Zenity list, Zenity returns a comma separated list of column entries.
In my case, my new 'action no' was the column(s) returned. I could then
split the returned string to get the individual action numbers - but I
couldn't find an elegant way of then getting which entry in the list
array matched that action no to mark it as done.
I did see there was an 'index' method, but I could see how that worked
with a multi-column array (my list has columns of done, action no and
text) - so I wanted to do a find like (pseudo code):
list.action.find("20121123110100")
..that code failed (errored) and list.find("valid value") returned nil.

I got a working example by iterating over the list array and manually
comparing the list[n].action to the Zenity returned values.

Is there a way of 'finding' (and getting the index value of the array
line) a value in a specific column in a multi-column array that you
know of?

···

--
Posted via http://www.ruby-forum.com/.

Far more elegant!
I'll bolt that into my code and see what I get.

Thanks again, Obi Wan.

···

--
Posted via http://www.ruby-forum.com/.

Steve,

I'm probably not understanding what you're trying to do here, but why
do you use the OpenStruct class here?

I think the question of how to access an anonymous object in an array
is still an interesting one. I think you have to give @item_rec
accessors (maybe just attr_reader? not sure). The attr_accessor's you
have set for :done, :action, and :name define instance variables
directly in TodoItem, but then you create a new object @item_rec and
assign instance variables to it. This results is your class really
having 4 instance variables, not 3, only 3 of which aren't being used:

TodoItem instance vars:
@done - not used by class
@action - not used by class
@name - not used by class
@item_rec - this is used

The object @item_rec points to has 3 instance variables:
@done
@action
@name

So, if you replace the attr_accessor line with:

attr_accessor :item_rec

Then, in your outer code, you can refer to it with the list as:

@list[0].item_rec.name

Still, unless this is an exercise in using OpenStruct, it's quite unnecessary.

Instead, you can simply do this:

class TodoItem
    attr_accessor :done, :action, :name
    alias_method :done?, :done

    def initialize(line)
        # Create a record of state, action and name with state (done)
set to false
        # set state to undone
        @done = false
        @action, @name = line.split("<<>>")
  # Generally not a good idea to have object print out internal state like this
  # It can be useful for debugging, but better to define a
  # to_s method that will format it how you like it
        # STDOUT.puts "New TodoItem - #{@item_rec}"
  # if this really is debugging info, consider using a
  # logger instead of tossing it to STDOUT
    end

    # maybe this is personal, but I'd call this state?
    # also, look at the ternary operator. the following could be written
    # @done ? "Done" : "Active"
    def state_getter
        if @done
            "Done"
        else
            "Active"
        end
    end

    # You might consider a more descriptive set of methods
    # that say what is happening, such as "complete" and "undo"
    # rather than the single method to flip the value.
    # flipping rather depends on the current state, obviously,
    # but it thus requires proper knowlege of the current
    # state to determine what to do. methods like "complete" and
    # "undo" state more clearly their intentions.
    def state_setter
        STDOUT.puts "Setter Status Was ("+@item_rec.done+") for
("+@item_rec.name+')'
        @item_rec.done = !@item_rec.done
        STDOUT.puts "Setter Status Now ("+@item_rec.done+") for
("+@item_rec.name+')'
    end

end

···

On Thu, Nov 22, 2012 at 8:37 AM, Steve Tucknott <lists@ruby-forum.com> wrote:

Robert

The TodoItem class is as attached.

I'm a complete novice, so I'm not sure how structures are used in Ruby -
I had tried a simple case of '..build record...put in array' in another
example where I built a local record and appended that to a local array,
and then I accessed the array fields by a simple array[n].field1. So I
stupidly assumed that the similar '@list << item_rec' would append the
structure of item_rec in the list array.

Attachments:
http://www.ruby-forum.com/attachment/7888/TodoItem.rb

I am 'playing'/'learning' at the moment - so what I have been doing is
taking base examples and then adding how I could use them in the 'real
world'.

This is great!! Experimenting with stuff that your learning is a great
thing, and no way would I want to discourage that. Checking in with
what your learning is a great thing, too.

In an earlier array example, I had used the record Openstruct structure
as:
            # -- This is my test to see how arrays can be indexed.....a
five element array
            # -- for names and address/post code
            addr =

            for i in 0..5
                addr_rec = OpenStruct.new
                addr_rec.name = "Cust#{i+1}"
                addr_rec.line1 = "C#{i+1}Line1"
                addr_rec.line2 = "C#{i+1}Line2"
                addr_rec.line3 = "C#{i+1}Line3"
                addr_rec.line4 = "C#{i+1}Line4"
                addr_rec.pcode = "C#{i+1}PCode"
                addr[i] = addr_rec
            end

            for i in 0..(addr.length-1)
                `zenity --info --title="My Array" --text="Each Array
Element:\n#{addr[i].name}\n#{addr[i].line1}\n#{addr[i].line2}\n#{addr[i].line3}\n#{addr[i].line4}\n#{addr[i].pcode}"`
            end

So, because that seemed to work ok, I then wanted to try to represent
the TodoList file line contents as a 'record' (in general, that is how
I'm used to handling file data). Hence then getting the 'line' record
into the array.

It does work, yet it might not be the best way to do things in the
ruby world. Looking at the above, it seems to be borrowing idioms from
other 'less expressive' languages than ruby.

For instance, for loops are almost never needed in ruby. To approach a
more idiomatic ruby expression of the above, you would use the "each"
method:

0..5.each do |i| # a range creates an iterator,
usable by each
  addr_rec = OpenStruct.new
  cnum = i+1 # calculating the "customer number" once
                                        # rather than recalculating it for each
                                        # field below
  addr_rec.name = "Cust#{cnum}"
  addr_rec.line1 = "C#{cnum}Line1"
  addr_rec.line2 = "C#{cnum}Line2"
  addr_rec.line3 = "C#{cnum}Line3"
  addr_rec.line4 = "C#{cnum}Line4"
  addr_rec.pcode = "C#{cum}PCode"
  addr[i] = addr_rec
  # this could also be written as:
  # addr << addr_rec
end

addr.each do |a| # an array also has an
iterator, also usable by each
  `zenity --info --title="My Array" --text="Each Array
Element:\n#{a.name}\n#{a.line1}\n#{a.line2}\n#{a.line3}\n#{a.line4}\n#{a.pcode}"`
end

Note the comment about calculating cnum; in general it's best to
calculate a local constant once, rather than several times. It
wouldn't surprise me if the above was optimized away under the hood,
but it's best not to rely on such things, and it's more expressive of
what you mean by giving it it's own variable name

In between postings though, I did remove the record structure, and made
the item three separate instance variables again.
So I think I'll create two versions of this - the single variables and
record structure options.

All the STDOUTs are my debug lines (as well as the main zenity system
calls) to see what is going on.

Another way to approach this, rather than inserting debug statements
you will later have to remove from code if you're going to use it in
an application, is to follow a technique known as Test Driven
Development. The ruby community has, more than any other I've
encountered, embraced testing as a way of life, and the best practice.
The idea that you write down how you are going to test something helps
tremendously in figuring out what you want the code to do. While
you're experimenting and just trying things out, it can seem like a
bit of a hindrance, yet establishing the habit early can be quite
beneficial.

Essentially, with the TodoItem class, instead of printing out the
debug statements, you could write test code that verified the
behaviour of the methods. There are some great ruby test gems, a lot
of people use RSpec because of the neat way you write tests, but even
a "poor man's" test program is possible, and in just starting out,
maybe more helpful.

In a test script, making a call like:

item = TodoItem.new("1<<>>a new item")

You can then debug/test that item was created successfully, doing
something like:

puts "done = #{item.done}"
puts "action = #{item.action}"
puts "name = #{item.name}"
puts "state is = #{item.state_getter}"
item.state_setter
puts "state is now #{item.state_getter}"

Also, if you haven't, do try out the ruby console, irb. You can
quickly throw ruby expressions like this at it and see what happens.

Is there a separate forum that you know of for complete Ruby newbies
like me?

Here is good -- we all learn from each other!

There are lots of great resources out there, but I think interacting
with real people is great way to learn the subtler nuances of things,
such as the each method if you're coming from a standard procedure
programming approach; in learning to use a language, the idioms that
are best used in that language are sometimes not that apparent. That's
where knocking heads with others helps a lot.

I'm also firmly of the belief that you can't *really* master something
unless you can teach it. So do me a favour and stick around. :slight_smile:

(and, crikey, i'm having fun, too :slight_smile: )

···

On Nov 22, 2012 11:07 AM, "Steve Tucknott" <lists@ruby-forum.com> wrote:

Thanks for all the advice.

My pleasure. :slight_smile:

Bear in mind though, that in some cases (not all!) the poor coding was
simply because I cut and pasted a line (ie the build of addr_rec) - I
wouldn't write code like that in working code.

That was understood -- I was merely trying to point out that such
things do leak into "real" code. Even when we're just learning, or
even playing around, it can help to keep the "discipline" if you will.
But I'm glad you see the difference!

I'll make notes though to
the best practice in my test examples (actually it's now one example as
I thought I'd bolt all the examples into one module and use arguments
and a case to drive it - just to see how arguments and case worked! -
once I know how to create libraries, I'll split them back out again!)

What I've found odd so far is that Ruby appears to have multiple ways of
doing the same thing

Nearly every language I've come across this is true. The richness of
expression is a good thing, in my view.

- so as you say, I could write:
line[i] = some_rec
or
line << some_rec
(assuming the index points to the next empty slot)

..and the examples don't appear (so far) to explain the why's and
where's and then best practices.

It's somewhat a matter of taste, sometimes a real boon to programming.

The latter is the classic "push" type of operation -- very useful when
you just want to append without knowing the current end of the array.

Ditto for iteration.

All this is my fault , as I was up to array handling, and then googled
something I didn't quite follow - and ended up at a site with a 'todo'
example that I thought would be good as a mini test module. So I jumped
ahead of myself and started faffing around with that and then hit
records in arrays etc.

I wouldn't consider it a fault -- as in laying blame or guilt or
anything like that. We all have our own learning style, and our own
way of approaching things. Jumping around can be useful, or you can
miss something, but it all works out, eventually.

Such is the learning curve.

Thanks for your patience.

--
Posted via http://www.ruby-forum.com/\.

I really hope I'm not discouraging you. This is actually fun stuff.
Take my comments for what they are, merely trying to help you up the
curve a bit. The best part about free advice is you don't have to take
it.

···

On Fri, Nov 23, 2012 at 6:49 AM, Steve Tucknott <lists@ruby-forum.com> wrote:

Yes, and it's a doozy :slight_smile:

if you know the action number, say "20121123110100" in your example:

list.index { |l| l.action == "20121123110100" } # => array index

yields the index in the array list that matches the first item.

The "{..}" is a block, which is a group of code. This group is only
one statement, the test for equality. the "|l|" defines a paramter to
use in the block of code. The method "index" operates on the block of
code like the "each" method seen earlier, but in this case, instead of
just iterating, it returns the array index where the block returns a
true value.

Now, if instead of the array index, you wanted the actual object, you
would use the "select" method:

list.select { |l| l.action == "20121123110100" } # => {TodoItem object}

···

On Sat, Nov 24, 2012 at 11:02 AM, Steve Tucknott <lists@ruby-forum.com> wrote:

Is there a way of 'finding' (and getting the index value of the array
line) a value in a specific column in a multi-column array that you
know of?