Forum: Ruby Ruby syntax question: instance.method(args) { more_stuff }

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Nic D. (Guest)
on 2007-05-01 14:15
(Received via mailing list)
Hi, I'm having difficulty working out the following construct:
img.write("myimage.jpg") { self.quality = 50 }
In this example (source
http://www.simplesystems.org/RMagick/doc/comtasks.html), img is a
RMagick::Image.

The API says "You may also specify optional arguments by setting
Image::Info <http://www.simplesystems.org/RMagick/doc/info.html>
attributes in an associated block." Is this a standard syntax in Ruby or
is it specific to this write method? And how does it work, how can I
make my own method accept blocks too? any advantage over passing a array
of arguments?

Also, I'm trying to pass my own instance variable in order to replace
the sample value of 50. So I've tried:

img.write("myimage.jpg") { self.quality = @my_object_quality }

Where @my_object_quality is defined as an instance variable on my class
def. But the interpreters seams to look for a @my_object_quality
instance variable *within* the RMagick class, which vaguely kind of make
sense. So how can I pass this instance variable into this method? and
why does it say "self.quality = 50" in the example and not "@quality =
50"?

Thanks in adance for your help. I'm looking for a genuine explanation of
what's happening at a Ruby syntax level, not just a code sample that
would work with this RMagick example.

Nic
Harry (Guest)
on 2007-05-01 14:26
(Received via mailing list)
On 5/1/07, Nic D. <removed_email_address@domain.invalid> wrote:
> how can I
> make my own method accept blocks too?
>
>

Here is one explanation.

http://www.rubycentral.com/book/tut_containers.html#UD

Harry
Brian C. (Guest)
on 2007-05-01 15:05
(Received via mailing list)
On Tue, May 01, 2007 at 07:14:43PM +0900, Nic D. wrote:
> The API says "You may also specify optional arguments by setting
> Image::Info <http://www.simplesystems.org/RMagick/doc/info.html>
> attributes in an associated block." Is this a standard syntax in Ruby or
> is it specific to this write method? And how does it work, how can I
> make my own method accept blocks too? any advantage over passing a array
> of arguments?

It's fairly standard. It has the advantage of only being run when
required,
e.g. zero times if no object ends up being created, or it could be run
multiple times. Compare:

    a = Array.new(2) { [] }
    a[0] << 'bar'
    a[1] << 'bap'

Here the initialization block is run twice, so you get two different
empty
arrays. This is different to

    a = Array.new(2, [])
    a[0] << 'bar'
    a[1] << 'bap'

Try it in irb, and look at 'a' afterwards in both cases.

For another example of this pattern, look at dependency injection, e.g.
http://wikis.onestepback.org/OSB/page/show/Depende...
(site appears to be very slow at present)

In this case the advantage is that the initialization code is run at the
latest possible time; at the time the block is created not all the
values
are known.

How to do it yourself?

  class Foo
    attr_accessor :x
    def initialize(&blk)
      instance_eval(&blk) if blk
    end
  end
  a = Foo.new { self.x = 99 }
  puts a.x

> Also, I'm trying to pass my own instance variable in order to replace
> the sample value of 50. So I've tried:
>
> img.write("myimage.jpg") { self.quality = @my_object_quality }
>
> Where @my_object_quality is defined as an instance variable on my class
> def. But the interpreters seams to look for a @my_object_quality
> instance variable *within* the RMagick class, which vaguely kind of make
> sense. So how can I pass this instance variable into this method?

Simplest just to use a local variable:

   foo = @my_object_quality
   .... { self.quality = foo }

If there are lots of these, then pass an instance of yourself, as long
as
you have public accessor methods.

   template = self
   ... { self.quality = template.quality }

> and
> why does it say "self.quality = 50" in the example and not "@quality =
> 50"?

I guess it's just standard accessor syntax, where it's considered
cleaner to
call a setter method (which might implement additional logic) rather
than
twiddle directly with instance variables in an object, which are usually
considered 'private'

Brian.
Tim H. (Guest)
on 2007-05-01 16:42
Brian C. wrote:
> On Tue, May 01, 2007 at 07:14:43PM +0900, Nic D. wrote:
(...)
>> why does it say "self.quality = 50" in the example and not "@quality =
>> 50"?
>
> I guess it's just standard accessor syntax, where it's considered
> cleaner to
> call a setter method (which might implement additional logic) rather
> than
> twiddle directly with instance variables in an object, which are usually
> considered 'private'
>
> Brian.

Brian's explanation is correct. I learned this technique from the 1st
edition of Hal F.'s "The Ruby Way." I think I remember seeing it in
the 2nd edition, too, although I don't have my copy with me to check.

I'm using this technique to support the dozen or so optional arguments
that Image#write can accept. Overall it works well, although since the
block is executed in the context of an RMagick object, all that object's
instance variables are exposed to the caller. RMagick mitigates this
problem by running the block in the context of an Image::Info object,
which doesn't have any private data or methods, just attribute
accessors.
This topic is locked and can not be replied to.