Your web-browser is very outdated, and as such, this website may not display properly. Please consider upgrading to a modern, faster and more secure browser. Click here to do so.

Elm City Craftworks

News and updates from Gregory Brown and Jordan Byron.
We work on the Practicing Ruby journal, and are active in F/OSS
.
Oct 16 '14

Four excellent articles on designing, developing, and maintaining complete software systems.

We suffer from an embarrassment of riches when it comes to programming idioms and tactics from the level of a single line of code up to an individual object in isolation. But when we start talking about the interactions between objects in our systems, or the interactions between systems, things get a lot more fuzzy.

To gain some clarity when thinking through high-level system design, development, and maintenance, read these four articles:

  1. A Laboratory for Teaching Object-Oriented Thinking

  2. Big Ball of Mud

  3. The Log: What every software engineer should know about real-time data’s unifying abstraction

  4. Coplien’s Segue

Each article covers a different theme in high-level software development, but the thread that unifies them is the idea of looking at software projects as being dynamic, interconnected systems-of-systems in which most of the interesting stuff happens at the integration layer.

I’ve shared a brief summary of each of these articles below, highlighting the parts I found most interesting. Hope you find them as useful as I did!

A Laboratory for Teaching Object-Oriented Thinking

This article introduces the concept of Class-responsibility-collaboration (CRC) cards, which are an extremely lightweight tool for reasoning about object-oriented software designs.

The basic idea is that you represent object roles in a system via simple index cards that include only the following information:

  1. The name of the class at the top of the card
  2. The responsibilities of the class on the left side of the card
  3. A list of collaborating objects on the right side of the card

To drive out a new design, designers start with a specific use case and then build just a couple cards to cover the various responsibilities in solving the problem. As the information gets sketched out, the names of the objects and their stated responsibilities can be refined and reworked, and cards can be added and removed, until a coherent design emerges.

I had first seen this technique used in the example project found in Growing Object-Oriented Software, Guided by Tests, but the technique itself has been around a very long time. This article was published in 1989!

The emphasis on a very minimal structure that is completely independent of a programming language’s implementation details, combined with the focus on the interactions between objects rather than static analysis of objects in isolation make this design technique especially suitable for systems-level thinking.

Big Ball of Mud

Although we spend a lot of time talking about what beautiful software design looks like, most real projects don’t come close to meeting our ideals. This is an open secret among software developers, but one that is seldom given the attention it deserves.

In this article, Foote and Yoder analyze the common patterns we see in typical software development lifecycles, and examines their costs and benefits. In doing so, the authors reveal both what causes a software system to devolve into a “big ball of mud”, as well as some methods for keeping a system in a constant state of health.

The seven specific patterns covered in the article are listed below, along with the recommendations for how to deal with them.

Big ball of mud: You need to deliver quality software on time, and under budget. Therefore, focus first on features and functionality, then focus on architecture and performance.

Throwaway code: You need an immediate fix for a small problem, or a quick prototype. Therefore, produce, by any means available, simple, expedient, disposable code that adequately addresses just the problem at-hand.

Piecemeal growth: Master plans are often rigid, misguided and out of date. Users’ needs change with time. Therefore, incrementally address forces that encourage change and growth. Allow opportunities for growth to be exploited locally, as they occur. Refactor unrelentingly.

Keep it working: Maintenance needs have accumulated, but an overhaul is unwise, since you might break the system. Therefore, do what it takes to maintain the software and keep it going. Keep it working.

Shearing layers: Different artifacts change at different rates. Therefore, factor your system so that artifacts that change at similar rates are together.

Sweeping it under the rug: Overgrown, tangled, haphazard spaghetti code is hard to comprehend, repair, or extend, and tends to grow even worse if it is not somehow brought under control. Therefore, if you can’t easily make a mess go away, at least cordon it off. This restricts the disorder to a fixed area, keeps it out of sight, and can set the stage for additional refactoring.

Reconstruction: Your code has declined to the point where it is beyond repair, or even comprehension. Therefore, throw it away and start over.

Don’t just settle for this brief summary! Be sure to read the article to see how the authors shine a light on each of these patterns and expose the interconnections between them. That’s where the real insights are.

The Log: What every software engineer should know about real-time data’s unifying abstraction

On the surface, this article may sound as if it’s targeted at a relatively narrow audience that needs to do “web scale” data processing. But don’t let that fool you: it truly is relevant to “every software engineer”, even if it’s only for the high level ideas it contains.

The article starts by discussing the concept of a data log in the abstract. This is a very simple append-only storage mechanism which contains a set of arbitrary records ordered by time. The difference between a data log and the logs we typically see for debugging purposes in applications is that a data log is meant to be consumed by computers rather than humans.

The author goes on to talk about how data logs are a key part of implementing database transactions, and discusses the many different ways that transactions can be logged, synchronized, and replicated. This high-level explanation was especially interesting to me as someone who doesn’t know a whole lot of stuff about database theory.

From that point forward, things get a lot more interesting. The author covers three main themes: data integration, real-time data processing, and distributed system design.

Of those three topics, I found the author’s ideas on data integration to be especially interesting, mainly because that’s the kind of problem I deal with most in my regular work. The basic idea is that if you can build a uniform means of publishing and subscribing to data streams from all your systems within an organization, you can get rid of a lot of crufty connection layers between systems. This style of design can even vastly simplify the responsibilities for data warehousing systems, by splitting out the job of “getting all the data in one place” from the task of restructuring information for complicated batch analysis tasks.

The discussion on stream processing goes on to discuss how the inherent characteristics of a log-based storage mechanism eliminate various data flow performance and reliability problems. The basic idea is that if each system consuming the log is only responsible for tracking a single number (which log entry it received last), it is easy to restart individual processes without losing information or causing system-wide service interruptions. And because the connection between worker processes and the uniform logging service is indirect, workers can catch up with the flow of new events on their own time without worrying about blocking or dropping data. The log system itself serves as a massive, universal buffer.

The author goes on to discuss at the high level the full range of architectural benefits that this style of design can have for distributed systems. He wraps things up by discussing what it might take to make to get this kind of architecture to gain widespread adoption, hinting at the possibility of unifying lots of open source tools and providing good abstractions over them to make this kind of architecture more accessible to the average developer.

I’ll admit that a ton of the stuff in this article was over my head, but it’s written in such plain language that I gained a ton from it nonetheless. It seems like if I come back to it again in a year, I’ll be able to learn just as much from it as I did on my first read, just at a more refined level.

Coplien’s Segue

This essay is a sequel to James Coplien’s widely read article “Why most unit testing is waste”. Although you’ll probably enjoy it more if you read both articles, it’s capable of standing on its own.

Unlike the other articles in this reading list, this one is a bit more opinionated and scattershot in its scope. However, it offers many compelling thought experiments and anecdotes about the limitations of unit testing, the potential virtues of embracing runtime assertions at the individual function level (e.g. contract-style programming), and the power of system-level tests to provide useful insights to aid debugging and define intended program behavior.

In addition to the testing related discussions, this essay drives at some important questions about what it means to deliver reliable, high quality software. It discusses the limitations of all forms of testing as a means of driving software quality, and hints at some important ideas around fault tolerance, self-healing software systems, and a number of other useful topics.

Unless you’re very familiar with Coplien’s perspective on things, you’ll probably find yourself disagreeing on at least some of his points. But he offers so much to think on and explore that I’m sure you’ll still find something of interest in this article.


These articles are fascinating, because they’re relevant to all software developers regardless of their background or level of experience. If you make your way through this entire reading list, you’ll undoubtedly level up your systems thinking skills.

I’ve really enjoyed studying each of these articles, so don’t hesitate to email me if you would like someone to discuss them with.

Oct 1 '14

Practicing Ruby Scavenger Hunt

NOTE: The contest deadline has passed and winners have been selected. Thanks everyone who participated! You can still participate in the hunt just for fun, though.

As of a few days ago, practicingruby.com has become a completely open-access publication. All 95 of our published articles are now freely available, and if you put them together, they’d span well over a thousand pages of printed materials.

Practicing Ruby has a big problem though: our archives are a simple chronological listing, and there isn’t any search functionality built into the website. So even if you know there is great stuff in there somewhere, it’s about as easy to find as that nice wool sweater you’ve been storing in your atttic since the late 1980s.

Over the coming weeks, I’m going to work hard on properly fixing our discoverability problem. But for now, let’s make lemons into lemonade by having ourselves a Scavenger Hunt!

Find that article!

Here are the articles to go hunt for over on practicingruby.com.

Remember that all you need to identify is the title of the article that matches the numbered clues below. They should mostly identify unique articles, but in cases where a clue could identify more than one article, I’ll accept any reasonably well-matched answer.

  1. Discusses the question “How do you redesign the API for a method that has too many parameters?”

  2. Simulates a mindless sort of creature that actually is pretty good at solving shortest path problems.

  3. Uses basic statistics to consider the question “What do money and population count have to do with performance in international athletic competitions?”

  4. Describes the outside-in code review process that Gregory Brown and Jordan Byron use when working together on practicingruby.com and other projects.

  5. Works through a real case study of how we can apply ideas inspired by highway maintenance to our daily coding work.

  6. Takes an incremental approach to building what may be the worst Pacman clone ever.

  7. Builds (and refines) an interpreter that can run this source code.

  8. Solves one of Djikstra’s classic concurrency problems in two different ways.

  9. Simulates recipe matching from Minecraft’s crafting tables.

  10. Explores some software design guidelines from a computer scientist who has shipped code that has landed on Mars.

  11. Dives deep into the “L” of the SOLID design principles, illustrating a few ideas from the original paper in Ruby.

  12. Describes a systematic way of categorizing different kinds of coupling in software systems.

  13. Implements a simulator that is capable of running this retro-gaming inspired machine code.

  14. Shows some examples of how Ruby blocks can cause memory leaks.

  15. Implements Ruby’s backticks operator for running system commands using nothing but fork, exec, and UNIX pipes.

  16. Illustrates a security vulnerability in Rack, and how it was fixed.

  17. Implements a Rails-style before_filter for use in plain-old Ruby objects.

  18. Uses a few lines of JRuby code to (crudely) simulate a forest fire.

  19. Includes a code sample with the words “You smell something terrible nearby!”.

  20. Uses metaprogramming to implement an object model that is similar to what you can find in Javascript, Self, and IO.

Happy hunting, and don’t hesitate to email me if you have any questions.

Sep 28 '14

Prawn 1.3 released!

Today I published a new release of the Prawn PDF toolkit. It is completely backwards compatible with 1.2, and includes some bug fixes, internal cleanup, and minor improvements (e.g. the Prawn::View mixin).

Be sure to check out CHANGELOG to see exactly what changed.

Should you upgrade to Prawn 1.3?

If you are already running Prawn 0.15 or any 1.x release, absolutely! If you are using tables in your PDFs, you’ll need to install the prawn-table gem and then require "prawn/table" to make your code work, but everything else should be a drop-in replacement.

If you’ve been using an older version of Prawn, you should treat your code as legacy and not assume that you’ll be able to upgrade without something breaking. Consider trying out our newer releases on your next project instead.

Getting help if you need it

Prawn’s API documentation and manual can be found at prawnpdf.org.

Please send support requests to us via the Prawn mailing list, or see if anyone is around in the #prawn channel on Freenode. To report problems or contribute code, check out prawnpdf/prawn on Github.

Acknowledgements

I’d like to thank Mario Albert, Dan Allen, Cédric Boutillier, Jesse Doyle, and David Ruan for their help with this release.

Enjoy the new code, and Happy Prawning everyone! -greg

NOTE: I was able to do some extra work this month on Prawn thanks to generous contributors who have joined the Prawn Supporters program. Consider joining if you haven’t already — a little bit of extra funding goes a long way!

Sep 9 '14

Plans for Practicing Ruby: Volume 8

I just hit the publish button on the last issue of Practicing Ruby’s seventh volume, and that means it’s time to share some thoughts on what the future holds for our beloved publication.

Whenever we wrap up a Practicing Ruby volume, I usually spend a lot of time talking about on what went well and what didn’t go well, in an attempt to provide the necessary context for readers to get on board with my plans for the future. But because I’m not sure whether that’s ever been useful for anyone other than myself, I’m going to skip the retrospective and focus on what lies ahead of us.

Cutting right to the good stuff, the next volume of Practicing Ruby is going to be unlike anything we’ve ever done before. The overarching goal is to prepare something that reads more like a good trade magazine and less like an unorganized collection of blog posts.

For starters, we’ll produce a few new articles in our traditional style, one for each of the following themes:

  • Tools and Techniques: Core programming concepts and Ruby language fundamentals.

  • Guided Explorations: Walkthroughs of in-depth sample projects.

  • Practicing Theory: Connecting abstract computing concepts to their practical applications.

  • Sustainable Development: Reducing waste and improving maintainability throughout the software development lifecycle.

I came up with these categories by looking at the different styles of articles we’re already producing for Practicing Ruby, so they shouldn’t come as much of a surprise to existing subscribers. But by balancing things out so that each theme gets equal time to shine in the new volume, we’re pretty much guaranteed to produce a diverse reading experience.

This mini-collection of traditional articles will form the backbone of the new volume, but layered on top of this we’ll also introduce a brand new category of articles called Deep Reads. I haven’t completely worked the details out for this part yet, but the idea is that these articles will uncover some fascinating knowledge buried in dense learning materials (e.g. books or papers) by connecting them back to everyday Ruby programming problems. The goal is to produce something capable of standing on its own if necessary, but also something that would be a great complimentary resource for anyone who’s read the original source materials.

The neat thing about Deep Reads is that it gives me an excuse to put together a few group discussions among Practicing Ruby subscribers about the books and papers we’re interested in. Within the next few weeks I will send out a poll to subscribers to see which items on my own reading list are interesting to them, and then we’ll try to line up our first conversation soon after that.

To top it all off, I’m planning to put together another self-guided course that will challenge readers to dig through Practicing Ruby’s archives to answer questions and to complete challenging project-based exercises. Practicing Rubyists can expect this new course to be similar in style to the Streams, Files, and Sockets course we built over the last few months, but hopefully with a little more of a polished finish to it.

So between the four traditional articles, the three Deep Reads, and a new four part course, we’re looking at about as much content as we’d typically run in a Practicing Ruby volume, but with a much more cohesive structure and many more opportunities for group discussions than we’ve had in the past. Putting this all together, I think we may have a formula for our best volume yet.

There are some challenges on the horizon, of course. Because we’ve nearly completed the transition to an open access publication model, it’s getting harder to convince new subscribers to pay for Practicing Ruby when so much of our content is available for free. I’ll resist the urge to whine about how the economics of the internet (and publishing in particular) are fundamentally broken, and instead say that there’s a tremendous amount of work that I haven’t done on the sales and marketing side of things which is most likely to blame for our gradually shrinking revenue stream.

The fact that I can no longer rely on Practicing Ruby as a primary source of income means that things are moving a lot slower. The funding provided by our subscribers makes sure that I spend at least a few days a month working on Practicing Ruby, but it’s much less time than I would invest into the project if it were fully funded.

The ultimate consequence of our razor-thin budget is just that we can expect everything to keep moving along somewhat slowly. Not nearly as slow as it was in May-July while I was spending a good chunk of my time with my newborn baby girl, but slow enough where it looks like it may take a year or more to produce this new volume. This is not the end of the world, but it’s a fact of life until we can find a way to get more money flowing into the project.

But honestly, even if the money situation gets worse before it gets better, I’m super excited about Practicing Ruby Volume 8 and the future of the project. I’m feeling rejuvenated and have a renewed interest in working on all of this, so it seems likely that one way or another, we’ll see Practicing Ruby live on for another year, and hopefully much more than that.

To all who have supported the project over the years, thanks so much, and here’s hoping for an amazing eighth volume!

To help me follow through on this plan, you can subscribe at practicingruby.com for only $8/month. If you’re already a subscriber, please consider switching to annual billing to help make sure we’re able to stick around over the long haul, and encourage your friends to subscribe!

Jul 27 '14

Prawn 1.2 released!

Today I published a new release of the Prawn PDF toolkit. It is mostly backwards compatible with 1.1, and includes some bug fixes, internal cleanup, and minor improvements.

Be sure to check out CHANGELOG to see exactly what changed.

Should you upgrade to Prawn 1.2?

If you are already running Prawn 0.15, 1.0, or 1.1, absolutely! If you are using tables in your PDFs, you’ll need to install the prawn-table gem and then require "prawn/table" to make your code work, but everything else should be backwards compatible.

If you’ve been using an older version of Prawn, you should treat your code as legacy and not assume that you’ll be able to upgrade without something breaking. Consider trying out our newer releases on your next project instead.

What will happen in future 1.x releases?

We’re going to keep working mostly on internal restructuring for the next several releases, in the hopes of making Prawn easier to extend through third-party gems, and also to make our own maintenance efforts easier. We’re going to either stabilize, extract, or cut any of the remaining experimental features between now and the far-future 2.0 release, but we’ll do it gradually so that it minimizes impact on Prawn’s users.

We have very limited time for the project currently, but little by little we hope to make Prawn better for everyone. Be sure to ask if you’re interested in helping out.

Getting help if you need it

Please send support requests to us via the Prawn mailing list, or see if anyone is around in the #prawn channel on Freenode. To report problems or contribute code, check out prawnpdf/prawn on Github.

Acknowledgements

I’d like to thank Simon Mansfield, James Coleman, Pete Sharum, Christian Hieke, and Hartwig Brandl for their help with this release.

Enjoy the new code, and Happy Prawning everyone! -greg

Jul 20 '14

Archaeological notes on The Law of Demeter

The Law of Demeter comes up often in software design conversations. Although many find the rule to be a helpful tool for eliminating tight coupling in the code, some treat it as an immutable law that isn’t subject to adaptation or scrutiny. This is the cause for much confusion, and the occasional controversy.

In The Law of Demeter: Some archaeological notes, David A. Black returns to the original source materials and conversations that established the Law of Demeter. Through a combination of intellectual curiosity and careful analysis, David shows that the Law of Demeter is something far more dynamic than what modern interpretations give it credit for, and hints at how the ideas behind it can be used for developing other kinds of rules for code organization and design practices.

Originally available to Practicing Ruby subscribers only, this article is now available to everyone. Go read it, and if you like what you see, check out the dozens of other articles that have been publicly released on practicingruby.com.


All articles on Practicing Ruby are reader-funded and reader-focused. We’re an independent publication and will never run advertisements.

If you’ve learned something useful from the content we’ve published, support our future work by subscribing. For only $8/month, you can help us make the programming world a more thoughtful and interesting place.

Jun 27 '14

Prawn 1.1.0 Released!

Today I published a new release of the Prawn PDF toolkit. It is mostly backwards compatible with 1.0.0, and includes some bug fixes, internal cleanup, and minor improvements.

Be sure to check out CHANGELOG to see exactly what changed.

Should you upgrade to Prawn 1.1?

If you are already running Prawn 0.15 or 1.0, absolutely! If you are using tables in your PDFs, you’ll need to require "prawn/table" to make your code work, but everything else should be backwards compatible.

If you’ve been using an older version of Prawn, you should treat your code as legacy and not assume that you’ll be able to upgrade without something breaking. Consider trying out our newer releases on your next project instead.

What will happen in future 1.x releases?

We’re going to keep working mostly on internal restructuring for the next several releases, in the hopes of making Prawn easier to extend through third-party gems, and also to make our own maintenance efforts easier. We’re going to either stabilize, extract, or cut any of the remaining experimental features between now and the far-future 2.0 release, but we’ll do it gradually so that it minimizes impact on Prawn’s users.

We have very limited time for the project currently, but little by little we hope to make Prawn better for everyone. Be sure to ask if you’re interested in helping out.

Getting help if you need it

Please send support requests to us via the Prawn mailing list, or see if anyone is around in the #prawn channel on Freenode. To report problems or contribute code, check out prawnpdf/prawn on Github.

Acknowledgements

I’d like to thank Evan Sharp, Uwe Kubosch, Deron Meranda, and Hartwig Brandl for their help with this release.

Enjoy the new code, and Happy Prawning everyone! -greg

Apr 9 '14

Practicing Ruby 6.4, 6.5, and 6.6 added to open access archives

Within the next few months, all of Practicing Ruby’s content will be freely available under the CC BY-SA license, and we will transition fully to an open-access publishing model.

Between now and then, we’re gradually releasing articles from behind the paywall, little-by-little. Today I’m happy to announce that the following issues are now publicly available:

  • Issue 6.4 discusses in obsessive detail the various ways of reusing code in Ruby — from classical inheritance and mixins to meta-programming and object composition. This article is possibly one of the most comprehensive articles you will find on the topic.

  • Issue 6.5 was written by Carol Nichols. It covers various tools and technique for dealing with buggy code without losing your cool.

  • Issue 6.6 is a straightforward recipe for taking methods that have too many parameters and streamlining them.

All of these articles have also been released in source-form on Github. Please see Practicing Ruby’s open source page for details on all of our F/OSS offerings, including our manuscripts repository.

Enjoy the articles, and happy hacking!

-greg

PS: Don’t forget that there are a total of 81 articles that have already been released to our public archives. If you haven’t checked those out yet, please do so and let us know what you think!

PPS: Although we’re making these learning materials freely available to all Ruby programmers, high quality independent publishing is a costly operation. The average Practicing Ruby article takes anywhere from 30-60 hours to produce, and our larger projects often take longer than that. You can support Practicing Ruby’s work by becoming a subscriber — it will only cost you $8/month but it makes a huge difference to us.

Mar 28 '14

A Link Dump from Practicing Rubyists

Lots of interesting links have been posted in Practicing Ruby’s campfire chat over the last couple weeks. Here are some of the things we’ve been reading, listening to, watching, and talking about:

Hope you enjoy this very eclectic set of links, and if you’re a subscriber to Practicing Ruby, I’d love to discuss them with you in our Campfire room!

Mar 17 '14

Prawn 1.0 is finally here

Work began on the Prawn PDF toolkit in April 2008, thanks to some generous crowd funding from the Ruby community. Nearly six years later, I’m proud to say that we’ve finally shipped our 1.0 release.

This 1.0 release represents a fresh start for Prawn, and I hope that even folks who have been using our project for several years will see it that way. Start with the Prawn by Example manual and our API documentation, and then get in touch via GitHub or our mailing list if you have questions or suggestions for us. Where six months ago you would have found a stagnant project, you should now see signs of life!

Our history is long and complicated, but the basic story is that Prawn is a very powerful but unwieldy ball of legacy code that we’ve been trying to rebuild from the inside out for many years now. We’re nowhere near done with that process, but we’ve done a lot over the last several months to get things pointed in the right direction.

It’s been a wild and bumpy ride, but over the last few months we’ve done the following things to make the project more sustainable:

  • Since 0.13 shipped in December 2013, we’ve resumed a monthly release cycle with clear and comprehensive notes in our CHANGELOG. We’re going to try to keep this going indefinitely, and will also maintenance releases when needed to fix bugs or make performance improvements.

  • We have put a free-flowing commit bit policy into action, and have added 20 new committers to Prawn since I resumed work on the project in November 2013.

  • Because our former core team members are mostly inactive now, I also invited two more people to help me with Prawn’s long-term maintenance and release management: Evan Sharp and Alexander Mankuta. While I’m planning to stick around for a while, Evan and Alex will help make sure the project doesn’t go stagnant again, even in my absence.

  • We have an official API compatibility policy now. If you look at it, you’re probably going to think “wow that’s complicated, why not just use semantic versioning?”, and you’d be right to think that. But the underlying goal is to make a clear dividing line between our stable API and extension API, and then either stabilize or extract all of our experimental features. Once that happens, we’ll make the switch to semantic versioning, probably starting with a 2.0 release somewhere down the line.

  • All open issues have now been responded to, and all pending pull requests have either been merged or rejected with a reason. We had something like 100+ issues without recent activity back in November, but we’re much better at being responsive now. Even though we’re not implementing very many new features or fixes ourselves, we’re trying to make it easier for others to contribute to the project by providing feedback, guidance, and reviews where needed.

  • We are starting to undo past mistakes by disabling or extracting features that never worked properly, and that we don’t currently have the resources to support. So far this has included the removal of our templates feature and transactions/grouping, and there may be more cuts to come in the coming months. Where possible, we’ll work with those who depend on these features to provide extension points that will let them live on as external add-ons to Prawn.

  • We’ve been putting an emphasis on returning to the ideas behind our original tagline: Fast, tiny, and nimble. The truth is that Prawn is none of those things right now, but the performance optimizations, introduction of new extension points, and restructuring of our internals over the last several months have hopefully hinted at our commitment to return to those roots.

A lot of the work that has been done over the last few months has been boring behind the scenes stuff, and for that reason our 1.0 release announcement isn’t nearly as exciting as what you might expect from a project with a less complicated history as our own. But if you give Prawn a spin, you’ll see that it is the product of many years of hard work from nearly 100 generous people around the world.

I’d like to thank the Ruby community for their initial funding of the project and the Prawn core team for putting so much effort into the project over the years. Over the last several months, I was provided with 100 hours of funded work from Madriska Inc. (Brad Ediger’s consulting company) to help get 1.0 out the door. So in addition to contributing a ton of code to Prawn, Brad also kicked in some money to help get it to where it is now!

Here’s hoping that somewhere in the not too distant future, we’ll have a Prawn 2.0 announcement to share with you. But even if that day never comes, I’m happy we at least made it this far.