Nicer printing of Rails models

I like my models to be printed nicely, to make the class of the model as well as the id and other data available, so, when they end up in a log or console, I can now exactly what it is. I’ve been doing this since before Rails 3 and since Rails projects now have an ApplicationRecord class, it’s even easier.

On my global parent for all model classes, ApplicationRecord I add this:

def to_s(extra = nil)
  if extra
    extra = ":#{extra}"
  end
  "#<#{self.class.name}:#{id}#{extra}>"
end

That makes all records automatically print as:

<ModelName:id>

For example:

<User:123>

which makes the record findable.

But also, allows the sub-classes to add a bit of extra information without having to re-specify the whole format. For example, for the User class, it might be:

  def to_s
    super(email)
  end

so that when the user gets printed, it ends up being:

<User:123:sam@example.com>

which I found helps a lot with quickly debugging issues, in production as well as development environments.

Editing Rails 6.0 credentials on Windows

Rails 6 shipped with a very nice feature to keep encrypted credentials on the repo but separate them by environment, so you can have the credentials for development, staging and production, encrypted with different keys, that you keep safe at different levels.

For example, you might give the development key to all developers, but the production keys are kept very secret and only accessible to a small set of devops people.

You edit these credentials by running:

bundle exec rails credentials:edit --environment development

for the development credentials, or

bundle exec rails credentials:edit --environment production

for production ones. You get the idea.

When you run it, if the credentials don’t exist, it generates a key. If they exist, you need to have the keys. After decrypting, it runs your default editor and on Windows, this is the error I was getting:

No $EDITOR to open file in. Assign one like this:

EDITOR="mate --wait" bin/rails credentials:edit

For editors that fork and exit immediately, it's important to pass a wait flag,
otherwise the credentials will be saved immediately with no chance to edit.

It took me a surprisingly long time to figure out how to set the editor on Windows, so, for me and others, I’m documenting it in this post:

$env:EDITOR="notepad"

After that, running the credentials:edit command works and opens Notepad. Not the best editor by far, but for this quick changes, it works. Oh, and I’m using Powershell. I haven’t run cmd in ages.

Talk to each other, you are missing out

For the past few weeks I’ve been talking to entrepreneurs, trying to help with their problems. I’ve heard both of these statements repeated a few times:

Marketing is easy, but coding is impossible.

Marketeers

and

Building a thing is fun, but then I have no idea how to market it.

Coders

It’s frustrating to hear both this things at the same time. Even within each of the communities, inside Indie Hackers, Microconf, TWiST, we seem to have subgroups of techies and non-techies that talk among themselves but not to one another and they are both wondering where the other ones are.

Please: start talking to one another, you need those connections, your idea need those connections.

If you are either a marketer or a coder and want the other one to join your team as a non-paid co-founder you are asking them to make an investment. A huge investment in terms of personal wealth. A person might be able to start 10 startups in their life, so, you are asking them for 1 tenth of their resources. Act accordingly. Put the effort, show traction, show results. I see people put more effort into talking to an investor that will invest only 1% of their resources.

The second thing a lot of us should do, and this include me, is not focus so much on our own ideas and try to work on other people’s ideas. Build someone else’s thing, market someone else’s thing. Don’t chase one idea, chase the outcome of a successful startup and accept that it might not be your idea.

If you are a developer, know this: whatever idea you come up with, it’s an idea that another developer is likely to have, and likely to implement to compete with you. That’s why there’s so much out there in terms of Twitter clones, issue trackers, cryptocurrencies thingies, etc. A non-coder idea has the value of less competition. A CRM for a niche you’ve never heard of might be the best SaaS ever!

If you are a marketer, know this: even in a crowded space, you can make a difference because there are a lot of companies out there that have a great product and are struggling with marketing. I often find a market need, like, “Private teachers need booking systems” and in my market research I find that the perfect booking system exists and nobody is using it. Lending your superpowers to a developer that’s charging ahead with building yet-another-whatever might be the best SaaS ever!

The Voyager 1 museum

Voyager 1 is, as of now, 22 billion kilometers away from home. One day, we’ll be a space-faring species and we’ll have Voyager 1 in a museum. It’ll be trivial for us to go and reach Voyager but we won’t retrieve it. What’s wonderful about Voyager 1 is not only the amazing science and engineering that we can see on the metal, plastics, cables, circuits, panels, batteries, etc. What’s amazing is it’s vector: direction and speed.

We’ll build a museum around Voyager 1 for people to visit and see it travel. The museum will have to be built very carefully, bringing materials from all directions at the same time, in a balanced way, to avoid affecting Voyager’s trip. Even the visitors will have to be controlled to avoid affecting it.

We’ll marvel at what once was the man-made object furthest away from earth, from home, from the cradle back when all of mankind lived there. Billions of minds will visit it and marvel through the millennia. The museum will act as beacon for commerce and science to stay away of its path… until that day.

One day, Voyager 1’s path will intersect with something else. It might be a planet, an asteroid, a star, a black hole. The odds are astronomical you might think, but so is, well, space and time.

That day unrecognizable humanity will gather to decide what to do. We’ll be mature enough to not need that piece of metal somewhere safe and instead we’ll say good bye.

Part of humanity will gather around, for weeks, maybe even months. We’ll have a festival in space about the 20th century, about how fucked it was, marveling at how close humanity came to self destruction and still produced Voyager 1. We’ll watch movies, attend concerts, both old and new. And eventually, the museum we’ll retreat and so we’ll we. We’ll all watch is silence as a relic of our infancy reaches the end of its life, as it collides and disintegrates. We’ll celebrate it, we’ll mourn it.

How can the price of oil or anything be negative?

The price of oil being negative is clearly a milestone of sorts, but $0 is not such a special price point. It feels special, it feels different, specially to consumers, but it’s not.

I won’t explain why oil got so low. There’s a mix of a pandemic going on, Russia fighting OPEC, etc. There’s also the difference or lack-of buying oil vs oil futures. I don’t know about that and I won’t speculate on that. Back to the problem of something costing $0 or even negative.

Let’s say you buy a trinket and it costs you $1 and then you can sell it for $2, making 100% profit. You go and spend $1000 in buying 1000 trinkets, you are about to make $1000 but then you realize 1000 trinkets take a lot of space, so, you rent a storage space for $200/month. Now you are making $800 per month. That’s ok, that’s a good profit.

One day, the prices of trinkets start to fall. First they fall to $0.9, and you sell them for $1.8, and now your profit is $700 because your revenue is $1800, the cost of buying 1000 trinkets is $900 and rent stays at $200. As the price continues to fall, this happens:

Buy priceSell priceRevenueCostRentProfit
$0.9$1.8$1800$900$200$700
$0.8$1.6$1600$800$200$600
$0.4$0.8$800$400$200$200
$0.2$0.4$400$200$200$0
$0.1$0.2$200$100$200$-100

See how it wasn’t required for the trinkets to get to $0 to make the business nonviable? At $0.2 per trinket, there’s no profit and you might as well put the on the sidewalk, and walk away. Except that you’d get fined, so now you are losing money.

This is the equation that all shops and warehouses run for all products.

For this example, I’ve assumed $200 was the rent for one month and that all trinkets would be sold in one month. Now, let’s imagine the trinkets don’t go down in price, but they sell more slowly. It takes 2 months, which means $400 in rent. If they stay in the warehouse long enough, the magic number at which it’ll cause loses would be much higher, even higher than $2.

This is why you often get business selling you products for less than the cost. Because they are losing money by keeping it around and they need to cut those loses. The magic number in which you go from profit to loses is never $0, it’s always above.

This means that at some point before oil hit $0 it already crossed the magic number at which holding oil was causing loses, it’s just that the loses were so high that it kept pushing down the number into the negative.

Finding a co-founder for your startup

Someone recently contacted me at MicroMentor with a few questions about their startup, including, how to proceed without a business partner. I told him he should probably find one and then I discovered I had more co-founder search resources than I thought:

Any other resources you are aware of?

Converting a Python data into a ReStructured Text table

This probably exist but I couldn’t find it. I wanted to export a bunch of data from a Python/Django application into something a non-coder could understand. The data was not going to be a plain CSV, but a document, with various tables and explanations of what each table is. Because ReStructured Text seems to be the winning format in the Python world I decided to go with that.

Generating the text part was easy and straightforward. The question was how to export tables. I decided to represent tables as lists of dicts and thus, I ended up building this little module:

def dict_to_rst_table(data):
    field_names, column_widths = _get_fields(data)
    with StringIO() as output:
        output.write(_generate_header(field_names, column_widths))
        for row in data:
            output.write(_generate_row(row, field_names, column_widths))
        return output.getvalue()


def _generate_header(field_names, column_widths):
    with StringIO() as output:
        for field_name in field_names:
            output.write(f"+-{'-' * column_widths[field_name]}-")
        output.write("+\n")
        for field_name in field_names:
            output.write(
                f"| {field_name} {' ' * (column_widths[field_name] - len(field_name))}"
            )
        output.write("|\n")
        for field_name in field_names:
            output.write(f"+={'=' * column_widths[field_name]}=")
        output.write("+\n")
        return output.getvalue()


def _generate_row(row, field_names, column_widths):
    with StringIO() as output:
        for field_name in field_names:
            output.write(
                f"| {row[field_name]}{' ' * (column_widths[field_name] - len(str(row[field_name])))} "
            )
        output.write("|\n")
        for field_name in field_names:
            output.write(f"+-{'-' * column_widths[field_name]}-")
        output.write("+\n")
        return output.getvalue()


def _get_fields(data):
    field_names = []
    column_widths = defaultdict(lambda: 0)
    for row in data:
        for field_name in row:
            if field_name not in field_names:
                field_names.append(field_name)
            column_widths[field_name] = max(
                column_widths[field_name], len(field_name), len(str(row[field_name]))
            )
return field_names, column_widths

It’s straightforward and simple. It currently cannot deal very well with cases in which dicts have different set of columns.

Should this be turned into a reusable library?

WordPress.com new editor handles titles right

I like my text properly formatted and with pretty much every CMS editor out there I always have some confusion when it comes to titles. The post or page has a title and then sections inside it also have titles and they are second level titles. On most CMS you have the option of titles or headers starting at level 1 through 6 (that maps to h1, h2, through h6 in HTML).

For example, this is Confluence, a tool that I really like:

The confusion that I get here is whether a subsection to this page should have Heading 1 or Heading 2. Sometimes Heading 1 will be displayed with the same font, size, etc as the title of the page, so, by using Heading 1 you are almost creating two pages in one. But in other CMS, Heading 1 is the top level section heading and the title of the page is a special title that will always sit above it.

The new WordPress.com editor does this correctly by being very clear that your first option for section headers, after setting the title of the page or post, is h2:

I think that was a very neat solution to the problem. Bravo WordPress.

Turning a list of dicts into a ReStructured Text table

I recently found myself having to prepare a report of some mortgage calculations so that non-technical domain experts could read it, evaluate it, and tell me whether my math and the way I was using certain APIs was correct.

Since I’m using Python, I decided to go as native as possible and make my little script generate a ReStructured Text file that I would then convert into HTML, PDFs, whatever. The result of certain calculations ended up looking like a data table expressed as list of dicts all with the same keys. I wrote a function that would turn that list of dicts into the appropriately formatted ReStructured Text.

For example, given this data:

creators = [{"name": "Guido van Rossum", "language": "Python"}, 
            {"name": "Alan Kay", "language": "Smalltalk"},
            {"name": "John McCarthy", "language": "Lisp"}]

when you call it with:

dict_to_rst_table(creators)

it produces:

+------------------+-----------+
| name             | language  |
+==================+===========+
| Guido van Rossum | Python    |
+------------------+-----------+
| Alan Kay         | Smalltalk |
+------------------+-----------+
| John McCarthy    | Lisp      |
+------------------+-----------+

The full code for this is:

from collections import defaultdict

from io import StringIO


def dict_to_rst_table(data):
    field_names, column_widths = _get_fields(data)
    with StringIO() as output:
        output.write(_generate_header(field_names, column_widths))
        for row in data:
            output.write(_generate_row(row, field_names, column_widths))
        return output.getvalue()


def _generate_header(field_names, column_widths):
    with StringIO() as output:
        for field_name in field_names:
            output.write(f"+-{'-' * column_widths[field_name]}-")
        output.write("+\n")
        for field_name in field_names:
            output.write(f"| {field_name} {' ' * (column_widths[field_name] - len(field_name))}")
        output.write("|\n")
        for field_name in field_names:
            output.write(f"+={'=' * column_widths[field_name]}=")
        output.write("+\n")
        return output.getvalue()


def _generate_row(row, field_names, column_widths):
    with StringIO() as output:
        for field_name in field_names:
            output.write(f"| {row[field_name]}{' ' * (column_widths[field_name] - len(str(row[field_name])))} ")
        output.write("|\n")
        for field_name in field_names:
            output.write(f"+-{'-' * column_widths[field_name]}-")
        output.write("+\n")
        return output.getvalue()


def _get_fields(data):
    field_names = []
    column_widths = defaultdict(lambda: 0)
    for row in data:
        for field_name in row:
            if field_name not in field_names:
                field_names.append(field_name)
            column_widths[field_name] = max(column_widths[field_name], len(field_name), len(str(row[field_name])))
    return field_names, column_widths

Feel free to use it as you see fit, and if you’d like this to be a nicely tested reusable pip package, let me know and I’ll turn it to one. One thing that I would need to add is making it more robust to malformed data and handle more cases of data that looks differently.

If I turn it into a pip package, it would be released from Eligible, as I wrote this code while working there and we are happy to contribute to open source.

Book Review: Scrum: The Art of Doing Twice the Work in Half the Time by Jeff Sutherland

scrum coverI first came in contact with Scrum when I was working at Google and since then, I’ve been applying it to the startups I co-founded with good outcomes. Since I was searching for a job, I kept seeing “Scrum Master” come up over and over and I thought it was about time that I learned all the details of Scrum to be able to be a proper Scrum master. Well, in only a couple of ways I finished the book and discovered I was already a proper Scrum master, having learned all the details about it from my time at Google and blog posts.

About the book itself, it’s short and entertaining with enough story telling to keep you engaged even if you only have a passing interest in Scrum. The system is rather simple, with only a few moving pieces and I’m glad of that. Simple systems tend to work better. The testimonials of how much productive a team is with Scrum feel exaggerated completely out of proportion, but then again, some companies are so terrible at producing anything at all, being the cradle of dysfunction, that is no surprise their productivity can be doubled or quadrupled.

★★★★☆

Buy The Art of Doing Twice the Work in Half the Time in USA

Buy The Art of Doing Twice the Work in Half the Time in UK