Creating Proxy-containers with ActiveSupport's delegate, proposing return_value_of keyword argument

Hello guys!

I’d like to propose new feature for ActiveSupport Module’s delegate method. I’ve actually implemented it already and i want your opinion on this matter. Recently during my work hours, i was writing enumerable proxy-object to encapsulate some business-logic and i wanted it to mimic Array functionally as much as possible, though behave in slightly different way.

I wanted to do it with composition, because i wholeheartedly agree with Steve Klabnik’s article on ‘subclassing vs composition’ here:

http://words.steveklabnik.com/beware-subclassing-ruby-core-classes

Delegate method quickly came to my mind, though i had one concern: delegated methods on proxy object must return proxy object itself to enable chaining of filters and such.

I’ll demonstrate it with example, let’s look on standard delegation:

class ProxyContainer < Struct.new(:array)

delegate :select!, to: :array

end

proxy = ProxyContainer.new(%w{a b c})

=> #<struct ProxyContainer array=[“a”, “b”, “c”]>

proxy.select! { |el| el != ‘a’ }

=> [“b”, “c”]

If we want to apply some business-logic filter and such, residing in proxy object, we must assign intermediate value to some variable, we cannot chain methods.

On the opposite side look at this delegation example with return_value_of in use:

class ProxyContainer < Struct.new(:brands)

delegate :select!, to: :brands, return_value_of: :self

def reject_unprofitable!

some business-logic here

end

def profitable

some business-logic here too

@brands # profitable brands

end

def business_criteria

criteria logic

end

end

Now we can do stuff like this:

proxy = ProxyContainer.new([#brand objects#])

=> #<struct ProxyContainer brands=[#brand objects#]>

proxy.select!(&:criteria).reject_unprofitable!

=> #<struct ProxyContainer brands=[#profitable brands#]>

If we want to use standard behaviour:

proxy.select!(&:criteria).profitable

=> [#profitable brands#]

It’s kind of switching default focus in a favor of proxy, making instance variable returns explicit in user-made logic.

Look at how simple it looks and feels. It doesn’t break backward compatibility and it will be easy to refactor, if it’ll come to this(maybe it is, by the looks of github issues).

I’ve added test and implementations and squashed to this commit:

https://github.com/dredozubov/rails/commit/81940715a2137ae765419d338d5c1704b1e54d8b

I can open parallel PR on github if needed.

I’ll be glad to update docs(or help to update them - my english isn’t perfect), if you will approve this one.

I like this idea and I would use it.

My only suggestion would be to normally return a new instance of the proxy object. This makes it easier to use this feature with immutable objects.

I know it would be possible to do this:

class ProxyContainer < Struct.new(:brands)

delegate :select, to: :brands, return_value_of: ‘self.class.new(result)’

end

but it’s not obvious that you could do this without reading through the definition.

I’m not sure what I would call it though. It almost seems like a different method than delegate. Maybe something like:

class ProxyContainer < Struct.new(:brands)

compose :select, with: :brands

end

Anyway, sorry for the rambling. Those are just my two cents.

-Amiel

These types of APIs are typically referred to as Fluent Interfaces, so adopting that nomenclature seems appropriate.

We already have a DSL for specifying the return value of a method: it’s called RUBY. :slight_smile:

Snark aside, I think adding too many bells-and-whistles to delegate is not the best idea. If you really want custom behavior, why not make it clearer and just write the methods?

At a minimum, code-as-string is kinda gross.

–Matt Jones

Basically this. There's so much going on here with the call to `delegate` that you may as well just write a Plain Old Method.