I don't like to top post even a little bit, but I want to apologize for 
the long post (don't really like attachements), and I'm sorry if the way 
I use Ruby makes some people cringe, but Ruby really makes my job easier 
for everything from testing hardware units to comparing and sorting 
bills of material, etc. Some people may want to skip to the end where 
there are some examples.

Robert Klemme wrote:
> On Fri, Oct 8, 2010 at 6:06 AM, John Sikora <john.sikora / xtera.com> 
> wrote:
> 
> So you have something like
> 
> class Foo
>   def self.each
>     yield "whatever"
>     self
>   end
> end
> 
> ?  Did you also do
> 
> class Foo
>   def self.each
>     yield "whatever"
>   end
> 
>   extend Enumerable
> end
> 
> ?  That would give you all methods like #find_all etc. for free.

Robert,
Note that I have kept the code and comments to a minimum here as I did 
not want the post to be even longer. I know that you are quite 
knowledgeable and that you should have little trouble following what I 
am doing - although you might not necessarily understand why I chose to 
do it this way:

class Base
  @class_all_enum_objects = Array.new

  class << self
    attr_accessor :class_all_enum_objects
  end

  def initialize
    self.class.class_all_enum_objects << self
  end

  def self.enum
     EnumeratorEOC.new(self, :each)
  end

  def self.each(&code_block)
    self.class_all_enum_objects.each do |object|
      yield object
    end
  end
end

module EnumeratorModifierEOC
  def find_all(*condition_strings, &code_block)
     if block_given?
       find_array = super
     else
       find_array = # process condition strings (syntactic sugar) here.
     end
     ArrayEOC.new(find_array)
  end

  # code for other methods such as sort, min, max, all?, etc. here.
end

class EnumeratorEOC < Enumerator         # avoids modifying Enumerator.
  include EnumeratorModifierEOC

  def initilize(object, method_sym)
    super
  end
end

class ArrayEOC < Array                   # avoids modifying Array.
  include EnumeratorModifierEOC

  def initialize(array = [])
    super(array)
  end
end

To suit my purposes: 1) I want the class itself to keep track of all of 
the class instances, as shown above, 2) if an attribute has more than 
one value or is more complex than an Array, I prefer to define a class 
for it (inherited from Base) and assign attributes, 3) I want syntactic 
sugar for enumerables, 4) especially for sort, including default sort 
parameters (min and max get defaults for free as a result of the 
necessary <=> definition). 5) I want to be able to define new functions 
and to be able to use syntactic sugar with them. See below. 6) I also 
keep track of descendants of classes although this is only partly shown 
below.  There is a little more on this at the end if anyone is still 
reading.

> 
>> and I have added syntactic sugar to some of the methods. So I can be
>> lazy (and quick) and write things like:
>>
>> Slot.enum.find_all(?:slot_num >= 8?, ?:slot_num <= 22?, ?:rate ==
>> ?ufec??).all?(?:errors == 0?)
> 
> Is this equivalent to
> 
> enum.find_all {|s| (8..22) === s.slot_num && s.rate == 'ufec'}.all?
> {|s| e.errors == 0}

Yes, you are correct (e.errors is a typo - should be s.errors), they are 
equivalent.

> 
> ?  If yes, I do not really see the advantage of your approach.  It's
> slower needs a similar amount of typing and will detect errors later
> (at execution time vs. at parse time).
> > 
> If my assumption above is correct I do not understand where the "easy
> way out" is.
> 
I think 'sort' is the main reason for my 'easy way out'. Plus the use of 
additional functions. To illustrate, I have included in the code below 
one such function, 'sum_total_eoc' which is just 'inject' set up for 
addition, but with the ability to use syntactic sugar (of course).

You may or may not agree with my 'easy way out', but here goes:

First, a short explanation: I have attribute and expression comparables 
for each class. The comparables are compared in the order that they are 
given in attr_accessor. There is also a class method called 
set_comparables_order that allows the user to re-arrange the order if 
desired (it also allows expressions, not just attributes). Child classes 
inherit the comparables and their order from the parent class. Any new 
attributes defined in attr_accessor in the child class are added to the 
comparables up front.

To do this, the following is added to the code above (stripped of error 
checking, etc. to avoid clutter):

class Base
  @class_comparables = Array.new

  class << self
    attr_accessor :class_comparables
  end

  self.inherited(child_class)
    child_class.class_all_enum_objects = Array.new   # init for the new 
class.
    child_class.class_comparables = self.class_comparables  # inherit.
  end

  def self.attr_accessor(*accessor_names)
    self.class_comparables.replace(accessor_names + 
self.class_comparables).uniq
    super
    # also update descendants' class_comparables if needed.
  end

  def self.set_comparables_order(*attr_and_expr)
    self.class_comparables.replace(attr_and_expr + 
self.class_comparables).uniq
   # also update descendants' class_comparables if needed.
  end

  def self.enum
     EnumeratorEOC.new(self, :each, class_comparables) # added 
class_comparables
  end

  def <=>(other)
     # use the comparables list here.
  end
end

class EnumeratorEOC
  def initilize(object, method_sym, comparables)
    @comparables = comparables
    super(object, method_sym)
  end
end

class ArrayEOC
   def initialize(array = [], comparables)
    @comparables = comparables
    super(array)
  end

  def sum_total_eoc(*params_exprs)       # added this function.
    self.inject(0) do |sum, object|
      # process params_exprs syntactic sugar on object here.
    end
  end
end

module EnumeratorModifierEOC
  def sort(*params_exprs, &code_block)   # sho w sort, since it is of 
interest.
    if block_given?
      return_array = super
    else
      return_array = # process params_exprs including setting 
comparables.
    end
    ArrayEOC.new(return_array, comparables)   # comparables propagate.
   end
end

--------------
OK, so what can I do with this? The following are some simplified 
examples:

class Slot < EnumerableObjectClass::Base
  attr_accessor :slot_num, :rate, :error_info # <--Array of ErrorInfo 
objects.

  def initialize(slot_num, rate, error_info)
    super()
    @slot_num, @rate, @error_info = slot_num, rate, error_info
  end
end

class ErrorInfo < EnumerableObjectClass::Base
  attr_accessor :time_stamp, :num_errors

  def initialize(time_stamp, num_errors)
    super()
    @time_stamp, @num_errors = time_stamp, num_errors
  end
end

Slot.new(3, 'efec', [ErrorInfo.new('10-07-10', 500), 
ErrorInfo.new('10-08-10', 1000)])
Slot.new(1, 'ufec', [ErrorInfo.new('10-04-10', 1000), 
ErrorInfo.new('10-05-10', 2000)])
Slot.new(8, 'ufec', [ErrorInfo.new('10-07-10', 1500), 
ErrorInfo.new('10-08-10', 1200)])
Slot.new(4, 'efec', [ErrorInfo.new('10-07-10', 3500)])
Slot.enum{}.find(':slot_num == 4').error_info << 
ErrorInfo.new('10-08-10', 2500)

-------------
# note - most of the examples below do not use variables. However, this 
is not a limitation and a couple of examples are given that use 
variables.

# default sort is sorted by :slot_num, etc. - order of attr_accessor
p Slot.enum.sort.collect(:slot_num)
# => [[1], [3], [4], [8]]

# sort by total errors.
p 
Slot.enum.sort(':error_info.sum_total_eoc(:num_errors)').collect(:rate, 
':error_info.sum_total_eoc(:num_errors)')
# => [["efec", 1500], ["ufec", 2700], ["ufec", 3000], ["efec", 6000]]

# sort by rate, total errors.
p Slot.enum.sort(:rate, 
':error_info.sum_total_eoc(:num_errors)').collect(:rate, 
':error_info.sum_total_eoc(:num_errors)')
# => [["efec", 1500], ["efec", 6000], ["ufec", 2700], ["ufec", 3000]]

# sort by rate, total errors, with total errors sorted high to low by 
ending with '___' triple underscore.
p Slot.enum.sort(:rate, 
':error_info.sum_total_eoc(:num_errors)___').collect(:rate, 
':error_info.sum_total_eoc(:num_errors)', :slot_num)
# => [["efec", 6000, 4], ["efec", 1500, 3], ["ufec", 3000, 1], ["ufec", 
2700,8]]

I think that the above would be hard to do in a one-liner w/o syntactic 
sugar, so this is my 'easy way out' (although I would not be surprised 
if you came up with one Robert). I think this is pretty easy and it 
saves me time.

# total errors of all slots.
p Slot.enum.sum_total_eoc(':error_info.sum_total_eoc(:num_errors)')
 # => 13200

# slots that have >= 3000 total errors.
p Slot.enum.find_all(':error_info.sum_total_eoc(:num_errors) >= 
3000').collect(:slot_num)
# => [[1], [4]]

# use a variable for sort parameter.
var = ':slot_num'
p Slot.enum.sort("#{var}").collect("#{var}")
# => [[1], [3], [4], [8]]

# use a variable w/o interpolation - must pass a binding with enum{} 
(which is where this topic started by the way). It is carried along as 
an attribute to ArrayEOC and/or EnumeratorEOC, similar to coparables.
var = ':slot_num'
p Slot.enum{}.sort(var).collect(var)
# => [[1], [3], [4], [8]]

# slots that have >= 2500 errors in a single time period.
p Slot.enum.find_all(":error_info.any?(:num_errors >= 
2500)").collect(:slot_num)
# => Exceptions galore! my simple 'parser' breaks down. will have to use 
the 'old fashioned way' of a code block. maybe I will try to get this to 
work in the future.

In case anyone is wondering, if data is input as an Array, it comes out 
as an ArrayEOC the first time it is read. In the simplified code above, 
I show a call to super in attr_accessor, but really for the reader part, 
I add a line that converts an Array to an ArrayEOC. I do this so I can 
use the ArrayEOC methods as mofified since I want to keep Array's 
methods unmodified.

Since I already use self.inherited, I am able to keep track of 
descendants (a recursive jaunt through the child classes using inject to 
build an array), so there is enum_only{} which enumerates only the given 
class, and enum{} which enumerates the given class and all descendants. 
I do this because I have a number of units of similar type which follow 
the classic inheritance OO model (even though it is apparent from posts 
that this has fallen out of favor). Oh well, it seems that I am behind 
the times on a lot of things.

Oh, and since Ruby is dynamic, I had to modify remove_method and 
undefine_method to remove any comparables, if applicable (there is a 
difference between the two however). And if there are subsequent calls 
to attr_accessor, I have to add those methods to the class comparables 
in the class and any descendant classes. I allow the user to reset the 
comparables to those given in the class and its ancestors. I also tried 
to make the code reflective, by making the comparables, child_classes, 
descndants, etc. availble. Writing this code has certainly been fun and 
educational!

js








-- 
Posted via http://www.ruby-forum.com/.