Home buyer letter

Having now successfully purchased a home in San Francisco, I have had friends asked me for resources. This was the letter we sent to the seller, which is very SF, but also the energy to bring. I also learned that these letters are waning because they are biased af.

Dear [seller],

Your home at [street address] is the right fit for us, our two cats, and (hopefully) our future family. We love the long layout, high ceilings (we’re both tall) and sunny patio, and look forward to hosting friends for Sunday brunch someday.

We’re rooted in the city through the South End Rowing Club, St. Francis Lutheran Church, supporting the YMCA, kickstarting local businesses like Andytown coffee, and working at tech companies and nonprofits. Ben worked for three years just around the corner at California and Grant, and Angelina has worked at numerous tech companies around Union Square and SOMA. We love the quiet contrast of Nob Hill against the rest of downtown San Francisco.

We see SF as a long term home and [street address] as a homebase for work and play: swimming and rowing on the SF Bay, and wine trips up to Napa and Sonoma too. Your home would have the perfect commute for Ben, walking down the hill to his nonprofit job at [organization] where he builds technology to help people apply for food stamps, remove marijuana convictions from criminal records, and improve how government delivers services to those who need them most. And it’s a short walk for Angelina to the [company] Shuttle or Downtown SF [company] office where she does business development for maps and location technology in use around the world.

Your home would provide the greatest fit for us as we aim to continue to contribute to the fabric of the city, where we hope to raise a family and be active in the community. We aim to take great care of an already-loved property while advocating for a neighborhood that supports all members throughout the growth of our city.

Our offer is fair given the condition, amenities and comparable sales, and we aim for being the best buyer to steward this home and the neighborhood. We look forward to your consideration of our offer for [street address].

Sincerely,
Ben and Angelina


GoodJob, what’s new: Cron, concurrency controls, and a dashboard demo (v1.12.0)

This is a quick roundup of what’s new with GoodJob v1.12.0 since the last update published for GoodJob v1.9.

GoodJob ( github ) is a multithreaded, Postgres-based, ActiveJob backend for Ruby on Rails. If you’re new to GoodJob, read the introductory blog post.

For further details on the following updates, check out GoodJob’s Changelog or Readme.

Cron-like replacement for repeating/recurring jobs

GoodJob now ships with a cron-like replacement for repeating/recurring jobs. The cron-like process runs either via the CLI, or async within the web server process. Repeating jobs can be scheduled to the second, powered by the Fugit gem. Here’s what the configuration looks like:

# config/environments/application.rb or a specific environment e.g. production.rb

# Enable cron in this process; e.g. only run on the first Heroku worker process
config.good_job.enable_cron = ENV['DYNO'] == 'worker.1' # or `true` or via $GOOD_JOB_ENABLE_CRON

# Configure cron with a hash that has a unique key for each recurring job
config.good_job.cron = {
  # Every 15 minutes, enqueue `ExampleJob.set(priority: -10).perform_later(52, name: "Alice")`
  frequent_task: { # each recurring job must have a unique key
    cron: "*/15 * * * *", # cron-style scheduling format by fugit gem
    class: "ExampleJob", # reference the Job class with a string
    args: [42, { name: "Alice" }], # arguments to pass; can also be a proc e.g. `-> { { when: Time.now } }`
    set: { priority: -10 }, # additional ActiveJob properties; can also be a lambda/proc e.g. `-> { { priority: [1,2].sample } }`
    description: "Something helpful", # optional description that appears in Dashboard (coming soon!)
  },
  another_task: {
    cron: "0 0,12 * * *",
    class: "AnotherJob",
  },
  # etc.
}

Concurrency controls

GoodJob now offers an ActiveJob extension to provide customizable limits on the number of jobs enqueued or executed concurrently. Rails might upstream it too. Here’s how to configure it:

# app/jobs/my_job.rb
class MyJob < ApplicationJob
  include GoodJob::ActiveJobExtensions::Concurrency

  good_job_control_concurrency_with(
    # Maximum number of jobs with the concurrency key to be concurrently enqueued
    enqueue_limit: 2,

    # Maximum number of jobs with the concurrency key to be concurrently performed
    perform_limit: 1,

    # A unique key to be globally locked against.
    # Can be String or Lambda/Proc that is invoked in the context of the job.
    # Note: Arguments passed to #perform_later must be accessed through `arguments` method.
    key: -> { "Unique-#{arguments.first}" } #  MyJob.perform_later("Alice") => "Unique-Alice"
  )

  def perform(first_name)
    # do work
  end
end

Dashboard Demo

Don’t take my word for what a good job it is. Check out the new GoodJob Dashboard demo running on Heroku entirely within a single free dyno: https://goodjob-demo.herokuapp.com/

More news:

  • Wojciech Wnętrzak aka @morgoth became a GoodJob maintainer.
  • Wrote up details of the evolving development philosophy behind GoodJob.
  • The Dashboard now allows removing jobs, with more actions coming soon.

Contribute

Code, documentation, and curiosity-based contributions are welcome! Check out the GoodJob Backlog , comment on or open a Github Issue, or make a Pull Request.

I also have a GitHub Sponsors Profile if you’re able to support GoodJob and me monetarily. It helps me stay in touch and send you project updates too.


Paranoid and Reparative readings

From How Twitter can ruin a life: Isabel Fall’s sci-fi story “I Sexually Identify as an Attack Helicopter” drew the ire of the internet:

The delineation between paranoid and reparative readings originated in 1995, with influential critic Eve Kosofsky Sedgwick. A paranoid reading focuses on what’s wrong or problematic about a work of art. A reparative reading seeks out what might be nourishing or healing in a work of art, even if the work is flawed. Importantly, a reparative reading also tends to consider what might be nourishing or healing in a work of art for someone who isn’t the reader.


GoodJob Updates v1.5 - v1.9: Dashboard, daemonize, async_server, and graceful shutdowns

This is a quick roundup of what’s new with GoodJob since the last update published for GoodJob v1.4.

GoodJob (github) is a multithreaded, Postgres-based, ActiveJob backend for Ruby on Rails. If you’re new to GoodJob, read the introductory blog post .

For further details on the following updates, check out GoodJob’s Changelog or Readme.

GoodJob v1.5: Web Dashboard and configuration

GoodJob ships with a web dashboard to display future, finished and errored jobs for easy inspection. The Dashboard mounts as a self-contained Rails Engine.

GoodJob uses more Rails.application.config... for improved autoloading compatibility; deprecation notices have examples for updating configuration.

GoodJob v1.6: Daemonize

GoodJob can run as a backgrounded daemon for folks who are still managing servers with --daemonize.

GoodJob v1.7: Scheduled job cache

GoodJob caches scheduled jobs (i.e. ExampleJob.set(wait: 30.minutes).perform_later) for signifiantly improved latency without relying upon polling.

GoodJob v1.8: Graceful shutdown

GoodJob added additional shutdown options, including GOOD_JOB_SHUTDOWN_TIMEOUT to allow jobs to finish before exiting.

GoodJob v1.9: async_server mode

GoodJob added an additional async execution mode to simplify the default configuration: running jobs as part of the web-process (and not console, Rake commands, etc.)

Upcoming: cron and concurrency controls

GoodJob will add support for cron-style repeating jobs, and concurrency controls to ensure that only a specified number (1 or more) jobs are enqueued or performed at the same time. I previously was opposed to extending ActiveJob’s interface, but have changed my mind; it’s on!

Contribute

Code, documentation, and curiosity-based contributions are welcome! Check out the GoodJob Backlog, comment on or open a Github Issue, or make a Pull Request. Thank you!!! to everyone who has contributed to GoodJob, including morgoth, tedhexaflow, weh, lauer, reczy, zealot128, gadimbaylisahil, Mr0grog, thilo, arku, sj26, jm96441, thedanbob, and joshmn.

I also have a GitHub Sponsors Profile if you’re able to support GoodJob and me monetarily. It helps me stay in touch and send you project updates too.


Is SecureRandom.choose the wrong name for generating randomized strings with Ruby?

I frequently need to generate a short, random string:

  • I need a non-numeric, non-enumerable url ID. e.g. https://example.com/resources/TOKEN
  • I need to generate a short, human-readable random activation token and I don’t want it to contain similar-looking characters like 1I or O0
  • I want to do something fun with strings of emojis.

I’ve been copy-pasting this snippet around for years:

def random_string(alphabet:, length:)
  Array.new(length) { alphabet.chars[rand(alphabet.chars.size)] }.join
end

random_string(alphabet: "AaBbCc123", length: 7) 
# => "C113A11", "Abcc3B2", "33cabbC", etc.

random_string(alphabet: "😀😍🙃🤪😎", length: 3)
# => "😍😀😎", "😀🤪🤪", "🙃😎😀", etc.

The gems friendly_id and uniquify can do this too.

There was recently a discussion on Ruby on Rails Link Slack about generating random strings, and I went looking to see if there was a better implementation.

I found SecureRandom.choose, which is built into the Ruby standard library, and as part of the SecureRandom module, should be fairly trustworthy. Looks perfect:

# SecureRandom.choose generates a string that randomly draws from a source array of characters.
#
# The argument _source_ specifies the array of characters from which to generate the string.
# The argument _n_ specifies the length, in characters, of the string to be generated.
#
# The result may contain whatever characters are in the source array.
#
#   require 'securerandom'
#
#   SecureRandom.choose([*'l'..'r'], 16) #=> "lmrqpoonmmlqlron"
#   SecureRandom.choose([*'0'..'9'], 5)  #=> "27309"

…but there is a problem: SecureRandom.choose is a private method. The choose method is used to implement the public SecureRandom.alphanumeric method, but is not itself exposed publicly. I went back to the initial feature request and found the reason:

I feel the method name, SecureRandom.choose, doesn’t represent the behavior well.

Fair enough. Until the name is figured out, I’ll still be using it:

require 'securerandom'

SecureRandom.send :choose, "😀😍🙃🤪😎".chars, 3
# => "😍😎😍"

Generic advice for managers

From Allison Green’s Ask a Manager: “I Got a Terrible Review on Glassdoor, and I’m Spiraling”:

The reality is, when you’re a manager, not everyone will like working for you. You could be the greatest boss in the world and some people still wouldn’t like you. Partly that’s because being a good manager means giving feedback, addressing problems, and holding people accountable in ways they might not like. If you need to correct someone a lot, or have difficult conversations with them about their work, or say no to something they wanted, it’s human nature that you might not end up being their favorite person. Or you might have a style that doesn’t align well with theirs — maybe you’re very direct and matter-of-fact and that feels brusque to them, or maybe you’re a planner and a devotee of process and they work more spontaneously. That wouldn’t mean either of you is in the wrong; it would just mean you don’t mesh well together.

Sometimes, too, people dislike a job or a manager for reasons that aren’t as much about the manager as they are about other things going on with that person — a dislike of their career path, stressors outside of work, a generally bad fit with the role, or all kinds of things.

Or, frankly, you might be an imperfect manager — most of us are — but that doesn’t mean you’re a horrible one. Managing people is hard, and every manager will get things wrong now and then. Ideally you’ll establish a track record of fairness, transparency, and good judgment so your mistakes are judged within that context … but you still might encounter an employee who judges your mistakes harshly. You’re basically on a stage when you’re the boss; you’re going to be scrutinized by the people under you, and there will be things they take issue with. It’s part of the job, and you’ve got to be okay with that.

Or, yes, you might be a terrible manager! It’s possible. There are lots of terrible managers out there. But I’m skeptical that you’re terrible in the specific ways the review described (mean and intolerant of mistakes), because your detailed explanation of your approach to mistakes sounds pretty healthy and because you sound genuinely thoughtful and caring toward team members. People can delude themselves, of course, and managers aren’t always reliable narrators of their own management styles. But the way you talk about how you operate — and your reaction now — doesn’t seem to line up with that review or with the feedback people have given your boss about you. That doesn’t mean that review is definitely wrong. I obviously can’t say that with certainty. But I don’t think it warrants the self-flagellation you’re doing.

That said, all managers have ways they could improve — things they’re doing that irritate or upset their teams, or make things run less effectively, or allow problems to fester. I can say with confidence that you could be a better manager, because we all could. So one option is to take this as an impetus to do a real inventory of the way you manage, figure out where you could improve, and lean into doing that work.


GoodJob 1.4: JRuby compatibility and more

GoodJob version 1.4 is released. GoodJob is a multithreaded, Postgres-based, ActiveJob backend for Ruby on Rails. If you’re new to GoodJob, read the introductory blog post .

GoodJob’s v1.4 release adds support for:

  • JRuby
  • MRI 3.0
  • Rails 6.1
  • Postgres 12+

And includes additional improvements to the Web Dashboard.

Version 1.4’s release comes three months after v1.3 and five months after GoodJob’s initial v1.0 release.

JRuby compatibility

GoodJob 1.4 adds compatibility for Ruby on Rails applications running under JRuby. GoodJob’s multithreading is built on top of ConcurrentRuby, which provides consistent behavior and guarantees on all four of the main Ruby interpreters (MRI/CRuby, JRuby, Rubinius, TruffleRuby).

A minor downside of JRuby is that JRuby’s database adapter, activerecord-jdbc-adapter, does not support Postgres LISTEN, which means GoodJob must rely on polling for new jobs under JRuby.

Broader compatibility and improvements

In addition to JRuby, GoodJob 1.4 adds compatibility for MRI 3.0, Rails 6.1, and Postgres 12+.

GoodJob 1.4 also includes improved filtering for the Web Dashboard,.

What’s next

The next version of GoodJob will add support for daemonization.

Contribute

Code, documentation, and curiosity-based contributions are welcome! Check out the GoodJob Backlog , comment on or open a Github Issue, or make a Pull Request.

I also have a GitHub Sponsors Profile if you’re able to support GoodJob and me monetarily. It helps me stay in touch and send you project updates too.


Maimonides’ levels of charity

This has been kicking around in my “to blog” bucket since July, 2008 when I had many more posts on Charity. I originally copied this from judaism.about.com, and Wikipedia has a version of these too.

Maimonides organized the different levels of tzedakah (charity) into a list from the least to the most honorable.

8. When donations are given grudgingly.

7. When one gives less than he should, but does so cheerfully.

6. When one gives directly to the poor upon being asked.

5. When one gives directly to the poor without being asked.

4. When the recipient is aware of the donor’s identity, but the donor does not know the identity of the recipient.

3. When the donor is aware of the recipient’s identity, but the recipient is unaware of the source.

2. When the donor and recipient are unknown to each other.

1. The highest form of charity is to help sustain a person before they become impoverished by offering a substantial gift in a dignified manner, or by extending a suitable loan, or by helping them find employment or establish themselves in business so as to make it unnecessary for them to become dependent on others.

To be critical, 8 and 1 are most meaningful to me, and the gradations between the others aren’t particularly strong, though I think 4 and 3 could swap places.


If you can hook them for a day, you can hook them for two

I think this essay has come to mind more often than any other in the past decade of my work as a developer, and sometimes distressingly, in life. From Tim Rogers “who killed videogames? (a ghost story)” :

One click in one of these social games will take the user to the Real-World Money-Costing In-Game-Currency-Unit-Buying Shop. Here, the player will see that the game indeed offers him an option for paying $100 for something which is not real: an in-game currency with which to buy things in the game.

At the time he makes the conscious decision to wait for his energy to refill, the player likely already knows that “micro”-transactions exist which have $100 price-tags. Now he learns how much energy costs — usually, it’s nowhere near $100, or even $10.

Do players buy energy? What sorts of players buy energy? The short answer is: actual idiots. The long answer is: people who don’t understand why they have so much real-world money.

In social games, energy doesn’t exist to be bought. It exists as an engagement-regulating filter. The player attaches to it some vague notion of “value”. Backward-like, he comes to associate waiting an hour in the real world before coming back to the game with “working” and “earning” the “value” of the thing the game is giving him for “free”.

This isn’t exactly a truthful impression. The impression the player should take away — and gets confused about — is that in social games, time is a currency. Time is what you use to buy energy. Energy is a currency for purchasing in-game money, and some less-abstract in-game currencies (the premium in-game currency which the player must use real money to purchase) and more-abstract in-game currencies (namely virality and chance) can be used to purchase energy directly.

Energy’s multiple conversion rates into multiple in-game currencies mystify the idea of time as a currency.

The old idiom “time is money” has many meanings, you see.

“Energy” is a money that literally directly represents time.

…It’s a compulsion trap.


Dying on management mountain

From First, break all the rules:

Managers were encouraged to focus on complex initiatives like reengineering or learning organizations, without spending time on the basics. The stages on the mountain reveal that if the employee doesn’t know what is expected of him as an individual (Base Camp), then you shouldn’t ask him to get excited about playing on a team (Camp 2). If he feels as though he is in the wrong role (Camp 1), don’t pander to him by telling him how important his innovative ideas are to the company’s reengineering efforts (Camp 3). If he doesn’t know what his manager thinks of him as an individual (Camp 1), don’t confuse him by challenging him to become part of the new “learning organization” (Camp 3). Don’t helicopter in at seventeen thousand feet, because sooner or later you and your people will die on the mountain.

And a quote about performing the performance:

A manager has got to remember that he is on stage every day. His people are watching him. Everything he does, everything he says, and the way he says it, sends off clues to his employees. These clues affect performance. So never forget you are on that stage.