From: John Sikora Date: 2010-10-09T15:25:19+09:00 Subject: Re: setting local variables in a binding 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 > 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/.