Denis Redozubov
2013-Aug-12 11:13 UTC
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. -- You received this message because you are subscribed to the Google Groups "Ruby on Rails: Core" group. To unsubscribe from this group and stop receiving emails from it, send an email to rubyonrails-core+unsubscribe@googlegroups.com. To post to this group, send email to rubyonrails-core@googlegroups.com. Visit this group at http://groups.google.com/group/rubyonrails-core. For more options, visit https://groups.google.com/groups/opt_out.
Amiel Martin
2013-Aug-12 23:25 UTC
Re: Creating Proxy-containers with ActiveSupport''s delegate, proposing return_value_of keyword argument
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 http://carnesmedia.com On Mon, Aug 12, 2013 at 4:13 AM, Denis Redozubov <denis.redozubov@gmail.com>wrote:> 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. > > -- > You received this message because you are subscribed to the Google Groups > "Ruby on Rails: Core" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to rubyonrails-core+unsubscribe@googlegroups.com. > To post to this group, send email to rubyonrails-core@googlegroups.com. > Visit this group at http://groups.google.com/group/rubyonrails-core. > For more options, visit https://groups.google.com/groups/opt_out. > > >-- You received this message because you are subscribed to the Google Groups "Ruby on Rails: Core" group. To unsubscribe from this group and stop receiving emails from it, send an email to rubyonrails-core+unsubscribe@googlegroups.com. To post to this group, send email to rubyonrails-core@googlegroups.com. Visit this group at http://groups.google.com/group/rubyonrails-core. For more options, visit https://groups.google.com/groups/opt_out.
Duncan Beevers
2013-Aug-12 23:36 UTC
Re: Creating Proxy-containers with ActiveSupport''s delegate, proposing return_value_of keyword argument
These types of APIs are typically referred to as Fluent Interfaces, so adopting that nomenclature seems appropriate. On Mon, Aug 12, 2013 at 4:25 PM, Amiel Martin <amiel@carnesmedia.com> wrote:> 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 > > > http://carnesmedia.com > > > On Mon, Aug 12, 2013 at 4:13 AM, Denis Redozubov < > denis.redozubov@gmail.com> wrote: > >> 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. >> >> -- >> You received this message because you are subscribed to the Google Groups >> "Ruby on Rails: Core" group. >> To unsubscribe from this group and stop receiving emails from it, send an >> email to rubyonrails-core+unsubscribe@googlegroups.com. >> To post to this group, send email to rubyonrails-core@googlegroups.com. >> Visit this group at http://groups.google.com/group/rubyonrails-core. >> For more options, visit https://groups.google.com/groups/opt_out. >> >> >> > > -- > You received this message because you are subscribed to the Google Groups > "Ruby on Rails: Core" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to rubyonrails-core+unsubscribe@googlegroups.com. > To post to this group, send email to rubyonrails-core@googlegroups.com. > Visit this group at http://groups.google.com/group/rubyonrails-core. > For more options, visit https://groups.google.com/groups/opt_out. > > >-- You received this message because you are subscribed to the Google Groups "Ruby on Rails: Core" group. To unsubscribe from this group and stop receiving emails from it, send an email to rubyonrails-core+unsubscribe@googlegroups.com. To post to this group, send email to rubyonrails-core@googlegroups.com. Visit this group at http://groups.google.com/group/rubyonrails-core. For more options, visit https://groups.google.com/groups/opt_out.
Matt Jones
2013-Aug-13 22:58 UTC
Re: Creating Proxy-containers with ActiveSupport''s delegate, proposing return_value_of keyword argument
On Aug 12, 2013, at 7:25 PM, Amiel Martin wrote:> 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.We already have a DSL for specifying the return value of a method: it''s called RUBY. :) 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 -- You received this message because you are subscribed to the Google Groups "Ruby on Rails: Core" group. To unsubscribe from this group and stop receiving emails from it, send an email to rubyonrails-core+unsubscribe@googlegroups.com. To post to this group, send email to rubyonrails-core@googlegroups.com. Visit this group at http://groups.google.com/group/rubyonrails-core. For more options, visit https://groups.google.com/groups/opt_out.
Aaron Patterson
2013-Aug-16 05:09 UTC
Re: Creating Proxy-containers with ActiveSupport''s delegate, proposing return_value_of keyword argument
On Tue, Aug 13, 2013 at 06:58:42PM -0400, Matt Jones wrote:> > On Aug 12, 2013, at 7:25 PM, Amiel Martin wrote: > > > 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. > > We already have a DSL for specifying the return value of a method: it''s called RUBY. :) > > 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.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. -- Aaron Patterson http://tenderlovemaking.com/ -- You received this message because you are subscribed to the Google Groups "Ruby on Rails: Core" group. To unsubscribe from this group and stop receiving emails from it, send an email to rubyonrails-core+unsubscribe@googlegroups.com. To post to this group, send email to rubyonrails-core@googlegroups.com. Visit this group at http://groups.google.com/group/rubyonrails-core. For more options, visit https://groups.google.com/groups/opt_out.