This project is archived and is in readonly mode.
Enumerable#every functionality
Reported by mynyml | April 6th, 2009 @ 04:28 PM | in 2.x
This patch adds the #every method to Enumerable. It is an alternative to using Symbol#to_proc in a common context, namely with #map. It enhences readability and elegance of the code.
This code started out as a gem and has recieved positive reactions from the community.
enum = [1.4, 2.4 ,3.4]
enum.map {|i| i.floor } #=> [1, 2, 3]
enum.map(&:floor) #=> [1, 2, 3]
enum.every.floor #=> [1, 2, 3]
%w( axb dxf ).every.gsub(/x/,'y') #=> ['ayb', 'dyf']
%w( axb dxf ).every.gsub(/x/) { 'y' } #=> ['ayb', 'dyf']
@company.clients.every.name
Note that the patch is general enough that it can easily be extended (for example to add the same type of functionality for #select and #reject).
Comments and changes to this ticket
-
Peter Wagenet April 7th, 2009 @ 02:40 AM
- Tag changed from enumerable, feature, patch to enumerable, feature, patch, tested, verified
It looks pretty good, though I have found a few funny cases when you call every without any methods. The following is from irb.
>> enum = %w( axc dxf ) => ["axc", "dxf"] >> enum.every => # Blank >> puts enum.every NoMethodError: undefined method `to_ary' for "axc":String from ./active_support/core_ext/enumerable/proxy.rb:10:in `__send__' from ./active_support/core_ext/enumerable/proxy.rb:10:in `method_missing' from ./active_support/core_ext/enumerable/proxy.rb:10:in `map' from ./active_support/core_ext/enumerable/proxy.rb:10:in `__send__' from ./active_support/core_ext/enumerable/proxy.rb:10:in `method_missing' from (irb):13:in `puts' from (irb):13
I'm not sure why the first example is blank. But, in the second case puts tries to call to_ary on the proxy which in turn tries to call to_ary on the strings. It doesn't like that so much.
I know that people shouldn't just call "every" by itself, but it would be nice to make it behave a bit more gracefully there.
-
Peter Wagenet April 7th, 2009 @ 02:44 AM
Looks like the blank was the fault of Wirble. Without Wirble I get:
irb(main):002:0> [1,2,3].every => 123
-
Peter Wagenet April 7th, 2009 @ 03:18 AM
This got me interested so I added a couple more methods.
The biggest change is the addition of a custom respond_to? Previously the default behavior was to call respond_to? on each item of the enum and return an enum of booleans. However, methods that call respond_to? expect a single boolean, not an array. My new method returns true if all items in the enum respond to the method and false if any of them do not. This also fixes our puts bug.
['a', 1].every.respond_to?(:to_s) # => true ['a', 1].every.respond_to?(:upcase) # => false
proxy_target - just returns the enum it's operating on proxy_method - method used to group return, usually :map
proxy_send - same as method_missing, allows you to call a method on the enum that is defined in the Proxy
['a', 1].every.proxy_send(:respond_to?, :upcase) #=> [true, false]
-
Peter Wagenet April 7th, 2009 @ 03:22 AM
Blah, stupid formatting. proxy_target and proxy_method are two separate methods even though my descriptions of them ended up on one line.
-
mynyml April 7th, 2009 @ 01:25 PM
I really your proxy_* methods. They provide simple introspection and feel like AR's association_proxy.
As for #respond_to?, though, I feel it might be breaking expected behaviour; a few people I discussed with proposed adding the same type of behaviour for #nil?, #any?, etc, but I think those are simple/intuitive enough with the basic behaviour, using #all? and #any?:
enum = ['foo', 'bar', ''] enum.every.empty?.all? #=> false enum.every.empty?.any? #=> true enum.every.respond_to?(:to_s).all? #=> true enum.every.blank?.any? #=> true enum.every.nil?.any? #=> false # ...
The two first statements are included in the method's examples, so that the user has an idea of how to easily handle predicate methods.
-
mynyml April 7th, 2009 @ 01:28 PM
Sorry about the formatting. That was:
enum = ['foo', 'bar', ''] enum.every.empty?.all? #=> false enum.every.empty?.any? #=> true enum.every.respond_to?(:to_s).all? #=> true enum.every.blank?.any? #=> true enum.every.nil?.any? #=> false # ...
-
mynyml April 7th, 2009 @ 01:31 PM
As for calling #every by itself, I agree it should be handled more gracefully; I'll work on an update this afternoon.
-
Peter Wagenet April 7th, 2009 @ 02:34 PM
The thing that got me interested in the respond_to? method was that Ruby likes to call the respond_to? method internally. And, when it gets back an array, is sees that as equivalent to true. In the case of puts, it calls every.respond_to?(:to_ary). Even if this array is [false, false, false...] it thinks that every does indeed respond to :to_ary. It then proceeds to call every.to_ary which blows up when not all of the elements do indeed respond to :to_ary. I guess the question is how much do you tighten the flexibility of the Every proxy to give Ruby (and Rails) responses they expect. My inclination is that some tightening is good in the most widely used methods, especially in question methods. You can work around this with proxy_send where necessary. I do also like the idea of the all? and any? methods, but that doesn't fix the problem of internal Ruby calls.
-
mynyml April 9th, 2009 @ 02:18 AM
I've been looking at how this can be improved, and it seems like there are two options:
-
Add special handling for #respond_to?, and warn the users in the method's documentation, letting them know they can use #proxy_send if they really want to use #respond_to? on #every.
-
Don't add special handling for #respond_to?, and caution the users in the method's documentation about using #every by itself.
I'm inclined to go with 2. for a number of reasons;
First, as I mentioned previously, it breaks consistency. There were personally a few occasions where I used every.respond_to? so I think it's relatively normal usage.
Also, the problem you describe above comes from a very specific context: #every called by itself, and as argument to a method (#puts). Pretty much an edge case. And even then, it behaves consistently (NoMethodError: undefined method
to_ary' for [axc](String)), so it's obvious that #every is trying to call #to_ary on each object. In fact, I don't think it's necessary to mention it in the method's documentation.
Keeping the original behaviour removes the need for #proxy_send, so it avoids adding an extra two methods (along with #respond_to?) leaving the proxy simple, lean and clean.
-
-
mynyml April 9th, 2009 @ 02:22 AM
Add updated the original patch to contain Peter's #proxy_target and #proxy_method if someone happens to prefer this.
-
Peter Wagenet April 9th, 2009 @ 02:34 AM
I see your point about consistency. What was confusing to me was not why #to_ary was breaking, but that is was calling #to_ary at all. Which, as I've already explained was due to a behind the scenes call to #respond_to? and the fact that the returned array was interpreted as equivalent to true.
-
mynyml April 9th, 2009 @ 02:34 AM
Oops. wrote with typos, patched with fail. Forgot to update the proxy methods whitelist.
-
Scott Taylor April 11th, 2009 @ 12:51 AM
I don't want this sort of method chaining in my environment. I don't like Symbol#to_proc much either, though, so maybe I'm in the minority.
Is this for internal rails usage (to replace Symbol#to_proc in internal usage), or for a user of the library (active_record, etc)?
That question also goes for Symbol#to_proc.
-
mynyml April 11th, 2009 @ 05:07 PM
Being pure ruby, #every is understandably slower than native c Enumerable#map, and slower than the Symbol#to_proc alternative (which is built into ruby since 1.8.7), so I'm not proposing it be used for rails internals, but simply as an alternative tool for the user.
Also, if you take a look at the implementation you will notice that the logic is very well contained; so if your comment about not wanting "this sort of method chaining in my environment" comes from a fear of seeing "too much magic" or hard to debug errors, I don't think it should be much of an issue. Of course you are free to avoid using Symbol#to_proc or Enumerable#every in your own code.
-
Pratik May 17th, 2009 @ 03:21 PM
- Assigned user set to Jeremy Kemper
- State changed from new to wontfix
I have mixed feelings about the patch. While it does make the code more readable, I'm not sure if the implementation complexity is worth it. Additionally, Sym#to_proc is a part of Ruby 1.9 already.
Closing as "wontfix" and assigning to Jeremy as he may have more to say.
Create your profile
Help contribute to this project by taking a few moments to create your personal profile. Create your profile »
<h2 style="font-size: 14px">Tickets have moved to Github</h2>
The new ticket tracker is available at <a href="https://github.com/rails/rails/issues">https://github.com/rails/rails/issues</a>