whoami
Lucas Jenß
cat /etc/motd
The Coding Journal ツ — Notes taken on an epic coding journey. Technical solutions, debugging notes, and practical guides from the trenches of software development.
ls -la ~/languages/
- drwxr-xr-x
- ▶ PHP
- ▶ Ruby
- ▶ Scala
- ▶ C#
- ▶ JavaScript
- ▶ Objective-C
- ▶ Shell Scripting
ls -la ~/toolchain/
- drwxr-xr-x
- ▶ Typo3
- ▶ Akka
- ▶ Capistrano
- ▶ Git
- ▶ MAMP
- ▶ Adobe Illustrator
- ▶ NSTrackingArea (Cocoa)
uname -a
- drwxr-xr-x
- ▶ Mac OS X
- ▶ Unix
Refactoring Ruby methods away from hidden side effects
Refactoring a Ruby method with excessive side effects into a service object starts with a simple suspicion: a method named process_order may be doing far more than its name suggests. It could validate an order, reserve stock, charge a card, write an audit record, publish an event, and send an email. Each action is useful, but bundling them behind one apparently innocent call makes the important decision difficult to see and the failure paths hard to reason about.
That pattern is familiar in Australian product teams. A Rails application maintained by a small Sydney squad, with contributors in Melbourne and a client contact in Brisbane, often grows through practical patches rather than a grand rewrite. A method acquires a cache write here and an HTTP request there while its tests rely on mocks that no longer describe production behaviour. A service object creates a named boundary for those operations: domain rules stay in Ruby objects, external clients are supplied explicitly, and the caller receives a result that explains what happened. It is a design move, not a dumping ground for code that lacked a home.
Separate business decisions from side effects
Start by reading the method and writing down its actions in order. Which values are calculated locally, which records change, and which network calls can fail? A price calculation or eligibility check can be isolated in a deterministic object. Saving to ActiveRecord, calling a payment API, enqueueing ActiveJob, and delivering mail are side effects with different timing and failure characteristics. Naming that difference is more useful than moving the entire method into a class with a broader name.
For example, an order may qualify for a discount because of its customer, date, and line items. That decision can return a quote or a validation error without touching the database. The surrounding workflow can then reserve inventory, take payment, persist the order, publish an event, and notify the customer. The service object should make the sequence visible and let each collaborator report failure. It should not pretend that a remote gateway is as reliable as a local arithmetic operation.
Give the service explicit collaborators
A service object is an application-level object that coordinates one use case and its collaborators. A small plain-Ruby object is usually enough. Pass the order and its dependencies through the constructor, keep the public operation short, and return a consistent success or failure value. The service can remain an application service while smaller objects own calculation, persistence, and notification details.
class PlaceOrder
def initialize(order:, inventory:, payments:, orders:, events:, notifier:)
@order = order
@inventory = inventory
@payments = payments
@orders = orders
@events = events
@notifier = notifier
end
def call
quote = QuoteBuilder.new(@order).call
return Result.failure(:invalid_order) unless quote.valid?
@inventory.reserve!(@order)
payment = @payments.charge(@order, quote.total)
@order.payment_id = payment.id
@orders.save!(@order)
@events.publish("order.placed", order_id: @order.id)
@notifier.confirmation(@order)
Result.success(@order)
end
end
QuoteBuilder and Result are illustrative names; the important part is the explicit contract around each operation. The constructor acts as dependency injection: a test can provide an inventory double and a fake payment client, while production code supplies adapters that talk to PostgreSQL, Stripe, or ActiveJob. Keep call free of hidden constants such as User.find and ENV.fetch where practical. A result object gives the controller a clear branch for success, rejection, or retry rather than forcing it to interpret a random exception. At the Rails boundary, a controller can call PlaceOrder.new(...).call and render the returned result instead of reaching into the old method. In a real implementation, the reservation and charge need an idempotency key and a compensation or reconciliation path if the process dies between remote calls.
If several legacy callers still depend on the old API, a temporary compatibility layer can delegate to the new service. A related note on runtime module prepend shows how the replacement can happen without editing every call site at once. Treat that shim as a migration aid with a removal date, not as a second permanent design.
Compare responsibilities at the boundary
A short responsibility comparison exposes whether the extraction has genuinely improved the design or merely relocated a long method.
| Concern | Hidden in one method | Explicit in the service |
|---|---|---|
| Inputs | Reads globals, records, and configuration | Receives order data and named collaborators |
| Decision | Mixes eligibility with writes and delivery | Returns a quote, outcome, or validation failure |
| External work | Buries HTTP, database, mail, and jobs in call order | Gives each adapter a visible responsibility |
| Failure | Raises from an unexpected branch | Reports a result and supports targeted handling |
| Verification | Requires broad setup and brittle mocks | Supports small unit tests plus integration checks |
The service object is not a magic boundary around pure code. Its purpose is to make I/O visible, coordinate it in a deliberate order, and keep the decision that triggered it testable. Use a database transaction for local writes, an outbox for reliable event publication, and idempotent commands for retried jobs. Those choices matter more than choosing a fashionable gem; a Rails application can use a PORO, a command object, or a small object from its own domain layer.
That separation also appears in infrastructure work: automated policy backups make a repeatable protective action explicit before a security update, rather than leaving it buried in a maintenance script. Australian operating patterns make those details especially concrete. A Melbourne team supporting a national retailer may reconcile a payment provider's UTC timestamps while displaying Australian Eastern Standard Time or Australian Eastern Daylight Time, depending on the season. Injecting a clock and storing explicit offsets avoids an order appearing to move backwards during a daylight-saving change. The same explicitness helps when an on-call developer in Perth investigates a failed shipment while the primary team is in Sydney.
Test the service at useful seams
Start with fast unit tests around the service and its collaborators. An RSpec example can pass a valid order, assert that the quote is calculated, and verify that the payment adapter receives the expected amount. Separate examples should cover insufficient stock, a declined card, a notification failure after a successful charge, and a retry with the same idempotency key. These tests describe policy without opening a socket or rendering a view.
Keep a smaller set of integration tests around ActiveRecord, the job queue, and the real adapter contract. They should prove that a successful operation persists the expected records and that a failed operation leaves a recoverable state. VCR or an equivalent fixture can make HTTP behaviour repeatable, but do not let recorded fixtures hide changed request fields. Run the suite on the same Ruby and dependency versions used in the release pipeline, then add a contract test whenever an external provider changes its response shape.
Because the service returns a status rather than markup, a front end can choose its own feedback. If an order screen uses an animated indicator, the animated SVG sprites example keeps that visual asset and inline CSS outside the Ruby workflow. The same boundary keeps a visual tweak from causing a payment adapter test to fail.
Roll out the change with a short checklist
Release the refactor in a way that lets the team observe behaviour rather than admire the new class. Keep the old method as a thin delegating entry point, log the service result with a correlation identifier, and compare success and failure rates before removing the old path. A feature flag or a small pilot tenant gives a no-drama rollout when the method handles real payments.
- Extract the calculation first, then move one side-effect cluster at a time.
- Add result values and typed failures for the paths callers must distinguish.
- Test compensation, retry, and idempotency alongside the happy path.
- Remove the compatibility layer after usage has fallen to zero and the contract is documented.
In a hybrid Australian team, put the sequence and rollback rules in the pull request and release notes. Enterprise and government buyers often expect an audit trail and rollback evidence, and a consultancy handover benefits from a shared service contract. The important thing is a small, observable change that preserves business behaviour while making future changes cheaper.
Replace the next oversized method with a named service boundary, wire in explicit collaborators, run the focused tests, and open the pull request with the measured behaviour in the description.
cat ~/interests.json
| Key | Value |
|---|---|
| editor | Terminal-first workflow |
| os | Mac OS X / Unix |
| vcs | Git, distributed version control |
| deploy | Capistrano, cron automation |
| graphics | SVG, Adobe Illustrator troubleshooting |
| networking | IP validation, SSH, VPN |
git log --oneline --reverse
Solving SVG import issues in Adobe Illustrator CS6 and CC
When importing an SVG into Illustrator, the operation fails with an unknown error [CANT]. A workaround for this Adobe-side bug.
Solving NDK build issues on OS X
Troubleshooting native development kit compilation problems on Mac OS X.
Programmatically adding PHP generated TypoScript to the backend configuration
Integrating dynamically generated TypoScript into Typo3 backend setups using PHP.
ArgumentError: Could not parse PKey: no start line
Debugging an SSH key parsing error encountered during deployment.
Validating IP-Addresses in PHP
Using PHP filter functions with flags like FILTER_FLAG_IPV4 and FILTER_FLAG_IPV6, and understanding how filter_var handles reserved IP addresses.
Cocoa: Using NSTrackingArea
A short tutorial on using Cocoa's NSTrackingArea to capture mouseEntered and mouseExited events.
cat ~/contact.txt