Tip #17 - Struct Your Stuff!
May 13th, 2008
Ruby is a really dynamic language, and you can do a lot of cool things, one of them is a Struct (Structure) that allows you to make throw away objects that you can call methods on….
How do you use them? Simple!
1 2 3 4 5 |
Person = Struct.new(:name, :age, :job) bob = Person.new("John", 21, "Postman") bob.name #=> "John" bob.age #=> 21 bob.job #=> "Postman" |
“But wait!” you say, “That looks like a class!”, well, it sort of is, and you could do the same with:
1 2 3 4 5 6 7 8 9 10 11 12 |
class Person attr_accessor :name, :job, :age end bob = Person.new bob.name = "John" bob.age = 21 bob.job = "Postman" bob.name #=> "John" bob.age #=> 21 bob.job #=> "Postman" |
But it is more work :)
What is a struct useful for? Well, turning a return array of arrays, into a return array of objects. This is good if you are, say, parsing a delimited file.
Say you had comma delimited file of people, you only needed to handle this once to import the data into your real Ruby on Rails Person ActiveRecord class, but you needed to do some work on the imported data before you created the Person in the database permanently, this would be perfect for a Struct.
Now, I am ignoring the CSV and FasterCSV libraries here, to keep things simple.
Lets say the file looked like this:
1 2 3 4 5 |
# PeopleFile.csv BOB SMITH,21,FARMER,SYDNEY AUSTRALIA JOHN JAMES,43,TECH,AUCKLAND NEW ZEALAND SAM SMITHERS,99,TEACHER,BRISBANE AUSTRALIA JESSIE JAMES,14,SCHOOL KID,ADELAIDE AUSTRALIA |
Now, we can see that we have a list of people, the first field is the first and last name, the second is the age, the third is the job title and the last is the location.
Lets define a structure for this:
TempPerson = Struct.new(:first_name, :last_name, :age, :job, :city, :country) |
Ok, good. Now, we go ahead and split the file up:
1 2 3 4 5 6 7 8 9 10 11 12 |
people = Array.new File.read('PeopleFile.csv').each_line do |line| attrs = line.split(",") name = attrs[0].split(" ", 2) location = attrs[3].split(" ", 2) people << TempPerson.new(name[0], name[1], attrs[2], attrs[3], location[0], location[1]) end |
At the end of this, we have an array called people that is full of TempPerson objects, each that can be accessed by #name, #age etc.
We could then get a list of all the countries by:
people.map { |p| p.country } |
Which is a lot more readable than:
people.map { |p| p[3].split(" ", 2)[1] } |
And do all sorts of other goodness with this fully fledged object!
Then when you are finally ready to save this into your database:
1 2 3 4 5 6 7 8 |
people.each do |p| Person.create(:first_name => p.first_name, :last_name => p.last_name, :age => p.age, :job => p.job, :city => p.city, :country => p.country) end |
You can obviously do a lot more with Structs, this is just a start and I like my tips to be short and sweet… anyone else got some good uses for them?
blogLater
Mikel
May 14th, 2008 at 03:47 AM
Did you mean Person.new instead of MyStruct.new in your second code excerpt?
Keep up the good work! Ali
May 14th, 2008 at 08:27 AM
Ali, thanks, yes you were right, good catch, I fixed the example.
June 11th, 2008 at 10:06 AM
~> irb >> Person = Struct.new(:name, :age, :job) => Person >> bob = Person.new(:name => “John”, ?> :age => 21, ?> :job => “Postman”) => #<struct name="{:age=" person>21, :name=>”John”, :job=>”Postman”}, age=nil, job=nil> >> bob.name #=> “John” => {:age=>21, :name=>”John”, :job=>”Postman”} >> bob.age #=> 21 => nil >> bob.job #=> “Postman” => nil >>
what’s wrong?
June 11th, 2008 at 09:01 PM
Codesink, the problem is that somehow, between me writing the code and me putting it on the blog I must have had a mental lapse…
Structs don’t get initialized with hashes, each item has to get initialized like a method.
Very sorry about that, I have corrected the code above. You need to do:
Person(“John”, 21, “Postman”)
Regards
Mikel