16

In Python, the with statement is used to make sure that clean-up code always gets called, regardless of exceptions being thrown or function calls returning. For example:

with open("temp.txt", "w") as f:
    f.write("hi")
    raise ValueError("spitespite")

Here, the file is closed, even though an exception was raised. A better explanation is here.

Is there an equivalent for this construct in Ruby? Or can you code one up, since Ruby has continuations?

Claudiu
  • 224,032
  • 165
  • 485
  • 680

7 Answers7

25

Ruby has syntactically lightweight support for literal anonymous procedures (called blocks in Ruby). Therefore, it doesn't need a new language feature for this.

So, what you normally do, is to write a method which takes a block of code, allocates the resource, executes the block of code in the context of that resource and then closes the resource.

Something like this:

def with(klass, *args)
  yield r = klass.open(*args)
ensure
  r.close
end

You could use it like this:

with File, 'temp.txt', 'w' do |f|
  f.write 'hi'
  raise 'spitespite'
end

However, this is a very procedural way to do this. Ruby is an object-oriented language, which means that the responsibility of properly executing a block of code in the context of a File should belong to the File class:

File.open 'temp.txt', 'w' do |f|
  f.write 'hi'
  raise 'spitespite'
end

This could be implemented something like this:

def File.open(*args)
  f = new(*args)
  return f unless block_given?
  yield f
ensure
  f.close if block_given?
end

This is a general pattern that is implemented by lots of classes in the Ruby core library, standard libraries and third-party libraries.


A more close correspondence to the generic Python context manager protocol would be:

def with(ctx)
  yield ctx.setup
ensure
  ctx.teardown
end

class File
  def setup; self end
  alias_method :teardown, :close
end

with File.open('temp.txt', 'w') do |f|
  f.write 'hi'
  raise 'spitespite'
end

Note that this is virtually indistinguishable from the Python example, but it didn't require the addition of new syntax to the language.

Allenaz
  • 1,013
  • 10
  • 14
Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • yea i agree it's a bad sign when your language needs new features. really the only reason you need 'with' in Python is because anonymous functions are impaired for no reason. by that logic Scheme is the best since you can implement new lang features yourself with macros =) – Claudiu Oct 06 '10 at 18:52
  • 2
    +1 It's very cool to be able to add this to the language on the fly (and with such a small amount of code). I think there is also an advantage of having a rich syntax in the language too however. Every Python developer knows what "with" means in Python, and it is well documented on the Python website/books. – John La Rooy Oct 06 '10 at 21:00
  • @Claudiu, Ruby can fake certain kinds of macros very well, see: `def my_if(condition, &block) block.call if condition; end` use like this: `my_if(x == 5) { puts "x is 5!" }` – horseyguy Oct 06 '10 at 21:50
  • 3
    Sorry, but I do not agree that "the responsibility of properly executing a block of code in the context of a File should belong to the File class". That's not how Python, or Java, or C#, or even Objective-C work, and I think those languages are pretty much object-oriented. Don't get me wrong, I can see the "beauty" of such statement, and I do respect the "Ruby way". But that's exactly what it is: a nice, elegant, extremely dynamic and expressive way of doing things, but not the "OO" way. In fact this is much more related to functional programming, not OO. – rsenna Oct 06 '10 at 22:24
  • 4
    Ruby has *a lot* more superfluous language features than python - the presence of `with` above of a block makes the context of everything scoped under it quite clear. – user3467349 Jun 18 '15 at 06:14
  • It's fine to comment about some elements of the difference between languages, but this is close to being a troll. This answer https://stackoverflow.com/a/3875616/500902 is much more succinct and answers the question without editorializing. The current answer does provide more information differnet options, however. – Marvin Nov 25 '17 at 14:54
  • The point of `with` is to explicitly mark points where resource management is happening in the background. The specific nature of that resource management is specified by class definitions. So the responsibility for properly executing a block of code in the context of a file *does* belong to the file class in Python. – senderle Jun 04 '20 at 22:39
12

The equivalent in Ruby would be to pass a block to the File.open method.

File.open(...) do |file|
  #do stuff with file
end  #file is closed

This is the idiom that Ruby uses and one that you should get comfortable with.

Ed S.
  • 122,712
  • 22
  • 185
  • 265
  • that's a particular case for this exact usage of 'with', but i'm interested in the generic construct – Claudiu Oct 06 '10 at 18:17
  • 4
    I don't know Python very well, but it looks like C#'s 'using' statement. There is no syntactic sugar like that in Ruby, but many of the library classes implement methods like I have shown above. – Ed S. Oct 06 '10 at 18:18
4

You could use Block Arguments to do this in Ruby:

class Object  
    def with(obj)  
        obj.__enter__  
        yield  
        obj.__exit__  
    end  
end

Now, you could add __enter__ and __exit__ methods to another class and use it like this:

with GetSomeObject("somefile.text") do |foo|  
    do_something_with(foo)
end  
leoluk
  • 12,561
  • 6
  • 44
  • 51
  • 1
    Ignoring that you need `ensure` to catch the exceptional cases, I both love and hate this solution. It shows how flexible Ruby is (so that it can almost approach Python syntax), and on the other hand... Why would you want to approach Python syntax in Ruby? :) – Amadan Oct 06 '10 at 18:42
  • 1
    yeah this is twisting the language for no reason. other answers are better. this si still awesom tho – Claudiu Oct 06 '10 at 18:46
  • I wouldn't do this in my code, but yes, it is cool (and a bit scary) that such things are possible :) – Ed S. Oct 06 '10 at 18:53
  • I don't think Ruby have a method named `__enter__` – Xuanyu May 27 '19 at 09:29
2

It's possible to write to a file atomically in Ruby, like so:

File.write("temp.txt", "hi")
raise ValueError("spitespite")

Writing code like this means that it is impossible to accidentally leave a file open.

2

I'll just add some more explanations for others; credit should go to them.

Indeed, in Ruby, clean-up code is as others said, in ensure clause; but wrapping things in blocks is ubiquitous in Ruby, and this is how it is done most efficiently and most in spirit of Ruby. When translating, don't translate directly word-for-word, you will get some very strange sentences. Similarly, don't expect everything from Python to have one-to-one correspondence to Ruby.

From the link you posted:

class controlled_execution:
    def __enter__(self):
        set things up
        return thing
    def __exit__(self, type, value, traceback):
        tear things down

with controlled_execution() as thing:
     some code

Ruby way, something like this (man, I'm probably doing this all wrong :D ):

def controlled_executor
  begin
    do_setup
    yield
  ensure
    do_cleanup
  end
end

controlled_executor do ...
  some_code
end

Obviously, you can add arguments to both controlled executor (to be called in a usual fashion), and to yield (in which case you need to add arguments to the block as well). Thus, to implement what you quoted above,

class File
  def my_open(file, mode="r")
    handle = open(file, mode)
    begin
      yield handle
    ensure
      handle.close
    end
  end
end

File.my_open("temp.txt", "w") do |f|
  f.write("hi")
  raise Exception.new("spitesprite")
end
Amadan
  • 191,408
  • 23
  • 240
  • 301
  • I think you're on to something.. I think ruby having blocks means that a special "with" statement is not necessary. i don't know ruby much, so is that just creating a block, passing it to controlled_executor, and that gets executed when "yield" is reached? – Claudiu Oct 06 '10 at 18:36
  • To write a bit more - Ruby having blocks means it can do really off-the-wall things with syntax. Check out examples for Sinatra, RSpec and Rake for some ideas of using blocks creatively. – Amadan Oct 06 '10 at 18:47
  • You can even (sort of) reimplement your own if/else in ruby using blocks: http://github.com/banister/custom_boolean/blob/master/README.markdown#readme – horseyguy Oct 06 '10 at 22:38
0

You could always use a try..catch..finally block, where the finally section contains code to clean up.

Edit: sorry, misspoke: you'd want begin..rescue..ensure.

mway
  • 4,334
  • 3
  • 26
  • 37
  • you can do this in Python, too. with is a nice convenience though, see the explanation link – Claudiu Oct 06 '10 at 18:19
  • Sorry, not so hot with Python. ;) AFAIK, there is no shorthand using effectively try/catch blocks. You're better off to use the blocks, though. – mway Oct 06 '10 at 18:24
0

I believe you are looking for ensure.

theIV
  • 25,434
  • 5
  • 54
  • 58