Posted on • Originally published atkatafrakt.me
Reconfiguring your application live with dRuby
dRuby is a pretty old but relatively unknown part of Ruby standard distribution. I first wrote about ithere in 2018 and I have to admit that to this day I haven really found a production use case for it. However, I still think it a gem worth knowing, even if only to impress you Ruby friends on a conference afterparty.
To demonstrate what dRuby can do, we will write a simple application. It will periodically check Mastodon API ofruby.social server and check for new messages (called toots). To keep things as simple as possible, we'll just usenet/http
as an HTTP client. Here's our first draft:
require"net/http"require"json"classRubySocialCheckerENDPOINT="https://ruby.social/api/v1/timelines/public?local=true"defcallresponse=Net::HTTP.get(URI(ENDPOINT+"&limit=1"))parsed=JSON.parse(response)@last_toot_id=parsed.first["id"]run_loopenddefrun_looploopdoresponse=Net::HTTP.get(URI(ENDPOINT+"&min_id=#{@last_toot_id}"))JSON.parse(response).eachdo|toot|putstoot['uri']@last_toot_id=toot['id']endsleep(5)endendendRubySocialChecker.new.call
If you run it and you're lucky (i.e. someone posts something), you will see links to new toots being printed to stdout. As you see, the code is not particularly complicated. So let's complicate it with seemengly no good reason. In the next step we will extract a configuration to a separate class:
classConfigattr_accessor:interval,:debugdefinitialize(interval:5,debug:false)@interval=interval@debug=debugendendclassRubySocialCheckerENDPOINT="https://ruby.social/api/v1/timelines/public?local=true"definitialize(config=Config.new)@config=configenddefcallresponse=Net::HTTP.get(URI(ENDPOINT+"&limit=1"))parsed=JSON.parse(response)@last_toot_id=parsed.first["id"]run_loopenddefrun_looploopdoresponse=Net::HTTP.get(URI(ENDPOINT+"&min_id=#{@last_toot_id}"))parsed=JSON.parse(response)if@config.debugputs"[#{Time.now}] Fetched#{parsed.size} toots"endJSON.parse(response).eachdo|toot|putstoot['uri']@last_toot_id=toot['id']endsleep(@config.interval)endendend
Hmm... This starts to look serious! We now have a config, which we pass to the checker. The config specifies how often we should check for new toots and also has a flag for a debug mode. In this mode we output a message with how many toot we just fetched, so you can at least see that something is happening.
We can run our checker in a debug mode now:
config=Config.new(debug:true)RubySocialChecker.new(config).call
Okay, but why did we do that? Because now we want to add dRuby. This gem essentially allows you to "hook into" your running Ruby program from another process in a controlled manner. Let's add dRuby server to our program right before the code starting the checker.
require"drb/drb"uri="druby://localhost:8787"config=Config.newDRb.start_service(uri,config)RubySocialChecker.new(config).call
Now, run the program (note that the debug mode is off) and now in a different terminal window fire up IRB. In the IRB session, do the following:
irb(main):001> require "drb/drb"=> trueirb(main):002> DRb.start_service=> #<DRb::DRbServer:0x00007f60d2da7868 ...>irb(main):003> config = DRbObject.new_with_uri("druby://localhost:8787")=> #<DRb::DRbObject:0x00007f60d27d3d10 @ref=nil, @uri="druby://localhost:8...irb(main):004> config.debug = true=> true
If you now look at the terminal where your program is running... Magic! The debug messages started to show. Now let's spice the things up a bit:
irb(main):005> config.interval = 1=> 1
The logs show up even faster. We haven't touched anything in the running program, it does not read from any database on each loop step, but we managed to alter its behaviour from the outside. It's also worth noting that the IRB process does not know anything about the checker or config. If you try to reference them, you'll see the uninitialized constant error.
irb(main):009> RubySocialChecker(irb):9:in `<main>': uninitialized constant RubySocialChecker (NameError) from /home/katafrakt/.asdf/...irb(main):010> Config(irb):10:in `<main>': uninitialized constant Config (NameError)Did you mean? RbConfig from /home/katafrakt/.asdf/...
See this in action:
Ok, but why?
Like I said, you probably won't benefit from it in your Rails application. Web applications are stateless by nature and here you need some in-memory state to hook into. However, there are some cases, mostly long-running processes, where this can be useful. The first time I've seen a magic like that, although it was not in Ruby, was an IRC bot, in which admin was able to turn some features on and off live, add people to denylist etc., all without restarting the application.
It might also be an alternative to logs. If you have, for example, a scraper that scrapes thousands of pages, instead of log results every 100 of them, you can expose an interface over dRuby to ask how many pages you checked, how many had useful results and even return these results.
But even if you don't do any of these things, it's good to know that doing things like that is possible and you don't even have to install any additional gem to do that.
You can read more about dRubyin the docs. Or you can evenbuy a book about it.
Top comments(0)
For further actions, you may consider blocking this person and/orreporting abuse