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.

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 C. wrote:

else

b.message_thread_1
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’).

On 26.04.2009 19:40, Tim C. wrote:

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.

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

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?

On Sun, Apr 26, 2009 at 3:30 PM, Robert K.
[email protected] wrote:

On 26.04.2009 19:40, Tim C. wrote:

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("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. 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.


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

On 26.04.2009 21:55, Rick DeNatale wrote:

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

Tim C. 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.

On Mon, Apr 27, 2009 at 11:44 PM, David M. [email protected]
wrote:

On Sunday 26 April 2009 12:40:05 Tim C. wrote:

  BatchExternalBooking.instance_eval "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
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 Sunday 26 April 2009 12:40:05 Tim C. wrote:

  BatchExternalBooking.instance_eval "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
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.