More than anything else, we should prefer to err on the side of simplicity. It is always easier to make a simple thing a bit more complex, than it is to simplify a complicated solution.
corollary: We should prefer to ship something that only does 80% of what we need, and is missing the other 20%, rather than build something that solves 120% of our current needs by trying to imagine things we might want in the future. Attempts to anticipate the future rarely succeed, and often paint us into a corner.
Where complexity is unavoidable (such as at the edges of an application, where it must deal with overly complicated interfaces provided by external Gems), we should prefer to encapsulate that complexity in a local interface, rather than allow it to spread into the core of the application.
DRY (Don't Repeat Yourself) and SPOT (Single-Points of Truth) are our design ideals. Any time a solution requires the repetition of identical information in multiple places it is a future bug report in the making.
The public surface area of a class or module should be as small as we can possibly make it. Even within an object, where an instance method can choose between accepting a parameter or relying on an instance variable, passing the parameter is preferable. Small surface areas and tightly-scoped variables encourage locality of reasoning, and ease refactoring in a codebase.
We should prefer to use standard Rails-community solutions rather than Library-community solutions, wherever such a choice exists. Where the Rails community has a diversity of opinions on an approach, we should prefer the approach espoused by the Rails core team.
Ideally this codebase should be easy to step into for even a junior developer with just a bit of Rails experience. Bespoke solutions and counter-cultural solutions can seem very appealing to some, but they inhibit the long-term health of the project by making it hard for new people to step into the codebase, understand, and maintain it over time.
We should prefer to write code in the simplest way we can. Locality of reasoning is an important criteria in code readability -- a solution that can be read and understood in one place is infinitely preferable to a solution that sends the reader chasing down opaque definitions. Prefer some_string == 'ok' to OpaqueConversionUtility.convert(some_string, to: :boolean), which conceals important implementation details inside a separate set of definitions that the reader must hunt down to fully understand.
an important corollary to this is: concrete types are important information to the reader. Dependency Injection, and types hidden behind intefaces (concern_class.new) create uncertainty as to the types and interfaces in the code. Dependency Injection is a pattern better suited to static languages, where the compiler can assure us that any Class being passed into an interface can be proven to conform to a suitable interface.
The language being used in this project is Ruby. We should not be ashamed of that fact, or avoid the tools its dynamic nature affords us (ie. monkey-patching), out of some mistaken belief that by confining ourselves to some subset of features corresponding to static languages we are somehow being more rigurous or pure. To be explicit: monkey-patching can be a perfectly OK solution, when used sparingly and with good taste.
Tests are important, but it is a mistake to compromise the simplicity of a design in order to increase the simplicity of testing it. I would rather write an integration test than significantly complicate the interface of a module just to make it easier to write a unit test.
We will find ourselves in situations where we need to violate one or more of the above points. Taste and pragmitism are the order of the day.