These days I have been comparing records in my daily job lots of times, which made me think about a better way to retrieve and compare them. When I want to navigate through several relations in a collection I often see myself writing code like the following:
Given orders as a collection of Order:
> orders.map(&:order_option).map(&:item).map(&:title)
=> ['Foo', 'Bar', 'Baz']
That is, chaining maps with Symbol to Proc coercions one after each other. Sharing my thoughts with my company’s architect we came up with the alternative:
> orders.map { |order| order&.order_option&.item&.title }
But we agreed that the notation was awful and didn’t improve what we had before. With this, I proposed what I think it is more like what we would expect Ruby to have. I would like to add a notation similar to the one we can find at Array#dig or Hash#dig in the following manner:
> orders.map(&:order_option, &:item, &:title)
The method doesn’t necessarily need to be named map or collect, we can agree on a different name for it if you want. Please share your thoughts with me. If you like this, I would be very happy to write a PR to include it in Rails.
Cheers,
Alberto Almagro
If you think that method would be useful there is no reason why it should be Rails specific. Please send a feature request to the Ruby issue tracker here https://bugs.ruby-lang.org/.
Rafael França
Hi Rafael,
I'm glad to see you here. The reason I see its place here at first is
because I think its use case fits better with the framework than with the
language. When thinking on the Ruby language I would expect more to deal
with collections which contain objets like String, Integer, and the likes,
while in Ruby on Rails you are much likely to have related collections
because of Active Record navigations that require this kind of map chaining.
Cheers,
Alberto Almagro
Hi Anthony,
thanks for your response. Great that you mention this. Yes I have seen
other Rubyists on the internet proposing a similar solution, but for me it
feels like a workaround, which could work in some cases, but it's not
ideal. My main objection for that is that it implies defining to_proc, not
only for Array, but for Enumerable, and you may need to define the method
for other purposes. For example, I have also seen other people defining
Array#to_proc using the first position of the provided array to hold the
method that will be sent to the collection object and the remaining
positions for the attributes that would be then passed to the method.
In the other hand, having the ability to pass more than one Symbol to the
map method only implies extending that method capabilities, thus limiting
the scope of the change to the method that we want to extend.
Cheers,
Alberto Almagro
These days I have been comparing records in my daily job lots of times, which made me think about a better way to retrieve and compare them. When I want to navigate through several relations in a collection I often see myself writing code like the following:
Given orders as a collection of Order:
> orders.map(&:order_option).map(&:item).map(&:title)
=> ['Foo', 'Bar', 'Baz']
That is, chaining maps with Symbol to Proc coercions one after each other. Sharing my thoughts with my company’s architect we came up with the alternative:
> orders.map { |order| order&.order_option&.item&.title }
Very small nitpick: the code above (which tolerates nil
) isn’t equivalent to the chained map (which doesn’t). But anyways…
But we agreed that the notation was awful and didn’t improve what we had before. With this, I proposed what I think it is more like what we would expect Ruby to have. I would like to add a notation similar to the one we can find at Array#dig or Hash#dig in the following manner:
> orders.map(&:order_option, &:item, &:title)
The method doesn’t necessarily need to be named map or collect, we can agree on a different name for it if you want. Please share your thoughts with me. If you like this, I would be very happy to write a PR to include it in Rails.
This reminds me of the Elixir function get_in
and the associated functions in Elixir.Access
. I’m not sure if any of the existing methods would make sense to extend with the behavior, though:
-
dig
is called on a collection and returns one element with many levels of nesting
-
pluck
is called on a collection and returns a collection, but only at one level of nesting
-
the proposed function is called on a collection and returns a collection, with many levels of nesting
Neither function can guarantee that its arguments are scalars (or even that they aren’t Procs, for that matter) so extending them is tricky. Probably better to pick a new name.
You might also consider “Proc#*” from Facets:
https://github.com/rubyworks/facets/blob/master/lib/core/facets/proc/compose.rb
I haven’t tried it, but in principle this should work if the operator precedence goes correctly:
orders.map(&:order_options * &:item * &:title)
—Matt Jones
Hi,
I am new to Ruby, can you please give me an example of the orders collection ?
Thanks.
Hi Matt,
thanks a lot for all your points and suggestions.
Having thought about naming, I’m also starting to think that picking a new name would be better. At least it would have the same mental effect as when you compare dig
vs [ ]
methods, dig
predisposes you to think on nested access.
Cheers,
Alberto
Hi Abdel,
sorry for the confusion, I guess I didn’t explain myself properly. orders
would be a collection composed by instances of Order model. For example, imagine you do Order.last(5)
to get the last 5 orders. I hope this clarifies the example.
Cheers,
Alberto
From the answers I got, I guess this isn’t a desired feature at the moment.
Anyway, thanks to everyone involved!