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?
on 2012-11-22 13:52
on 2012-11-22 15:02
On Thu, Nov 22, 2012 at 1:52 PM, Steve Tucknott <lists@ruby-forum.com> wrote: > 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 2012-11-22 15:37
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.
on 2012-11-22 17:27
On Thu, Nov 22, 2012 at 8:37 AM, Steve Tucknott <lists@ruby-forum.com> wrote: > structure of item_rec in the list array. > > Attachments: > http://www.ruby-forum.com/attachment/7888/TodoItem.rb 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 2012-11-22 18:06
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.
on 2012-11-22 23:13
On Nov 22, 2012 11:07 AM, "Steve Tucknott" <lists@ruby-forum.com> wrote: > 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. > addr_rec.line1 = "C#{i+1}Line1" > 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. :) (and, crikey, i'm having fun, too :) )
on 2012-11-23 13:49
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.
on 2012-11-24 13:05
On Fri, Nov 23, 2012 at 6:49 AM, Steve Tucknott <lists@ruby-forum.com> wrote: > Thanks for all the advice. My pleasure. :) > 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 2012-11-24 18:02
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?
on 2012-11-25 03:33
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? Yes, and it's a doozy :) 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 2012-11-25 11:36
Far more elegant! I'll bolt that into my code and see what I get. Thanks again, Obi Wan.
Please log in before posting. Registration is free and takes only a minute.
Existing account
(Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
Log in with Google account | Log in with Yahoo account
No account? Register here.