Need a ruby approach

  • Thread starter Fredrik Jagenheim
  • Start date
F

Fredrik Jagenheim

Hi,

First a little warning, this post is more of a rubberducking session
than a proper question.

I have a rather trivial problem, but since I'm spending 8 hours a day
cursing C++ and far coding in Ruby, I'm afraid I can't think of a
proper Ruby solution, and instead come up with 'this is how it
could've been done in C++'-solutions.

The problem: I have a data set. I'd like to see if this data set
fullfills certain properties, so I did a couple of classes which will
do the matching against these properties:

def MatchA
def MatchA.match?(set)
# Complex calculations on set
true
end
end

def MatchB
def MatchB.match(set)
# More complex calculations on set
true
end
end

Then I'd just do:
puts "It matched A" if MatchA.match?(set)
puts "It matched B" if MatchB.match?(set)

Ok, but now I have 50 of these match classes, so I figured I should
make an array of them:

array_of_match_objects.each{ |match|
puts "It matched #{match.name}" if match.match(set)
}

I would have to change the methods from class to object first, I
guess...

Anyway, then I started to think about using a superclass for these
classes and use Ruby introspection to find all the subclasses and call
match from there. That's when I realized there must be simpler
solution I just can't see...

Perhaps extending the set-class with the methods;

def MatchB
def match_b?
# Complex calculation
end
end

set.extend(MatchB)

puts "Matched B" if set.match_b?

It probably be the best way OO-wise, since it's a property of the Set.

Or am I really wrong here?

//F
 
M

Martin DeMello

Fredrik Jagenheim said:
Anyway, then I started to think about using a superclass for these
classes and use Ruby introspection to find all the subclasses and call
match from there. That's when I realized there must be simpler
solution I just can't see...

Here's one way (untested)

class SetMatcher
def initialize
@tests = []
end

def add(&test)
@tests << test
end

def match?(set)
@tests.all? {|test| test.call(set)}
end
end

m = SetMatcher.new
m.add {|set|
#complex test
}

m.add {|set|
#complex test
}

s = DataSet.new(datasource)
if m.match?(s)
...
end

#---------------------------------------------------------

You could also rewrite it slightly to be a module mixed into your set
class, in which case match? wouldn't need a parameter, as wouldn't the
matching procs.

martin
 
R

Robert Klemme

Fredrik Jagenheim said:
Hi,

First a little warning, this post is more of a rubberducking session
than a proper question.

I have a rather trivial problem, but since I'm spending 8 hours a day
cursing C++ and far coding in Ruby, I'm afraid I can't think of a
proper Ruby solution, and instead come up with 'this is how it
could've been done in C++'-solutions.

The problem: I have a data set. I'd like to see if this data set
fullfills certain properties, so I did a couple of classes which will
do the matching against these properties:

My first question would be, do you want to be able to do the checks
individually or do you want to always apply all checks?

[snip]
Anyway, then I started to think about using a superclass for these
classes and use Ruby introspection to find all the subclasses and call
match from there.

That's what I'd do. It gives you the flexibility to execute each
individual test if you need it and provides for easy finding of all tests.
That's when I realized there must be simpler
solution I just can't see...

Perhaps extending the set-class with the methods;

def MatchB
def match_b?
# Complex calculation
end
end

set.extend(MatchB)

puts "Matched B" if set.match_b?

No, this seems overly complex to me and (more important) these tests have
nothing to do with Sets per se. You should then create a sub class of Set
that contains all match methods. But the other approach (individual
classes per test) is more modular, flexible and easier to extend. If you
hava a single Set sub class that contains all tests you'd have to use
something like this for all matches

def all_matches?
public_methods.grep( /^match/ ).each do |m|
return false unless self.send m
end
true
end
It probably be the best way OO-wise, since it's a property of the Set.

I regard the single test classes better OO wise, since they all implement
a single interface you you don't have to do method name tricks to find all
tests.
Or am I really wrong here?

No, I think you picked the reasonable solutions.

Cheers

robert
 
R

Robert Klemme

An example:

require "Set"

class SetTest
def self.inherited(cl)
( @test_classes ||=[] ) << cl
end

def self.all_tests(set)
@test_classes.each do |cl|
return false unless cl.new.match(set)
end

true
end
end


class Test1 < SetTest
def match(set)
puts "Test: #{self.class.name}"
true
end
end

SetTest.all_tests( Set.new )

robert
 
M

Martin DeMello

Robert Klemme said:
No, this seems overly complex to me and (more important) these tests have
nothing to do with Sets per se. You should then create a sub class of Set
that contains all match methods. But the other approach (individual
classes per test) is more modular, flexible and easier to extend. If you
hava a single Set sub class that contains all tests you'd have to use
something like this for all matches

def all_matches?
public_methods.grep( /^match/ ).each do |m|
return false unless self.send m
end
true
end

This seems rather heavy and, well, Java-like (where you're forced to
create a class because you can't have unattached procs). A test is,
conceptually, an unbound function, and Ruby does indeed give us a
mechanism to create such.

martin
 
R

Robert Klemme

Martin DeMello said:
This seems rather heavy and, well, Java-like (where you're forced to
create a class because you can't have unattached procs).

Hm, I find this clearer - just because certain things can be done does
necessarily mean that's the best way to do it.
A test is,
conceptually, an unbound function, and Ruby does indeed give us a
mechanism to create such.

I assume you mean blocks / procs. The OP stated that the tests are fairly
complex, that's why IMHO a class per test type seems more appropriate
since then handling of state (i.e. instance vars) is handled more
gracefully than in a block. Of course it can be done there, too, but the
complexity calls for separate classes.

The other solution that I find appropriate is to create a module that is
used for extending specific set instances (those that carry the data which
I want to apply the tests to). My 2 cent...

Regards

robert
 
F

Fredrik Jagenheim

I assume you mean blocks / procs. The OP stated that the tests are fairly
complex, that's why IMHO a class per test type seems more appropriate
since then handling of state (i.e. instance vars) is handled more
gracefully than in a block. Of course it can be done there, too, but the
complexity calls for separate classes.

I went with the subclassing, and from your excellent example it worked
perfectly. Many thanks.

//F
 

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,755
Messages
2,569,536
Members
45,020
Latest member
GenesisGai

Latest Threads

Top