Iterating through class names using a block

G

Ge Bro

Hey all,
this is a total newbie question, i'm just starting to learn Ruby.

here's the thing: i've got a couple of arrays with several items in
each. I'd like to use a block to iterate through each array, and create
objects of the class that is named the same as the array.

This is supposed to populate a database where tables are named after
arrays, and each table contains 1 column called "item_name" with items
from the corresponding array.

allow me to illustrate:

###

some_item = ['foo', 'bar']
other_item = ['alpha', 'bravo', 'foxtrot', 'zebra']

['some_item', 'other_item'].each do |this_array|
array_length = 0
this_object = this_array.to_s.capitalize #<== here's the problem
until array_length == this_array.length
this_object.new:)item_name => this_array[array_length])
array_length += 1
end
end

###

the problem is, this_object becomes a string with the value of
|this_array|. How do I explain to the program that its value is not just
a string, but actually a name of the class, an instance of which i'm
trying to initialize?

Forgive the dumb question, but i'm just starting out. I've been banging
my head against this for hours.... any help would be appreciated...

thanks!
 
P

Phrogz

this_object = this_array.to_s.capitalize #<== here's the problem

Search the archives (this is not just a forum on a website, but a
mailing list and newsgroup) for posts like string name to class. The
simple answer involves const_get. This is a FAQ.
 
P

Phrogz

Search the archives (this is not just a forum on a website, but a
mailing list and newsgroup) for posts like string name to class. The
simple answer involves const_get. This is a FAQ.

Oh, and...welcome to Ruby :)
 
7

7stud --

Ge said:
['some_item', 'other_item'].each do |this_array|
..
this_object = this_array.to_s.capitalize #<== here's the problem
the problem is, this_object becomes a string with the value of
|this_array|.

This loop:

['some_item', 'other_item'].each do |this_array|

says to take each element in the array and stuff it in the variable
this_array. The array is that thing surrounded by brackets. Since each
element of the array is a string--that's what the quotes mean--those
strings get stuffed into the variable this_array in turn. For example,
the first time through the loop, the string 'some_item' will get stuffed
into the variable this_array.

Inside the loop, you tell ruby to convert the string stored in
this_array, which is 'some_item', into a string, but the string
'some_item' is already a string, so the to_s method call doesn't do
anything. Then you say to capitalize the string 'some_item', which
gives you: 'Some_item', and then you assign 'Some_item to this_object.
the problem is, this_object becomes a string with the value of
|this_array|. How do I explain to the program that its value is not just
a string, but actually a name of the class

'Some_item' is the name of a class? Where is the 'Some_item' class
defined in your code? For that matter, where is any class defined in
your code?
 
D

dusty

Hey all,
this is a total newbie question, i'm just starting to learn Ruby.

here's the thing: i've got a couple of arrays with several items in
each. I'd like to use a block to iterate through each array, and create
objects of the class that is named the same as the array.

This is supposed to populate a database where tables are named after
arrays, and each table contains 1 column called "item_name" with items
from the corresponding array.

allow me to illustrate:

###

some_item = ['foo', 'bar']
other_item = ['alpha', 'bravo', 'foxtrot', 'zebra']

['some_item', 'other_item'].each do |this_array|
array_length = 0
this_object = this_array.to_s.capitalize #<== here's the problem
until array_length == this_array.length
this_object.new:)item_name => this_array[array_length])
array_length += 1
end
end

###

the problem is, this_object becomes a string with the value of
|this_array|. How do I explain to the program that its value is not just
a string, but actually a name of the class, an instance of which i'm
trying to initialize?

Forgive the dumb question, but i'm just starting out. I've been banging
my head against this for hours.... any help would be appreciated...

thanks!

Something like this will work.

class Foo; end
class Bar; end
arr = ['foo','bar','bad']
out = arr.collect {|a| Object.const_get(a.capitalize) rescue nil }
out.compact!

See http://www.ruby-doc.org/core/classes/Array.html

Look for collect and compact! to see what I'm doing there.
 
D

dusty

Hey all,
this is a total newbie question, i'm just starting to learn Ruby.
here's the thing: i've got a couple of arrays with several items in
each. I'd like to use a block to iterate through each array, and create
objects of the class that is named the same as the array.
This is supposed to populate a database where tables are named after
arrays, and each table contains 1 column called "item_name" with items
from the corresponding array.
allow me to illustrate:

some_item = ['foo', 'bar']
other_item = ['alpha', 'bravo', 'foxtrot', 'zebra']
['some_item', 'other_item'].each do |this_array|
array_length = 0
this_object = this_array.to_s.capitalize #<== here's the problem
until array_length == this_array.length
this_object.new:)item_name => this_array[array_length])
array_length += 1
end
end

the problem is, this_object becomes a string with the value of
|this_array|. How do I explain to the program that its value is not just
a string, but actually a name of the class, an instance of which i'm
trying to initialize?
Forgive the dumb question, but i'm just starting out. I've been banging
my head against this for hours.... any help would be appreciated...

Something like this will work.

class Foo; end
class Bar; end
arr = ['foo','bar','bad']
out = arr.collect {|a| Object.const_get(a.capitalize) rescue nil }
out.compact!

Seehttp://www.ruby-doc.org/core/classes/Array.html

Look for collect and compact! to see what I'm doing there.

Sorry, forgot to instantiate the objects. Should have been.

class Foo; end
class Bar; end
arr = ['foo','bar','bad']
out = arr.collect {|a| Object.const_get(a.capitalize).new rescue nil }
out.compact!
 
G

Ge Bro

Great, so with everyone's help this is what I ended up doing in the end:

foo = [1foo, 2foo, 3foo]
bar = [abar, bbar]

['foo', 'bar'].each do |this_array|
length = 0
this_obj = this_array.to_s.capitalize.constantize
until length == eval(this_array).length
this_obj.create:)word => eval(this_array)[length])
length += 1
end

the trick was to use the .constantize method to turn 'foo' and 'bar'
into classes Foo and Bar, and then to use eval() to use the array's
names instead of its contents.

I came across .constantize after searching for const_get as phrogz
suggested.

7stud, my classes are defined in their corresponding controllers - this
is a part of a Rails exercise

dusty, your method using collect and compact looks interesting, I'll
play with it too.

Thanks everyone for help. I haven't been excited about programming for
the last 15 years, but Ruby is changing that rapidly :)
 
G

Ge Bro

Ge said:
Great, so with everyone's help this is what I ended up doing in the end:

foo = [1foo, 2foo, 3foo]
bar = [abar, bbar]

['foo', 'bar'].each do |this_array|
length = 0
this_obj = this_array.to_s.capitalize.constantize
until length == eval(this_array).length
this_obj.create:)word => eval(this_array)[length])
length += 1
end

the trick was to use the .constantize method to turn 'foo' and 'bar'
into classes Foo and Bar, and then to use eval() to use the array's
names instead of its contents.

I came across .constantize after searching for const_get as phrogz
suggested.

7stud, my classes are defined in their corresponding controllers - this
is a part of a Rails exercise

dusty, your method using collect and compact looks interesting, I'll
play with it too.

Thanks everyone for help. I haven't been excited about programming for
the last 15 years, but Ruby is changing that rapidly :)


btw, i do have to admit that i have no clear idea of why eval() works
here and what it's actually supposed to do. Just reading about it now
and trying to make sense of it.
 
7

7stud --

Ge said:
Great, so with everyone's help this is what I ended up doing in the end:

You didn't learn your lessons very well. Look up what the method to_s
does. Does calling to_s on a string do anything? Experiment.
7stud, my classes are defined in their corresponding controllers - this
is a part of a Rails exercise

This isn't a Rails forum.
I came across .constantize after searching for const_get as phrogz
suggested.

If you are discarding the ruby solutions that were proffered in favor of
rails specific solutions, then why not just go to the rails forum?

In any case, if you look at the definition of the constantize method, it
just calls the ruby method module_eval, and module_eval seems to act
like const_get in your situation, although module_eval can also do some
other things.

btw, i do have to admit that i have no clear idea of why eval() works
here and what it's actually supposed to do.

You don't seem to understand the difference between a variable name and
a string. The most obvious difference is that a string has quotes
around it. Look at this example:

arr = [1, 2, 3, 4, 5, 6, 7]

puts 'arr'.length
puts arr.length

Can you guess what the output will be? Run the code and see if you are
correct.

Now try this:

puts 'arr'[0, 1]
puts arr[0, 1]

Those lines say get the elements starting at position 0 and stopping at
position 1(which does not include the stopping position). Can you
guess the output?

A string and a variable name are different animals.

The eval method says to treat a string as if it is ruby code. If your
string looks like this:

str = "num = 10; puts num"

and you eval() that string then ruby will treat the string as code and
execute it. Essentially, eval removes the quotes around a string and
then treats what's left as code. As a result, when you ask ruby to eval
a string like:

"name"

that becomes:

name

and to ruby that looks like a variable name or a method call. Try this
program:

eval("name")

--output:---
r5test.rb:1: undefined local variable or method `name' for main:Object
(NameError)

ruby can't find a variable named name nor a method named name, so ruby
produces the error message. Now try this:

def name
puts 'Jon'
end

name

The output should be obvious. Now using eval:

def name
puts 'Jon'
end

eval("name")

Why is that useful? Why not just use the previous example's code?
Because sometimes you have a method name as a string, and you want to
execute the method, e.g.:

puts "What method do you want to execute: "
input = gets
input = input.strip

def hi
puts 'hello'
end

def bye
puts 'goodbye'
end

eval(input)

--output:--
What method do you want to execute:
hi
hello

However, you should avoid using eval whenever possible. First, it's
slow. Second, someone could enter a string that contains a command to
erase your hard drive. When you eval that string, poof!
 
7

7stud --

Ge said:
Hey all,
this is a total newbie question, i'm just starting to learn Ruby.

here's the thing: i've got a couple of arrays with several items in
each. I'd like to use a block to iterate through each array, and create
objects of the class that is named the same as the array.

This is supposed to populate a database where tables are named after
arrays, and each table contains 1 column called "item_name" with items
from the corresponding array.

allow me to illustrate:

###

some_item = ['foo', 'bar']
other_item = ['alpha', 'bravo', 'foxtrot', 'zebra']

['some_item', 'other_item'].each do |this_array|
array_length = 0
this_object = this_array.to_s.capitalize #<== here's the problem
until array_length == this_array.length
this_object.new:)item_name => this_array[array_length])
array_length += 1
end
end

###

the problem is, this_object becomes a string with the value of
|this_array|. How do I explain to the program that its value is not just
a string, but actually a name of the class, an instance of which i'm
trying to initialize?

Forgive the dumb question, but i'm just starting out. I've been banging
my head against this for hours.... any help would be appreciated...

thanks!

By the way, are you aware that your arrays can contain the class objects
themselves rather than strings:

class Dog
def id
puts "I'm a Dog"
end
end

class Flower
def id
puts "I'm a Flower"
end
end

class Circle
def id
puts "I'm a Circle"
end
end

arr = [Dog, Flower, Circle]

arr.each do |a_class|
obj = a_class.new
obj.id
end

--output:--
I'm a Dog
I'm a Flower
I'm a Circle
 
G

Giles Bowkett

this_object = this_array.to_s.capitalize #<== here's the problem
Search the archives (this is not just a forum on a website, but a
mailing list and newsgroup) for posts like string name to class. The
simple answer involves const_get. This is a FAQ.

The gateway link to this list is the top link on ruby-forum.com.
Considering how easy it would be for a newbie to just click the first
link on the list, the newbie questions we get are actually pretty
good. But I e-mailed the Ruby Forum guy anyway to see if I could
persuade him to move the link down the list a bit and maybe put the
Rails list link at the top. No dice so far. No response so far, in
fact.

Also, just a note - the OP says he doesn't know what eval does - and 7stud said:
However, you should avoid using eval whenever possible. First, it's
slow. Second, someone could enter a string that contains a command to
erase your hard drive. When you eval that string, poof!

Just wanted to chime in with my agreement. eval is risky, don't mess
with eval unless you're sure you know what you're doing (or you're
feeling extremely lucky).

Also:
here's the thing: i've got a couple of arrays with several items in
each. I'd like to use a block to iterate through each array, and create
objects of the class that is named the same as the array.

I would say that the real way to fix this would be to fix this part
here. That's really where the bug is. Naming the array after the
class. If you have that data, you can also use it to create a string,
and that's so much easier.

Instead of

arbitrary_class = [item, other_item]

And then a whole bunch of "meta" stuff, including an eval on the class
name - which you have to create an array of strings to do anyway - I
would recommend maybe this:

classes_with_items = {"ArbitraryClass" => ["item", "other_item"]}

and then

classes_with_items.each do |class_name, array|
array.each do |item_name|
class_name.constantize.new:)item_name => item_name)
end
end

It's a lot easier to read and you won't be cursing yourself a month
later when you find the code, read it, and need to remember what it
does and how it does it.

--
Giles Bowkett

Podcast: http://hollywoodgrit.blogspot.com
Blog: http://gilesbowkett.blogspot.com
Portfolio: http://www.gilesgoatboy.org
Tumblelog: http://giles.tumblr.com
 
7

7stud --

Ge said:
Ge said:
Great, so with everyone's help this is what I ended up doing in the end:

foo = [1foo, 2foo, 3foo]
bar = [abar, bbar]

['foo', 'bar'].each do |this_array|

Just a thought: Are you under the impression that if you specify a
variable named this_array and then your code assigns a value to that
variable that the value will be converted into an array? Because that
is not correct. The variable this_array is going to have the strings
'foo' and 'bar' assigned to it. So this_array might more properly be
named my_string, which would make your code less confusing to read, and
might solve some of your coding problems.
 
G

Ge Bro

7stud said:
You didn't learn your lessons very well. Look up what the method to_s
does. Does calling to_s on a string do anything? Experiment.

You're right, no sense to use it there - it was a leftover from my
previous experiments that I forgot to remove.
This isn't a Rails forum.

The question was purely ruby-related, the point is that my classes _are_
defined, and class definition is not a problem in this case.

If you are discarding the ruby solutions that were proffered in favor of
rails specific solutions, then why not just go to the rails forum?

In any case, if you look at the definition of the constantize method, it
just calls the ruby method module_eval, and module_eval seems to act
like const_get in your situation, although module_eval can also do some
other things.

Thanks for clarifying that - however, i was not discarding the Ruby
solution, but rather picking one that seemed to be more suitable in my
situation.... I could be wrong, of course - hence the "learning"
part....
You don't seem to understand the difference between a variable name and
a string. The most obvious difference is that a string has quotes
around it.

Yes, that was exactly one of the problems, which is why i had to use
eval() - otherwise my subsequent .length method returned the number of
characters in the string 'foo' as opposed as the number of items in the
array foo[].
The eval method says to treat a string as if it is ruby code.

Ah! that makes sense now. thanks.
However, you should avoid using eval whenever possible. First, it's
slow. Second, someone could enter a string that contains a command to
erase your hard drive. When you eval that string, poof!

Good to know... i'm trying to rewrite it without using eval... it's just
the only thing so far that seems to turn 'foo' into foo...
By the way, are you aware that your arrays can contain the class objects
themselves rather than strings:

Yes, this is how i initially started. but then i ran into a reverse
issue: i had to turn that class' name into a name of the array, one
item of which would be passed as an option to each new instance of the
class:


class Item
def initialize(x)
@property = x
end
end


class Dog < Item
end

class Flower < Item
end

class Circle < Item
end

#here are the arrays holding the future values of @property

dog = ['jump', 'bark']
flower = ['red', 'yellow', 'blue', 'green']
circle = ['large', 'medium', small']

#and here's the array that holds class names

arr = [Dog, Flower, Circle]

#now, create instances of the 3 classes,
#while reading the value for @property from the corresponding array,
#to be set on each object's instantiation.
#the result should be (in this case) 2 instances of Dog, 4 of Flower,
and 3 of Circle, each instance with its own @property.

arr.each do |a_class|
length = 0
until length == a_class.length #<==== here's the problem.
obj = a_class.new:)x => a_class[length]) #<=== and again.
length += 1
end
end



so, my issue here then is translating a_class into its corresponding
variable name for each iteration of arr.each, so that it reads the item
number [length] from its corresponding array. the only way i could
think of doing this is by using a_class.to_s.downcase, but of course ran
into the problem of 1) a_class being a class, so to_s method won't
return what I need, and 2) even if I were to somehow get the name of
this class and managed to .to_s.downcase it, i'd have to use eval()
again to make it a variable....

Thus, i had to populate my arr[] with lowercase strings, instead of the
(more elegant) class names, like you suggest.....
 
G

Ge Bro

Giles said:
The gateway link to this list is the top link on ruby-forum.com.
Considering how easy it would be for a newbie to just click the first
link on the list, the newbie questions we get are actually pretty
good. But I e-mailed the Ruby Forum guy anyway to see if I could
persuade him to move the link down the list a bit and maybe put the
Rails list link at the top. No dice so far. No response so far, in
fact.

Yep, i'm getting to this list via ruby-forum.com... However i did
specifically post in the ruby list as opposed to the rails one, since it
did seem like a ruby-specific question... i mean, yes, i _am_ trying to
populate a database via rails, in case you're wondering :)... but it's
the ruby part that i'm having trouble with.
Also:
here's the thing: i've got a couple of arrays with several items in
each. I'd like to use a block to iterate through each array, and create
objects of the class that is named the same as the array.

I would say that the real way to fix this would be to fix this part
here. That's really where the bug is. Naming the array after the
class. If you have that data, you can also use it to create a string,
and that's so much easier.

Instead of

arbitrary_class = [item, other_item]

And then a whole bunch of "meta" stuff, including an eval on the class
name - which you have to create an array of strings to do anyway - I
would recommend maybe this:

classes_with_items = {"ArbitraryClass" => ["item", "other_item"]}

and then

classes_with_items.each do |class_name, array|
array.each do |item_name|
class_name.constantize.new:)item_name => item_name)
end
end

It's a lot easier to read and you won't be cursing yourself a month
later when you find the code, read it, and need to remember what it
does and how it does it.

So, you're basically using a hash to map Class names to their
corresponding arrays? That's pretty cool!!! :) kinda makes everything
much easier.... a super elegant solution, actually. I didn't realize you
could have an array inside of a hash (why not, now that i think of
it)...
I'll try it this way, thanks! (with .constantize as well as with
const_get, just for the sake of Ruby purity)...
 
G

Ge Bro

Giles said:
Instead of

arbitrary_class = [item, other_item]

And then a whole bunch of "meta" stuff, including an eval on the class
name - which you have to create an array of strings to do anyway - I
would recommend maybe this:

classes_with_items = {"ArbitraryClass" => ["item", "other_item"]}

and then

classes_with_items.each do |class_name, array|
array.each do |item_name|
class_name.constantize.new:)item_name => item_name)
end
end

This worked beautifully! love the solution.

Just for fun, I used:

Object.const_get(class_name).create:)item_name => item_name)

Note how i had to use .create instead of .new... This probably has to do
with Rails, so I'll take it to the Rails forum. But this has really
straightened out my understanding of Ruby's nested arrays, hashes, and
blocks.... Thank you!
 
G

Giles Bowkett

classes_with_items = {"ArbitraryClass" => ["item", "other_item"]}
This worked beautifully! love the solution.

Cool! :)
Just for fun, I used:

Object.const_get(class_name).create:)item_name => item_name)

Note how i had to use .create instead of .new... This probably has to do
with Rails, so I'll take it to the Rails forum. But this has really
straightened out my understanding of Ruby's nested arrays, hashes, and
blocks.... Thank you!

Well, create does a save while new just creates the object, but don't
tell anyone I told you that.

--
Giles Bowkett

Podcast: http://hollywoodgrit.blogspot.com
Blog: http://gilesbowkett.blogspot.com
Portfolio: http://www.gilesgoatboy.org
Tumblelog: http://giles.tumblr.com
 
G

Giles Bowkett

classes_with_items = {"ArbitraryClass" => ["item", "other_item"]}
I just realized, there's a better way to do it. Instead of putting the
class name in a hash, just put the class itself in there.

classes_with_items = {ArbitraryClass => ["item", "other_item"]}

classes_with_items.each do |klass, array|
array.each do |item_name|
klass.create!:)item_name => item_name)
end
end

--
Giles Bowkett

Podcast: http://hollywoodgrit.blogspot.com
Blog: http://gilesbowkett.blogspot.com
Portfolio: http://www.gilesgoatboy.org
Tumblelog: http://giles.tumblr.com
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,733
Messages
2,569,440
Members
44,830
Latest member
ZADIva7383

Latest Threads

Top