Is there a better way of doing this

D

Damjan Rems

I have three classes.
module mymodule

class Abstract
end

class A < Abstract
end

class B < Abstract
end

end

And I want the third class to subclass class A or B dependent on
parameter passed. my_src is also the name of source file:

This is what I do:

I create my_src.rb where I write:
class my_src < Abstract
....
end

Finally I have a method to load my class and do something with it.
def run_class(filename, parm)
src = File.read(filename + '.rb')
src.sub!('Abstract', parm)
# Evaluate is equal to load
eval src.to_s
obj = eval( File.basename(filename).capitalize + '.new' )
obj.do_something
end
run_class('my_src')

Ruby is wonderful language which allows us to do this. But the
substitution part is kinda ugly to me.

So is there a better way of doing this?



by
TheR
 
M

Marc Heiler

Would Object.const_get not work?

eval indeed looks ugly.

It kinda reminds me of a farmer who uses a new machine to work on his
field, and then using his bare hands to dig holes for new seeds, because
his new machine looks a bit awkward to him right now, and digging holes
with his hands (using eval) is so much easier...
 
D

Damjan Rems

Marc said:
Would Object.const_get not work?

eval indeed looks ugly.

It kinda reminds me of a farmer who uses a new machine to work on his
field, and then using his bare hands to dig holes for new seeds, because
his new machine looks a bit awkward to him right now, and digging holes
with his hands (using eval) is so much easier...

To be frank this is the solution that I came to. It is ugly but it
works. I don't know (yet) any other way.


by
TheR
 
D

Damjan Rems

Marc said:
Would Object.const_get not work?

eval indeed looks ugly.

It kinda reminds me of a farmer who uses a new machine to work on his
field, and then using his bare hands to dig holes for new seeds, because
his new machine looks a bit awkward to him right now, and digging holes
with his hands (using eval) is so much easier...

And nope:

NameError: wrong constant name My_src.new


by
TheR
 
J

Joel VanderWerf

Damjan said:
I have three classes.
module mymodule

class Abstract
end

class A < Abstract
end

class B < Abstract
end

end

And I want the third class to subclass class A or B dependent on
parameter passed. my_src is also the name of source file:

This is what I do:

I create my_src.rb where I write:
class my_src < Abstract
....
end

Finally I have a method to load my class and do something with it.
def run_class(filename, parm)
src = File.read(filename + '.rb')
src.sub!('Abstract', parm)
# Evaluate is equal to load
eval src.to_s
obj = eval( File.basename(filename).capitalize + '.new' )
obj.do_something
end
run_class('my_src')

Ruby is wonderful language which allows us to do this. But the
substitution part is kinda ugly to me.

So is there a better way of doing this?

One idea: define A and B as modules instead of classes. Then, when you
load the file that defines "class MySrc < Abstract", you don't have to
substitute anything. Just include the module, as in:

Module ModA; ...; end

load filename + ".rb"
cl = Object.const_get( File.basename(filename).capitalize )
cl.class_eval {include ModA}

Now, instances of cl will have all the methods of module ModA.
 
D

David Masover

And I want the third class to subclass class A or B dependent on
parameter passed.

On loading the file? That might not be the best way...
Finally I have a method to load my class and do something with it.
def run_class(filename, parm)
src = File.read(filename + '.rb')
src.sub!('Abstract', parm)
# Evaluate is equal to load
eval src.to_s
obj = eval( File.basename(filename).capitalize + '.new' )
obj.do_something
end
run_class('my_src')

Ok... First of all, that method is too long... you probably don't want
obj.do_something in there. You probably want it to return obj so you can do
something with it.

Second: Do A and B have to be classes? Can they be modules instead?
How about Myclass? Can it be a module instead of a class?

Let's say A and B are modules:

class MyClass < Abstract
...
end
def run_class type
obj = MyClass.new
obj.extend type
obj
end
run_class(A)
run_class(B)

Or, let's say MyClass is actually MyModule:

class A < Abstract
include MyModule
end
class B < Abstract
include MyModule
end
# not sure why you still need this, but here you go:
def run_class type
type.new
end

Or, if you don't know which classes MyModule might be mixed into, you could do
something like this:

def run_class type
obj = type.new
obj.extend MyModule
end

Or, you could even do hacks like this:

[A, B].each do |type|
type.send :include, MyModule
end

Which of these routes makes sense depends very much on what you're trying to
build. I don't know what that is. For example, several people have suggested
using const_get, but I don't see why you can't just pass the module/class
itself as a parameter -- they're objects, too!
 
T

Tim Hunter

Damjan said:
I have three classes.
module mymodule

class Abstract
end

class A < Abstract
end

class B < Abstract
end

end

And I want the third class to subclass class A or B dependent on
parameter passed. my_src is also the name of source file:

This is what I do:

I create my_src.rb where I write:
class my_src < Abstract
....
end

Finally I have a method to load my class and do something with it.
def run_class(filename, parm)
src = File.read(filename + '.rb')
src.sub!('Abstract', parm)
# Evaluate is equal to load
eval src.to_s
obj = eval( File.basename(filename).capitalize + '.new' )
obj.do_something
end
run_class('my_src')

Ruby is wonderful language which allows us to do this. But the
substitution part is kinda ugly to me.

So is there a better way of doing this?



by
TheR

I'm not sure if this is what you're looking for, but...keep in mind that
the class name ("Abstract") is just a constant that contains the class
object. You can assign that class object to a variable and use the
variable in place of the actual class name when you're defining the
subclass. So you could have

class A < Abstract
end

class B < Abstract
end

if use_A then
sup = A
else
sup = B
end

class C < sup
end

Furthermore, you can create a new class with Class.new and specify the
superclass as the argument to new. Then you can create new objects of
the class by sending the new method to the class object.

my_cls = Class.new(sup)
my_obj = my_cls.__send__:)new)
 
A

Andrew Timberlake

I'm not sure if this is what you're looking for, but...keep in mind that
the class name ("Abstract") is just a constant that contains the class
object. You can assign that class object to a variable and use the variable
in place of the actual class name when you're defining the subclass. So you
could have

class A < Abstract
end

class B < Abstract
end

if use_A then
sup = A
else
sup = B
end

class C < sup
end

Furthermore, you can create a new class with Class.new and specify the
superclass as the argument to new. Then you can create new objects of the
class by sending the new method to the class object.

my_cls = Class.new(sup)
my_obj = my_cls.__send__:)new)

def run_class(param)
Class.new(param) do
def test
'Hello World'
end
end.new #<- Note I've got .new in here
end

c = run_class(A)
c.is_a?(A)
#=> true
c.test
#=> Hello World

Andrew Timberlake
http://ramblingsonrails.com
http://www.linkedin.com/in/andrewtimberlake

"I have never let my schooling interfere with my education" - Mark Twain
 
R

Robert Klemme

The question is: of doing what? You say you want the third class to
subclass either A or B. But the code you presented just replaces the
first occurrence of "Abstract" with something else. Also, your
run_class is not called with the second parameter.

If you want to control inheritance from either A or B you can do

class X < condition ? A : B
end

But frankly, this approach seems strange at least. The question is, why
do you need make the inheritance structure of your class configurable?
The suggestion to make A and B mixin modules seems much better to me.
You can even extend instances of your class with a module on a case by
case basis.

Can you present a bigger picture so we can understand what motivates
your approach?

Kind regards

robert
 
D

Damjan Rems

Robert said:
The question is: of doing what? You say you want the third class to
subclass either A or B. But the code you presented just replaces the
first occurrence of "Abstract" with something else. Also, your
run_class is not called with the second parameter.

If you want to control inheritance from either A or B you can do

class X < condition ? A : B
end

But frankly, this approach seems strange at least. The question is, why
do you need make the inheritance structure of your class configurable?
The suggestion to make A and B mixin modules seems much better to me.
You can even extend instances of your class with a module on a case by
case basis.

Can you present a bigger picture so we can understand what motivates
your approach?

Kind regards

robert

What I am trying to do is create a reporting sistem, something like
Ruport, but simplier and different. So my Class A is ReportPDF and Class
B is ReportHTML and Abstract is ReportBase which defines methods and
variables common to all subclasses. And there can be ReportXX Class in
the future which should expand reporting system to XX format when
required.

What I have accomplished so far is I can call:

run_report('myReportFile', :format => 'HTML')
run_report('myReportFile', :format => 'PDF')

and get result in pdf or html file, with no changes to myReportFile.rb.
Final idea is to make it Rails plugin and run it maybe like this:

respond_to do |format|
format.htmlp { render :file => 'myReportFile', format => 'HTML' }
format.pdf { render :file => 'myReportFile', format => 'PDF' }
end

I guess I still have to learn a lot until then.

But it looks like ugly hack and I don't consider myself to be Ruby
hacker. I am just using what I have learned so far and mix it with my
previuos experiances. Which is mostly wrong because I am always thinking
on how I used to do this before Ruby.

So I am asking if this can be accomplished differently?


by
TheR
 
A

Andrew Timberlake

What I am trying to do is create a reporting sistem, something like
Ruport, but simplier and different.

...

I guess I still have to learn a lot until then.

...

So I am asking if this can be accomplished differently?

For starters, have a look at how Ruport does it.
Also, Gregory actually covers this in his book, Ruby Best Practices
which is worth buying and reading anyway

Andrew Timberlake
http://ramblingsonrails.com
http://www.linkedin.com/in/andrewtimberlake

"I have never let my schooling interfere with my education" - Mark Twain
 
R

Robert Klemme

2009/4/24 Damjan Rems said:
What I am trying to do is create a reporting sistem, something like
Ruport, but simplier and different. So my Class A is ReportPDF and Class
B is ReportHTML and Abstract is ReportBase which defines methods and
variables common to all subclasses. And there can be ReportXX Class in
the future which should expand reporting system to XX format when
required.

What I have accomplished so far is I can call:

run_report('myReportFile', :format =3D> 'HTML')
run_report('myReportFile', :format =3D> 'PDF')

and get result in pdf or html file, with no changes to myReportFile.rb.
Final idea is to make it Rails plugin and run it maybe like this:

=A0respond_to do |format|
=A0 =A0format.htmlp { render :file =3D> 'myReportFile', format =3D> 'HTML= ' }
=A0 =A0format.pdf =A0 { render :file =3D> 'myReportFile', format =3D> 'PD= F' }
=A0end

I guess I still have to learn a lot until then.

But it looks like ugly hack and I don't consider myself to be Ruby
hacker. I am just using what I have learned so far and mix it with my
previuos experiances. Which is mostly wrong because I am always thinking
on how I used to do this before Ruby.

So I am asking if this can be accomplished differently?

Yes. This is a textbook example for using delegation over
inheritance. You should make your Report class *use* a format instead
of *inheriting* a format. You can still have a formatter base class
which implements common things (e.g. if you want your numbers to come
out identical in all reports then you could have a number formatting
method there). But the different formatting (e.g. using <li> vs. some
PDF markup) is done in the specific formatter class.

It seems to me that inheritance is generally overused and people
should more often resort to delegation. Maybe we should blog about
this - although it is not generally a Ruby best practice to delegate
instead of inherit.

Kind regards

robert

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

Damjan Rems

Robert said:
It seems to me that inheritance is generally overused and people
should more often resort to delegation. Maybe we should blog about
this - although it is not generally a Ruby best practice to delegate
instead of inherit.

Kind regards

robert

Can you appoint me to a link, where I could read more about delegation
and how it is done.

by
TheR
 
D

Damjan Rems

Joel said:
One idea: define A and B as modules instead of classes. Then, when you
load the file that defines "class MySrc < Abstract", you don't have to
substitute anything. Just include the module, as in:

Module ModA; ...; end

load filename + ".rb"
cl = Object.const_get( File.basename(filename).capitalize )
cl.class_eval {include ModA}
Now, instances of cl will have all the methods of module ModA.

I am trying this variation for now. And it might look like good until
here:
cl.class_eval {include ModA}

I guess my version is wrong
cl.class_eval { "include 'DrgReports#{opts[:format]}'" }

report = eval( File.basename(custom_class).capitalize + '.new' )

because after this I get error that method found in included module is
missing.


How to write the line correct?


by
TheR
 
R

Robert Klemme

2009/4/24 Damjan Rems said:
Joel said:
One idea: define A and B as modules instead of classes. Then, when you
load the file that defines "class MySrc < Abstract", you don't have to
substitute anything. Just include the module, as in:

=A0 =A0Module ModA; ...; end

=A0 =A0load filename + ".rb"
=A0 =A0cl =3D Object.const_get( File.basename(filename).capitalize )
=A0 =A0cl.class_eval {include ModA}
Now, instances of cl will have all the methods of module ModA.

I am trying this variation for now. And it might look like good until
here:
=A0 =A0cl.class_eval {include ModA}

I guess my version is wrong
=A0 =A0cl.class_eval { "include 'DrgReports#{opts[:format]}'" }

Here you are creating a string only.
=A0 =A0report =3D eval( File.basename(custom_class).capitalize + '.new' )

because after this I get error that method found in included module is
missing.


How to write the line correct?

This should work:

cl.class_eval "include 'DrgReports#{opts[:format]}'"

But again, inclusion - which is a special form of inheritance - does
not seem the proper technique here.

Kind regards

robert


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

Damjan Rems

Robert said:
How to write the line correct?

This should work:

cl.class_eval "include 'DrgReports#{opts[:format]}'"

But again, inclusion - which is a special form of inheritance - does
not seem the proper technique here.

Kind regards

robert

Thanks this works now. I had to leave out single quotes.

def run_report(source_file, opts={}, default_opts={})
opts[:format] ||= 'HTML'
load source_file + '.rb'
cl = Object.const_get File.basename(source_file).capitalize
cl.class_eval "include DrgReports#{opts[:format]}"
report = cl.class_eval 'new'

report.set_options(default_opts)
report.set_options(opts)
report.create
report.do_report
report.close
end

It looks nicer and I understand a piece of Ruby a little more.

I will try to study delegation this weekend.


by
TheR
 

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,968
Messages
2,570,153
Members
46,699
Latest member
AnneRosen

Latest Threads

Top