Calling an arbitrary method as and when an array's contents change

I have a collection class that extends the Array class and I wish to
have a particular method to be called whenever the contents of an
instance of the class change. Can anyone advise on how I can achieve
this?

I'm aware that I can compare #hash with a previous saved value to
determine if the contents of the instance have changed since the last
time #hash was called. Therefore, if I can find a means of calling an
arbitrary method or piece of code every time any method is called on
the instance, I could achieve what I'm after. Can I override
Object#send?

It sounds like you might be able to set up something like this with Observer.
http://www.ruby-doc.org/stdlib/libdoc/observer/rdoc/index.html

···

On 7/5/07, Dan Stevens (IAmAI) <dan.stevens.iamai@gmail.com> wrote:

I have a collection class that extends the Array class and I wish to
have a particular method to be called whenever the contents of an
instance of the class change. Can anyone advise on how I can achieve
this?

I'm aware that I can compare #hash with a previous saved value to
determine if the contents of the instance have changed since the last
time #hash was called. Therefore, if I can find a means of calling an
arbitrary method or piece of code every time any method is called on
the instance, I could achieve what I'm after. Can I override
Object#send?

For this purpose, it's probably better to wrap/delegate to Array than extend it.

That way, you can have control of all the methods that change something, and
guarantee that they will perform the appropriate notification. I'd also
recommend looking at Observer for the notification of the equation.

-mental

···

On Fri, 6 Jul 2007 07:13:31 +0900, "Dan Stevens (IAmAI)" <dan.stevens.iamai@gmail.com> wrote:

I have a collection class that extends the Array class and I wish to
have a particular method to be called whenever the contents of an
instance of the class change. Can anyone advise on how I can achieve
this?

It sounds like you might be able to set up something like this with Observer.

Ah! I'd forgotten about the observer pattern. That should do the trick. Thanks!

···

On 05/07/07, Gregory Brown <gregory.t.brown@gmail.com> wrote:

On 7/5/07, Dan Stevens (IAmAI) <dan.stevens.iamai@gmail.com> wrote:
> I have a collection class that extends the Array class and I wish to
> have a particular method to be called whenever the contents of an
> instance of the class change. Can anyone advise on how I can achieve
> this?
>
> I'm aware that I can compare #hash with a previous saved value to
> determine if the contents of the instance have changed since the last
> time #hash was called. Therefore, if I can find a means of calling an
> arbitrary method or piece of code every time any method is called on
> the instance, I could achieve what I'm after. Can I override
> Object#send?

It sounds like you might be able to set up something like this with Observer.
http://www.ruby-doc.org/stdlib/libdoc/observer/rdoc/index.html

Great. Houlihans is quiet enough for us to talk. 5:00pm work for you?

···

Sent from my iPhone

On Jul 5, 2007, at 5:26 PM, MenTaLguY <mental@rydia.net> wrote:

On Fri, 6 Jul 2007 07:13:31 +0900, "Dan Stevens (IAmAI)" <dan.stevens.iamai@gmail.com > > wrote:

I have a collection class that extends the Array class and I wish to
have a particular method to be called whenever the contents of an
instance of the class change. Can anyone advise on how I can achieve
this?

For this purpose, it's probably better to wrap/delegate to Array than extend it.

That way, you can have control of all the methods that change something, and
guarantee that they will perform the appropriate notification. I'd also
recommend looking at Observer for the notification of the equation.

-mental

Ah! I'd forgotten about the observer pattern. That should do the trick. Thanks!

After looking at the Observable module, it only really solves half of
my problem. While it's a nice mechanism for notify observers that an
object has changed, it does help me determine whether or not an object
has changed; it leaves that up to the programmer.

For this purpose, it's probably better to wrap/delegate to Array than extend it.

This is a potential solution - I could override all the methods of the
Array class that I think modify the contents of the Array in a
superclass, calling Observable#changed before/after calling the
parent's method. However, what if I think there's one too many methods
to be overriding or a forget to override one?

Truth is, I've now realised that I actually don't *need* an answer to
this question to solve my problem (helps to have full insight into
one's problems first). However, for the sake of argument and my
curiosity, does anyone thing there's an easier and more reliable
solution?

···

On 05/07/07, MenTaLguY <mental@rydia.net> wrote:

On Fri, 6 Jul 2007 07:13:31 +0900, "Dan Stevens (IAmAI)" <dan.stevens.iamai@gmail.com> wrote:
> I have a collection class that extends the Array class and I wish to
> have a particular method to be called whenever the contents of an
> instance of the class change. Can anyone advise on how I can achieve
> this?

For this purpose, it's probably better to wrap/delegate to Array than extend it.

That way, you can have control of all the methods that change something, and
guarantee that they will perform the appropriate notification. I'd also
recommend looking at Observer for the notification of the equation.

-mental

Hi --

···

On Fri, 6 Jul 2007, Micah Martin wrote:

Great. Houlihans is quiet enough for us to talk. 5:00pm work for you?

You forgot to tell us which Houlihans :slight_smile:

David

--
* Books:
   RAILS ROUTING (new! http://www.awprofessional.com/title/0321509242\)
   RUBY FOR RAILS (http://www.manning.com/black\)
* Ruby/Rails training
     & consulting: Ruby Power and Light, LLC (http://www.rubypal.com)

Great. Houlihans is quiet enough for us to talk. 5:00pm work for you?

We will be there!

Be sure to grab a big enough table to hold us. A few thousand will be fine.

Sent from my iPhone

Does it help you see _who_ you are sending messages to by chance? :wink:

James Edward Gray II

···

On Jul 5, 2007, at 5:57 PM, Micah Martin wrote:

Um.... do you really need an observable array or are you building an
object that uses an array under the hood that needs to be observable?
Big difference there.

In the former case, you'll likely want to use a blank slate proxy that
does the notification on delegation. On the latter case it's really
easy, just notify on the methods you're actually using. :slight_smile:

Word on the street is that subclassing core classes to change
behaviour is a bad idea anyway. You might risk your subclass methods
not getting called in favor of the C methods being called without you
knowing...

···

On 7/5/07, Dan Stevens (IAmAI) <dan.stevens.iamai@gmail.com> wrote:

> Ah! I'd forgotten about the observer pattern. That should do the trick. Thanks!

After looking at the Observable module, it only really solves half of
my problem. While it's a nice mechanism for notify observers that an
object has changed, it does help me determine whether or not an object
has changed; it leaves that up to the programmer.

> For this purpose, it's probably better to wrap/delegate to Array than extend it.

This is a potential solution - I could override all the methods of the
Array class that I think modify the contents of the Array in a
superclass, calling Observable#changed before/after calling the
parent's method. However, what if I think there's one too many methods
to be overriding or a forget to override one?

I changed my mind. Red Lobster is better.

···

On 7/5/07, Micah Martin <micah@8thlight.com> wrote:

Great. Houlihans is quiet enough for us to talk. 5:00pm work for you?

It's really not too much work:

#!/usr/bin/env ruby -wKU

require "observer"

class OArray
   instance_methods.each { |meth| undef_method(meth) unless meth =~ /\A__/ }

   include Observable

   def initialize(array)
     @array = array
   end

   def method_missing(meth, *args, &block)
     @array.send(meth, *args, &block)

     if %w{ = clear concat delete delete_at
            delete_if fill pop push replace shift }.include?(meth.to_s) or
        meth.to_s[-1] == ?!
       changed
       notify_observers(@array, meth, *args, &block)
     end
   end
end

class Array
   def to_oarray
     OArray.new(self)
   end
end

observer = Object.new
class << observer
   def update(array, *call)
     puts "You called #{call.first} and the Array is now #{array.inspect}."
   end
end

observed = [1, 2, 3].to_oarray
observed.add_observer(observer)
observed[1]
observed[1] = "2"

__END__

James Edward Gray II

···

On Jul 5, 2007, at 6:04 PM, Dan Stevens (IAmAI) wrote:

For this purpose, it's probably better to wrap/delegate to Array than extend it.

This is a potential solution - I could override all the methods of the
Array class that I think modify the contents of the Array in a
superclass, calling Observable#changed before/after calling the
parent's method. However, what if I think there's one too many methods
to be overriding or a forget to override one?

Hi --

···

On Fri, 6 Jul 2007, James Edward Gray II wrote:

On Jul 5, 2007, at 5:57 PM, Micah Martin wrote:

Great. Houlihans is quiet enough for us to talk. 5:00pm work for you?

We will be there!

Be sure to grab a big enough table to hold us. A few thousand will be fine.

Sent from my iPhone

Does it help you see _who_ you are sending messages to by chance? :wink:

Hey, it's only the first iteration of the iPhone -- you can't expect
all the bells and whistles yet :slight_smile:

David

--
* Books:
   RAILS ROUTING (new! http://www.awprofessional.com/title/0321509242\)
   RUBY FOR RAILS (http://www.manning.com/black\)
* Ruby/Rails training
     & consulting: Ruby Power and Light, LLC (http://www.rubypal.com)

Hi --

···

On Fri, 6 Jul 2007, Gregory Brown wrote:

On 7/5/07, Micah Martin <micah@8thlight.com> wrote:

Great. Houlihans is quiet enough for us to talk. 5:00pm work for you?

I changed my mind. Red Lobster is better.

I vote for Ruby Tuesday.

David

--
* Books:
   RAILS ROUTING (new! http://www.awprofessional.com/title/0321509242\)
   RUBY FOR RAILS (http://www.manning.com/black\)
* Ruby/Rails training
     & consulting: Ruby Power and Light, LLC (http://www.rubypal.com)

You think this will be a common iPhone thing?

···

On 7/5/07, dblack@wobblini.net <dblack@wobblini.net> wrote:

Hi --

On Fri, 6 Jul 2007, Micah Martin wrote:

> Great. Houlihans is quiet enough for us to talk. 5:00pm work for you?

You forgot to tell us which Houlihans :slight_smile:

David

I contemplated using the #hash method to determine if the array had changed:

[snip]

  def method_missing(meth, *args, &block)
    @last_hash = @array.hash
    @array.send(meth, *args, &block)
    if @array.hash != @last_hash
      changed
      notify_observers(@array, meth, *args, &block)
    end
  end

[snip]

That way I don't have to worry about which methods change the contents
of the array.

···

On 06/07/07, James Edward Gray II <james@grayproductions.net> wrote:

On Jul 5, 2007, at 6:04 PM, Dan Stevens (IAmAI) wrote:

>> For this purpose, it's probably better to wrap/delegate to Array
>> than extend it.
>
> This is a potential solution - I could override all the methods of the
> Array class that I think modify the contents of the Array in a
> superclass, calling Observable#changed before/after calling the
> parent's method. However, what if I think there's one too many methods
> to be overriding or a forget to override one?

It's really not too much work:

#!/usr/bin/env ruby -wKU

require "observer"

class OArray
   instance_methods.each { |meth| undef_method(meth) unless meth =~ /
\A__/ }

   include Observable

   def initialize(array)
     @array = array
   end

   def method_missing(meth, *args, &block)
     @array.send(meth, *args, &block)

     if %w{ = clear concat delete delete_at
            delete_if fill pop push replace shift }.include?
(meth.to_s) or
        meth.to_s[-1] == ?!
       changed
       notify_observers(@array, meth, *args, &block)
     end
   end
end

class Array
   def to_oarray
     OArray.new(self)
   end
end

observer = Object.new
class << observer
   def update(array, *call)
     puts "You called #{call.first} and the Array is now #
{array.inspect}."
   end
end

observed = [1, 2, 3].to_oarray
observed.add_observer(observer)
observed[1]
observed[1] = "2"

__END__

James Edward Gray II

Yeah, Houlihans might be at Bennigan's.

···

On 7/5/07, dblack@wobblini.net <dblack@wobblini.net> wrote:

Hi --

On Fri, 6 Jul 2007, Gregory Brown wrote:

> On 7/5/07, Micah Martin <micah@8thlight.com> wrote:
>> Great. Houlihans is quiet enough for us to talk. 5:00pm work for you?
>
> I changed my mind. Red Lobster is better.

I vote for Ruby Tuesday.

David

Maybe that's a clever marketing ploy. Apple has joined forces with Houlihans!

···

On 7/5/07, Todd Benson <caduceass@gmail.com> wrote:

On 7/5/07, dblack@wobblini.net <dblack@wobblini.net> wrote:
> Hi --
>
> On Fri, 6 Jul 2007, Micah Martin wrote:
>
> > Great. Houlihans is quiet enough for us to talk. 5:00pm work for you?
>
> You forgot to tell us which Houlihans :slight_smile:
>
> David

You think this will be a common iPhone thing?

First Google, then AT&T, and now Houlihans! The trifecta of evil
corporation partnerships is complete! Our world is going to be taken
over! I for one welcome our stylish, heavily indexed, delicious
tasting, cellular providing overlords!

···

On 7/5/07, Gregory Brown <gregory.t.brown@gmail.com> wrote:

Maybe that's a clever marketing ploy. Apple has joined forces with Houlihans!

--
Chris Carter
concentrationstudios.com
brynmawrcs.com