Using method missing to create getters and setters

Hi,
I am trying to create a model (BatchExternalBooking) which has the
following methods:
BatchExternalBooking.message_thread_1=
BatchExternalBooking.message_thread_1
BatchExternalBooking.message_thread_2=
BatchExternalBooking.message_thread_2
... etc all the way upto
BatchExternalBooking.message_thread_x=
BatchExternalBooking.message_thread_x

I assume that I need to do this via method_missing because I don't know
how many of these methods I will actually need.

My code currently looks like this:
class BatchExternalBooking
  def method_missing(method_sym, *args)
    if method_sym.to_s =~ /^message_thread_([0-9]*)=?(\w*)?$/
      BatchExternalBooking.instance_eval "attr_accessor
:message_thread_#{$1}"
      self.send("message_thread_#{$1}=", $2)
    else
      super
    end
  end
end

However, this is not working. See below:

b = BatchExternalBooking.new

=> #<BatchExternalBooking:0x3e54040>

b.message_thread_1 = 45

=> 45

b.message_thread_1

=> ""
The value wasn't actually set but the attr_accessor correctly created
the setter method. If I try and set the variable again, it works:

b.message_thread_1 = 45

=> 45

b.message_thread_1

=> 45

Why isn't the setter working the first time round?

Thanks a lot.

···

--
Posted via http://www.ruby-forum.com/\.

Hmm seems a little confusing with the $1 in the evals and $2 is
outright wrong you mean args.first

def method_missing sym, *args
   name = sym.to_s
   aname = name.sub("=","")

   super unless aname =~ /whatever/

   self.class.module_eval do # just a matter of taste
      attr_accessor aname
   end
  send name, args.first unless aname == name
end

HTH
Robert

Tim Conner wrote:

Hi,
I am trying to create a model (BatchExternalBooking) which has the
following methods:
BatchExternalBooking.message_thread_1=
BatchExternalBooking.message_thread_1
BatchExternalBooking.message_thread_2=
BatchExternalBooking.message_thread_2
... etc all the way upto
BatchExternalBooking.message_thread_x=
BatchExternalBooking.message_thread_x

I assume that I need to do this via method_missing because I don't know
how many of these methods I will actually need.

My code currently looks like this:
class BatchExternalBooking
  def method_missing(method_sym, *args)
    if method_sym.to_s =~ /^message_thread_([0-9]*)=?(\w*)?$/
      BatchExternalBooking.instance_eval "attr_accessor
:message_thread_#{$1}"
      self.send("message_thread_#{$1}=", $2)
    else
      super
    end
  end
end

However, this is not working. See below:

b = BatchExternalBooking.new

=> #<BatchExternalBooking:0x3e54040>

b.message_thread_1 = 45

=> 45

b.message_thread_1

=> ""
The value wasn't actually set but the attr_accessor correctly created
the setter method. If I try and set the variable again, it works:

b.message_thread_1 = 45

=> 45

b.message_thread_1

=> 45

Why isn't the setter working the first time round?

Thanks a lot.

Two thoughts. First, why not use an array? Then

BatchExternalBooking.message_thread[n] = whatever

If that doesn't work for you, then investigate OpenStruct (i.e. 'ostruct').

···

--
RMagick: http://rmagick.rubyforge.org/

Hi,
I am trying to create a model (BatchExternalBooking) which has the
following methods:
BatchExternalBooking.message_thread_1=
BatchExternalBooking.message_thread_1
BatchExternalBooking.message_thread_2=
BatchExternalBooking.message_thread_2
.. etc all the way upto
BatchExternalBooking.message_thread_x=
BatchExternalBooking.message_thread_x

I assume that I need to do this via method_missing because I don't know
how many of these methods I will actually need.

My code currently looks like this:
class BatchExternalBooking
  def method_missing(method_sym, *args)
    if method_sym.to_s =~ /^message_thread_([0-9]*)=?(\w*)?$/
      BatchExternalBooking.instance_eval "attr_accessor
:message_thread_#{$1}"

You might rather want to use class_eval here.

Also, as Robert pointed out already, using $1 only once and storing the value in a local variable is safer because it can be changed by any method you invoke.

      self.send("message_thread_#{$1}=", $2)
    else
      super
    end
  end
end

However, this is not working. See below:

b = BatchExternalBooking.new

=> #<BatchExternalBooking:0x3e54040>

b.message_thread_1 = 45

=> 45

b.message_thread_1

=> ""
The value wasn't actually set but the attr_accessor correctly created
the setter method. If I try and set the variable again, it works:

b.message_thread_1 = 45

=> 45

b.message_thread_1

=> 45

Why isn't the setter working the first time round?

Thanks a lot.

Is there a reason that you do not use OpenStruct or an ordinary Array for this? It seems you are indexing by number anyway so why not use an Array?

Kind regards

  robert

···

On 26.04.2009 19:40, Tim Conner wrote:

Why the string eval?

BatchExternalBooking.send :attr_accessor, :"message_thread_#{$1}"

Sorry, I'm a pedant about things like that... Also, you might want to check
that this does the right thing when the getter is called before the setter. At
least, your method_missing regex seems to assume that this might be the case,
but your code doesn't.

···

On Sunday 26 April 2009 12:40:05 Tim Conner wrote:

      BatchExternalBooking.instance_eval "attr_accessor

The reason that I am trying to use methods/attributes to store the
information rather than an array is because this model is used to create
a batch update form in a Rails project. If one of the updates fails
then I want to display the errors on the form next to the relevant
fields. For this to work, I will need to use the error_messages_for
helper to which I need to pass the attribute.

Am I approaching this in the correct manner?

···

--
Posted via http://www.ruby-forum.com/.

My code currently looks like this:
class BatchExternalBooking
def method_missing(method_sym, *args)
if method_sym.to_s =~ /^message_thread_([0-9]*)=?(\w*)?$/
BatchExternalBooking.instance_eval "attr_accessor
:message_thread_#{$1}"

You might rather want to use class_eval here.

No instance_eval works just as well for sending attr_accessor to a
class/module. The only difference with class_eval is when using def to
define a method.

Both do the evaluation in the context of the receive but class_eval
sets the current class to the receiver.

Class.class_eval("def foo;end") defines an instance method while
Class.instance_eval("def foo;end") defines a class method.

This seems a bit less odd when you consider that a class method is
just a singleton method on the class (albeit one which can be
inherited by subclasses).

Also, as Robert pointed out already, using $1 only once and storing the
value in a local variable is safer because it can be changed by any method
you invoke.

 self\.send\(&quot;message\_thread\_\#\{$1\}=&quot;, $2\)

That's not true either. The $n variables are frame local, in a given
invocation frame they will only change when another regex match is
done in the same invocation. The bug here is that this line should
have been

       self.send("message_thread_#{$1}=", *args) # or args.first if you must.

Since the regexp only had a single capture, $2 is always nil, so even
though the newly created setter is geting called, it's setting the
instance variable to nil.

···

On Sun, Apr 26, 2009 at 3:30 PM, Robert Klemme <shortcutter@googlemail.com> wrote:

On 26.04.2009 19:40, Tim Conner wrote:

--
Rick DeNatale

Blog: http://talklikeaduck.denhaven2.com/
Twitter: http://twitter.com/RickDeNatale
WWR: http://www.workingwithrails.com/person/9021-rick-denatale
LinkedIn: http://www.linkedin.com/in/rickdenatale

Well apart that Robert has said this already nothing to be sorry about. :wink:
This is a very concise implementation and I prefer it to mine.
Cheers
Robert

···

On Mon, Apr 27, 2009 at 11:44 PM, David Masover <ninja@slaphack.com> wrote:

On Sunday 26 April 2009 12:40:05 Tim Conner wrote:

  BatchExternalBooking\.instance\_eval &quot;attr\_accessor

Why the string eval?

BatchExternalBooking.send :attr_accessor, :"message_thread_#{$1}"

Sorry, I'm a pedant about things like that... Also, you might want to check

My code currently looks like this:
class BatchExternalBooking
def method_missing(method_sym, *args)
   if method_sym.to_s =~ /^message_thread_([0-9]*)=?(\w*)?$/
     BatchExternalBooking.instance_eval "attr_accessor
:message_thread_#{$1}"

You might rather want to use class_eval here.

No instance_eval works just as well for sending attr_accessor to a
class/module. The only difference with class_eval is when using def to
define a method.

I did not want to state that instance_eval is the issue. Sorry for being imprecise. A simple

BatchExternalBooking.send "attr_accessor", "message_thread_#$1"

would be sufficient.

Also, as Robert pointed out already, using $1 only once and storing the
value in a local variable is safer because it can be changed by any method
you invoke.

     self.send("message_thread_#{$1}=", $2)

That's not true either. The $n variables are frame local, in a given
invocation frame they will only change when another regex match is
done in the same invocation.

A simple test verifies this to be true. But now I wonder how I have come to this misconception. I am pretty sure I stored $1 in a local variable to avoid issues with changing values. Maybe I just had another match in the same method and extended the overwriting problem to method calls.

Thank you for the education, Rick!

Kind regards

  robert

···

On 26.04.2009 21:55, Rick DeNatale wrote:

On Sun, Apr 26, 2009 at 3:30 PM, Robert Klemme > <shortcutter@googlemail.com> wrote:

On 26.04.2009 19:40, Tim Conner wrote:

Tim Conner wrote:

The reason that I am trying to use methods/attributes to store the
information rather than an array is because this model is used to create
a batch update form in a Rails project. If one of the updates fails
then I want to display the errors on the form next to the relevant
fields. For this to work, I will need to use the error_messages_for
helper to which I need to pass the attribute.

Am I approaching this in the correct manner?

I believe that ostruct by itself will do the job, or you can use
method_missing to delegate to a hash without actually defining any
methods.

At least, I know this works for form helpers. You'd have to test it with
validations and error_messages_for.

···

--
Posted via http://www.ruby-forum.com/\.