[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Using method missing to create getters and setters

Tim Conner

4/26/2009 5:40:00 PM

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

9 Answers

Robert Dober

4/26/2009 6:49:00 PM

0

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

Robert Klemme

4/26/2009 7:26:00 PM

0

On 26.04.2009 19:40, 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}"

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

Tim Hunter

4/26/2009 7:28:00 PM

0

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.ruby...

Tim Conner

4/26/2009 7:50:00 PM

0

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

Rick DeNatale

4/26/2009 7:55:00 PM

0

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

>> My code currently looks like this:
>> class BatchExternalBooking
>> =A0def method_missing(method_sym, *args)
>> =A0 =A0if method_sym.to_s =3D~ /^message_thread_([0-9]*)=3D?(\w*)?$/
>> =A0 =A0 =A0BatchExternalBooking.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 metho=
d
> you invoke.
>
>> =A0 =A0 =A0self.send("message_thread_#{$1}=3D", $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}=3D", *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.

--=20
Rick DeNatale

Blog: http://talklikeaduck.denh...
Twitter: http://twitter.com/Ri...
WWR: http://www.workingwithrails.com/person/9021-ric...
LinkedIn: http://www.linkedin.com/in/ri...

Robert Klemme

4/27/2009 6:14:00 AM

0

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:
>
>>> 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

Brian Candler

4/27/2009 8:21:00 AM

0

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

David Masover

4/27/2009 9:45:00 PM

0

On Sunday 26 April 2009 12:40:05 Tim Conner 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.

Robert Dober

4/28/2009 2:19:00 PM

0

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:
>> =A0 =A0 =A0 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 che=
ck
Well apart that Robert has said this already nothing to be sorry about. ;)
This is a very concise implementation and I prefer it to mine.
Cheers
Robert