<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Vinicius Porto</title>
    <description>The latest articles on DEV Community by Vinicius Porto (@viniciuspuerto).</description>
    <link>https://dev.to/viniciuspuerto</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F374428%2Fb22cd406-8c7a-489b-9654-1878845c5186.jpeg</url>
      <title>DEV Community: Vinicius Porto</title>
      <link>https://dev.to/viniciuspuerto</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/viniciuspuerto"/>
    <language>en</language>
    <item>
      <title>When the Scraper Breaks Itself: Building a Self-Healing CSS Selector Repair System</title>
      <dc:creator>Vinicius Porto</dc:creator>
      <pubDate>Wed, 01 Apr 2026 23:15:54 +0000</pubDate>
      <link>https://dev.to/viniciuspuerto/when-the-scraper-breaks-itself-building-a-self-healing-css-selector-repair-system-312d</link>
      <guid>https://dev.to/viniciuspuerto/when-the-scraper-breaks-itself-building-a-self-healing-css-selector-repair-system-312d</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A Python sidecar that watches your scraper fail, calls a local LLM, and fixes the problem before your users notice.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Problem with Fragile Selectors
&lt;/h2&gt;

&lt;p&gt;Production web scrapers have a hidden fragility: they depend on CSS selectors and XPath expressions authored against a snapshot of a third-party website's DOM. The moment the site redesigns its layout, renames a class, or restructures a table, those selectors silently return nothing — or, worse, return the wrong data.&lt;/p&gt;

&lt;p&gt;For a surf alert system that monitors dozens of forecast sources, this is a recurring operational problem. A selector like &lt;code&gt;tr.forecast-table__row[data-row-name="wave-heigth"]&lt;/code&gt; (yes, a typo the upstream site just fixed) breaks at 3 AM. The scraper records a failure, the forecast pipeline stalls, and users stop receiving alerts for a beach they care about. An engineer wakes up to a Slack notification, digs through logs, finds the selector, pushes a fix, and deploys.&lt;/p&gt;

&lt;p&gt;The fix itself usually takes under five minutes. The detection, investigation, and deploy ceremony takes two hours.&lt;/p&gt;

&lt;p&gt;This is not a scaling problem — it is a &lt;strong&gt;friction problem&lt;/strong&gt;. The actual repairs are trivially mechanical: look at the new HTML, find the element, write a new selector. Automating that loop, safely, is what the Self-Healer is for.&lt;/p&gt;




&lt;h2&gt;
  
  
  Core Idea in One Paragraph
&lt;/h2&gt;

&lt;p&gt;When the Ruby scraper fails to extract a field (e.g., &lt;code&gt;wave_height&lt;/code&gt; returns nil), it publishes a &lt;strong&gt;repair job&lt;/strong&gt; to a Redis queue. A Python sidecar — the Self-Healer — picks up that job, fetches the current HTML from the source URL, trims it to fit a token budget, and sends a targeted prompt to a &lt;strong&gt;local LLM running via MLX&lt;/strong&gt;. The LLM proposes new CSS/XPath selector candidates with confidence scores and reasoning. Each candidate is then tested against the live HTML using BeautifulSoup and lxml, and the extracted value is validated against a type schema (e.g., &lt;code&gt;float:0.1-20.0&lt;/code&gt; for wave height). If a candidate passes, the new selector is written directly to &lt;code&gt;data_sources.selector_overrides&lt;/code&gt; in PostgreSQL — no redeploy required. The next scraper run reads the updated config and proceeds as if nothing happened.&lt;/p&gt;




&lt;h2&gt;
  
  
  Design Principles
&lt;/h2&gt;

&lt;p&gt;The shape of this system comes from a few deliberate constraints.&lt;/p&gt;

&lt;h3&gt;
  
  
  LLM as proposer, not decider
&lt;/h3&gt;

&lt;p&gt;The model suggests selectors. Code decides whether they work. Every candidate goes through deterministic BeautifulSoup/lxml evaluation against the actual HTML before anything touches the database. The LLM output is treated like a PR from an intern: read with interest, merged only after review.&lt;/p&gt;

&lt;p&gt;This matters because LLMs are confident even when wrong. A model can generate a plausible-looking XPath that matches the wrong column in a table — extracting the header label instead of the numeric value. Type validation catches this: &lt;code&gt;"Wave Height (m)"&lt;/code&gt; fails &lt;code&gt;float:0.1-20.0&lt;/code&gt;, so the candidate is rejected.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sandbox before promotion
&lt;/h3&gt;

&lt;p&gt;Candidates run against fetched HTML in-process, using the same parsing libraries the scraper would use. There is no staging environment, no shadow traffic, no A/B rollout. The sandbox &lt;em&gt;is&lt;/em&gt; the gate. A selector that does not match the current HTML and extract a value that passes type validation never reaches the database.&lt;/p&gt;

&lt;h3&gt;
  
  
  Escalation over hallucination
&lt;/h3&gt;

&lt;p&gt;Not all broken selectors can be repaired automatically. Two cases get escalated instead of guessed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;JS-rendered pages&lt;/strong&gt;: If the fetched HTML is an empty SPA shell — detected via React/Vue/Angular framework markers, sparse body text relative to total HTML size, or loading spinners — there is no static selector to find. The repair status becomes &lt;code&gt;ESCALATED_JS&lt;/code&gt;, the &lt;code&gt;data_sources&lt;/code&gt; row is flagged with &lt;code&gt;is_js_rendered = true&lt;/code&gt;, and a Slack message goes to the team.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exhausted retries&lt;/strong&gt;: If the LLM generates candidates across multiple context window widths and none pass validation, the status becomes &lt;code&gt;ESCALATED_HUMAN&lt;/code&gt;. Automated repair has reached its limit; a human needs to look.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both paths are first-class outcomes, not error states. They produce repair log entries, update DB flags, and send structured Slack notifications — so the team has full visibility without polling logs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Decoupling via Redis
&lt;/h3&gt;

&lt;p&gt;The Ruby scraper and Python sidecar share nothing except two Redis queues: &lt;code&gt;healer:jobs&lt;/code&gt; (input) and &lt;code&gt;healer:results&lt;/code&gt; (output). The scraper does &lt;code&gt;LPUSH healer:jobs&lt;/code&gt; on failure; the healer does &lt;code&gt;BRPOP healer:jobs&lt;/code&gt; and blocks until a job arrives. There is no shared process, no RPC, no HTTP call between services. Either side can restart independently without affecting the other.&lt;/p&gt;

&lt;h3&gt;
  
  
  Local inference
&lt;/h3&gt;

&lt;p&gt;The LLM runs on-device via &lt;a href="https://github.com/ml-explore/mlx" rel="noopener noreferrer"&gt;MLX&lt;/a&gt;, Apple's machine learning framework for Apple Silicon. This has three practical implications: fetched HTML never leaves the machine, there are no API costs per repair, and latency is bounded by local compute rather than network round-trips. The LLM client uses an OpenAI-compatible HTTP API (&lt;code&gt;POST /chat/completions&lt;/code&gt;), so switching to a cloud model is a one-line config change if needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Traceability by default
&lt;/h3&gt;

&lt;p&gt;Every repair attempt — successful or not — writes to &lt;code&gt;selector_repair_logs&lt;/code&gt;. Jobs carry an optional &lt;code&gt;scrape_failure_id&lt;/code&gt; that links back to the original &lt;code&gt;scrape_failures&lt;/code&gt; row. When a repair succeeds, &lt;code&gt;scrape_failures.resolved_at&lt;/code&gt; is stamped and &lt;code&gt;resolved_by&lt;/code&gt; is set to &lt;code&gt;'healer'&lt;/code&gt;. The full audit trail is in the database, queryable, and joinable.&lt;/p&gt;




&lt;h2&gt;
  
  
  System Context
&lt;/h2&gt;

&lt;p&gt;The Self-Healer is a container in the existing Docker Compose stack, enabled via &lt;code&gt;--profile healer&lt;/code&gt;. It adds no new database (it reads and writes to shared PostgreSQL), no new queue infrastructure (it uses the existing Redis), and no new external dependencies beyond the local MLX process and a Slack webhook.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzcljecboe7feezfphwtl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzcljecboe7feezfphwtl.png" alt=" " width="800" height="933"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  End-to-End Pipeline
&lt;/h2&gt;

&lt;p&gt;A single repair job moves through eight steps:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Job arrives.&lt;/strong&gt; &lt;code&gt;RepairWorker&lt;/code&gt; pops from &lt;code&gt;healer:jobs&lt;/code&gt;. The job carries &lt;code&gt;beach_name&lt;/code&gt;, &lt;code&gt;field_name&lt;/code&gt;, &lt;code&gt;failed_selector&lt;/code&gt;, &lt;code&gt;source_url&lt;/code&gt;, and optionally an &lt;code&gt;html_snapshot&lt;/code&gt; and a &lt;code&gt;last_known_good_value&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. HTML fetch.&lt;/strong&gt; &lt;code&gt;HTMLFetcher&lt;/code&gt; makes a plain HTTP GET to &lt;code&gt;source_url&lt;/code&gt;. If the job included an &lt;code&gt;html_snapshot&lt;/code&gt;, it is used directly — useful for testing and replaying past failures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. JS detection.&lt;/strong&gt; &lt;code&gt;JSDetector&lt;/code&gt; checks the HTML for framework markers (&lt;code&gt;&amp;lt;div id="root"&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;, &lt;code&gt;data-reactroot&lt;/code&gt;, &lt;code&gt;ng-app&lt;/code&gt;, etc.), loading indicators, and sparse body content relative to total HTML size. If any check fires, the job escalates immediately. Feeding an LLM trimmed markup from an empty SPA shell produces useless selectors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. HTML trimming.&lt;/strong&gt; &lt;code&gt;HTMLTrimmer&lt;/code&gt; reduces a 300 KB page to a ~5 KB snippet. It removes &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt;, and other non-content tags; strips non-selector attributes (keeping &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;class&lt;/code&gt;, &lt;code&gt;data-*&lt;/code&gt;, &lt;code&gt;aria-*&lt;/code&gt;); locates the element nearest the target selector or the expected value; then walks up &lt;code&gt;context_levels&lt;/code&gt; parent nodes to include enough surrounding structure. The default is 3 levels. Retries use 5, 7, then 9 levels.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. LLM call.&lt;/strong&gt; &lt;code&gt;LLMClient&lt;/code&gt; builds a prompt with the field name, the failed selector, the expected type, the last known good value, and the trimmed HTML snippet. It posts to the MLX OpenAI-compatible endpoint with &lt;code&gt;temperature=0.3&lt;/code&gt;. The response is parsed into a &lt;code&gt;SelectorCandidates&lt;/code&gt; object. If the model outputs preamble before the JSON (some models do), &lt;code&gt;_extract_balanced_brace&lt;/code&gt; finds the &lt;code&gt;{...}&lt;/code&gt; block regardless.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Sandbox.&lt;/strong&gt; &lt;code&gt;SelectorSandbox&lt;/code&gt; runs each candidate against the full HTML using BeautifulSoup (CSS) or lxml (XPath) and extracts the matched value. Results are sorted: successful matches first, then by LLM confidence score.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Validation.&lt;/strong&gt; &lt;code&gt;Validator&lt;/code&gt; checks each passing sandbox result against the expected type spec. For &lt;code&gt;float:0.1-20.0&lt;/code&gt;, it extracts the numeric portion via regex (handling mixed strings like &lt;code&gt;"1.5ESE"&lt;/code&gt;), converts to float, and range-checks it. A header cell returning &lt;code&gt;"Wave Height (m)"&lt;/code&gt; fails here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. Promote or retry/escalate.&lt;/strong&gt; The first candidate that passes validation is promoted: &lt;code&gt;selector_overrides&lt;/code&gt; is updated, the repair log is written, the scrape failure is resolved, and Slack gets a success notification. If no candidate passes and retries remain, the trimmer is called again with wider context. If retries are exhausted, the job escalates to human review.&lt;/p&gt;

&lt;p&gt;The sequence diagram below shows how messages and data move across services for a single repair:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqxvxqags76w75ahjxapb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqxvxqags76w75ahjxapb.png" alt=" " width="800" height="581"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the internal step-by-step flow inside a single repair attempt:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ra3hhsva4vfjakupx48.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ra3hhsva4vfjakupx48.png" alt=" " width="800" height="1201"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Deep Dives
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Token budget and the context_levels retry loop
&lt;/h3&gt;

&lt;p&gt;Sending a full HTML page to an LLM is wasteful and often impossible — 300 KB of HTML is around 75,000 tokens, well beyond typical context windows and full of noise. The trimmer's job is to find the smallest HTML fragment that still gives the LLM enough signal.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;context_levels&lt;/code&gt; parameter controls how far up the DOM tree to walk from the target element. At level 3 you might get &lt;code&gt;table &amp;gt; tbody &amp;gt; tr &amp;gt; td&lt;/code&gt; — enough to understand the structure. At level 9 (used on the final retry) you might get the full forecast section container. Each retry widens the window, trading token cost for contextual richness.&lt;/p&gt;

&lt;p&gt;This mirrors how a human engineer approaches the same problem: start by looking at the specific row, and if that isn't enough, zoom out to the table.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpu5g76al8sbnsa7yde1b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpu5g76al8sbnsa7yde1b.png" alt=" " width="800" height="54"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Prompting for structured output
&lt;/h3&gt;

&lt;p&gt;The prompt template is explicit about output format: "You must respond with ONLY a single JSON object. Start your response with &lt;code&gt;{&lt;/code&gt; and end with &lt;code&gt;}&lt;/code&gt;." It also encodes rules that push the model toward stable selectors: prefer &lt;code&gt;data-*&lt;/code&gt; and &lt;code&gt;aria-*&lt;/code&gt; attributes over class names, prefer label-anchored selectors over positional &lt;code&gt;nth-child&lt;/code&gt;, include both CSS and XPath variants when possible.&lt;/p&gt;

&lt;p&gt;The structured output — a &lt;code&gt;candidates&lt;/code&gt; array with &lt;code&gt;selector&lt;/code&gt;, &lt;code&gt;type&lt;/code&gt;, &lt;code&gt;confidence&lt;/code&gt;, &lt;code&gt;reasoning&lt;/code&gt;, and &lt;code&gt;attribute&lt;/code&gt; — lets the pipeline operate deterministically on the model's output without parsing prose. The &lt;code&gt;reasoning&lt;/code&gt; field is stored in the repair log, useful for post-incident review and prompt iteration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why type validation is not enough
&lt;/h3&gt;

&lt;p&gt;A selector can match the right element &lt;em&gt;type&lt;/em&gt; but the wrong &lt;em&gt;element&lt;/em&gt;. Consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;tr&lt;/span&gt; &lt;span class="na"&gt;data-row-name=&lt;/span&gt;&lt;span class="s"&gt;"wave-height"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;Wave Height (m)&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;   &lt;span class="c"&gt;&amp;lt;!-- header --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;1.5&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;               &lt;span class="c"&gt;&amp;lt;!-- data    --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the LLM generates &lt;code&gt;tr[data-row-name="wave-height"] th&lt;/code&gt; instead of &lt;code&gt;td&lt;/code&gt;, the sandbox extracts &lt;code&gt;"Wave Height (m)"&lt;/code&gt;. Type validation rejects this because it cannot parse a float from that string. Correct outcome, for the right reason.&lt;/p&gt;

&lt;p&gt;The deeper issue is that the healer currently validates in isolation. It does not verify that the extracted HTML structure matches what the Ruby scraper's &lt;code&gt;process_wave_data&lt;/code&gt; method expects downstream — for example, a &lt;code&gt;&amp;lt;td&amp;gt;&lt;/code&gt; carrying a &lt;code&gt;data-swell-state&lt;/code&gt; JSON attribute that encodes the full swell envelope. A selector that extracts &lt;code&gt;1.5&lt;/code&gt; from a plain &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; would pass today's validation but silently break the downstream extraction logic.&lt;/p&gt;

&lt;p&gt;This is an acknowledged gap. The roadmap includes a &lt;code&gt;StructuralValidator&lt;/code&gt; that codifies field-level DOM requirements (element type, required attributes, required children) and a &lt;code&gt;ValueCrossValidator&lt;/code&gt; that compares extracted values against recent historical data for the same beach — catching plausible-looking outliers that type ranges alone would pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh3zaet5719q8j7ntmeki.png" alt=" " width="800" height="1187"&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Limitations and Honest Boundaries
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Static HTML only.&lt;/strong&gt; The automated repair path works only when target data is present in the raw HTTP response. Single-page applications that populate content via JavaScript require a headless browser for rendering, which is a different class of tooling. For now, JS-rendered sources are escalated rather than guessed at.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scraper coupling.&lt;/strong&gt; Even a correctly extracted value might not flow through the pipeline if the scraper's &lt;code&gt;process_*&lt;/code&gt; methods expect a specific DOM structure the new selector doesn't deliver. The healer validates the selector in isolation; it cannot yet confirm end-to-end compatibility with Ruby-side extraction logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LLM correctness.&lt;/strong&gt; A local model running at &lt;code&gt;temperature=0.3&lt;/code&gt; is not a theorem prover. The validation pipeline exists precisely because the model can propose syntactically valid but semantically wrong selectors. No incorrect LLM suggestion can reach production without passing independent deterministic checks — but monitoring scrape quality over time (alerts, golden samples, periodic audits of &lt;code&gt;selector_overrides&lt;/code&gt;) remains necessary.&lt;/p&gt;




&lt;h2&gt;
  
  
  Roadmap: From Reactive Repair to Proactive Extraction Engine
&lt;/h2&gt;

&lt;p&gt;The current system operates in &lt;strong&gt;repair mode&lt;/strong&gt;: it reacts to production failures and fixes them. The building blocks — fetch, detect, trim, prompt, sandbox, validate, promote — compose naturally into a second mode.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scout mode&lt;/strong&gt; would run proactively for new sources: given a URL and a target schema (wave height, period, wind speed, tide, etc.), the system discovers selectors without waiting for a failure. It adds stricter &lt;strong&gt;series validation&lt;/strong&gt; — not just "does this selector extract &lt;em&gt;a&lt;/em&gt; float?" but "does it extract &lt;em&gt;a time-aligned series&lt;/em&gt; of consistent floats across multiple forecast periods?" — and returns a structured config the Ruby scraper can consume immediately.&lt;/p&gt;

&lt;p&gt;The architectural upgrade this enables is significant. Instead of onboarding a new surf forecast source by manually inspecting HTML and writing selectors, an engineer submits a URL and reviews the proposed config. The same Python service handles both ongoing repair and initial discovery with shared components and a single mental model. The system stops being "a scraper with a repair button" and becomes a &lt;strong&gt;forecast extraction engine&lt;/strong&gt; with two modes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fftdlsjup1tj7yer2bqsd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fftdlsjup1tj7yer2bqsd.png" alt=" " width="800" height="2202"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>python</category>
    </item>
    <item>
      <title>The Staging Ground Symbiote: A Deep Dive into Monkey Patching for Bug and Exception Simulation (Part 1: Foundations &amp; Risks)</title>
      <dc:creator>Vinicius Porto</dc:creator>
      <pubDate>Sun, 19 Oct 2025 00:25:15 +0000</pubDate>
      <link>https://dev.to/viniciuspuerto/the-staging-ground-symbiote-a-deep-dive-into-monkey-patching-for-bug-and-exception-simulation-36fn</link>
      <guid>https://dev.to/viniciuspuerto/the-staging-ground-symbiote-a-deep-dive-into-monkey-patching-for-bug-and-exception-simulation-36fn</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;The chasm between a developer's local environment and the complex, interconnected reality of a deployed staging or QA environment is the birthplace of some of software engineering's most frustrating bugs. The infamous phrase, "but it works on my machine," is more than a meme; it is a testament to the emergent, unpredictable behaviors that arise from the interplay of shared databases, third-party APIs, network latency, and concurrent user actions. Reproducing these elusive, environment-specific issues is often the first and most challenging step toward resolving them. How can an engineer reliably test the resilience of an application against a database connection pool exhaustion that only occurs under specific load, or a third-party payment gateway that intermittently times out? Waiting for these conditions to manifest organically is inefficient and unreliable.&lt;/p&gt;

&lt;p&gt;This is where one of software development's most powerful and controversial techniques enters the stage: &lt;strong&gt;monkey patching&lt;/strong&gt;. At its core, monkey patching is the practice of dynamically modifying or extending code at runtime. In dynamic languages like Ruby, this means having the ability to reopen any class—even core language classes or those from third-party libraries—and change its behavior on the fly. While its use in production code is widely debated and often condemned as an anti-pattern that creates brittle, unmaintainable systems, its application within the controlled confines of staging and QA environments presents a compelling case. Here, monkey patching transforms from a potential architectural liability into a precision tool for chaos engineering, enabling engineers to simulate a vast array of failure modes, exceptions, and bugs with surgical accuracy.&lt;/p&gt;

&lt;p&gt;This three-part series provides a comprehensive, publication-ready exploration of using monkey patching for fault injection in staging and QA environments, targeted at intermediate to senior software engineers working primarily within the Ruby and Rails ecosystem. In this first installment, we navigate the theoretical underpinnings and the long-standing debate over its classification as a design pattern or an anti-pattern, followed by a thorough examination of the significant security considerations. Part 2 will dive deep into practical implementation with numerous complete code examples, while Part 3 will equip you with a decision-making framework, best practices, and thought-provoking discussion points to foster a deeper understanding of this potent practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design Pattern and Theoretical Foundation
&lt;/h2&gt;

&lt;p&gt;The practice of monkey patching occupies a contentious and ambiguous space within the established lexicon of software design. It is a technique born of the extreme flexibility offered by dynamic languages, yet it often stands in direct opposition to the principles of structure, predictability, and encapsulation that underpin classical software architecture. To truly understand its role, particularly in the context of testing and fault injection, one must first explore the vigorous debate surrounding its identity, its deep connection to the broader concept of metaprogramming, and how it contrasts with the canonical design patterns that offer more structured solutions to similar problems.&lt;/p&gt;

&lt;p&gt;The central debate is whether monkey patching should be classified as a pragmatic design pattern or a dangerous anti-pattern. Proponents of the anti-pattern classification build their case on a foundation of core software engineering principles that the practice inherently violates. Monkey patching undermines &lt;strong&gt;modularity&lt;/strong&gt; by creating invisible, runtime dependencies between otherwise disconnected parts of a system. A patch applied to a core library class in one part of the application can have unforeseen and disastrous consequences in another, completely unrelated part. It shatters the principle of &lt;strong&gt;information hiding&lt;/strong&gt; by reaching into the internal implementation details of a class or module to alter its behavior, creating a tight and brittle coupling that is prone to breaking when the underlying library is updated. This discrepancy between the static source code and the dynamic runtime behavior dramatically increases cognitive load for developers, making the system harder to reason about, debug, and maintain. In a collaborative environment, this can lead to a maintenance nightmare, where multiple developers unknowingly patch the same methods, creating subtle conflicts that manifest as maddeningly intermittent bugs.&lt;/p&gt;

&lt;p&gt;Despite this formidable list of criticisms, a pragmatic defense of monkey patching persists, arguing that in certain contexts, it is not an anti-pattern but a necessary tool—a "lesser evil" when no better options are available. In staging and QA environments, its value proposition shifts. The goal is no longer long-term maintainability but controlled, temporary chaos. It serves as an indispensable technique for simulating failure modes in third-party dependencies, allowing teams to test retry logic, circuit breakers, and user-facing error handling without relying on the unpredictable availability of external systems. For emergency hotfixes in production, a carefully crafted monkey patch can be a lifeline, restoring critical functionality in minutes while a proper, permanent fix is developed and deployed through standard processes. Similarly, when integrating with poorly designed legacy systems or APIs that lack proper extension points, monkey patching can provide a crucial bridge to achieve necessary functionality. In these scenarios, its defenders argue, the immediate, practical benefits outweigh the long-term architectural risks, provided the patch is treated as a temporary, well-documented, and highly targeted intervention.&lt;/p&gt;

&lt;p&gt;Fundamentally, monkey patching is a specific application of &lt;strong&gt;metaprogramming&lt;/strong&gt;—the concept of code that writes or manipulates other code. This connection is vital because it frames monkey patching not as an isolated hack but as part of a spectrum of powerful, language-level capabilities that include introspection (examining code at runtime) and dynamic code generation. Ruby, in particular, has a culture that deeply embraces metaprogramming, making runtime modification a natural and accessible feature. This cultural acceptance is a double-edged sword. The same dynamic features that enable elegant Domain-Specific Languages (DSLs) and the magic of frameworks like Ruby on Rails can become significant security vulnerabilities when misused. A related metaprogramming concept, often seen in the Ruby world, is "duck punching," which typically involves modifying a single object instance at runtime to alter its behavior, offering a more localized and slightly less dangerous alternative to class-level monkey patching.&lt;/p&gt;

&lt;p&gt;When contrasted with classical design patterns from the seminal "Gang of Four" book, the unstructured nature of monkey patching becomes even more apparent. The &lt;strong&gt;Decorator&lt;/strong&gt; pattern, for instance, also adds behavior to objects dynamically, but it does so through composition, wrapping objects in decorator classes that share the same interface. This approach is explicit, maintainable, and respects object boundaries, unlike monkey patching, which directly modifies the original class. The &lt;strong&gt;Adapter&lt;/strong&gt; pattern solves the problem of incompatible interfaces by creating a new adapter class that acts as a translator, preserving the integrity of the original classes. Monkey patching might be used to crudely force compatibility by altering a class directly, but the Adapter pattern provides a clean, architecturally sound solution. Similarly, the &lt;strong&gt;Strategy&lt;/strong&gt; pattern encapsulates interchangeable algorithms, allowing a client to select one at runtime. While one could mimic this by swapping out method implementations with a monkey patch, the Strategy pattern does so in a structured, type-safe, and explicit manner. These classical patterns promote key architectural principles like high cohesion, separation of concerns, and explicit dependencies, all of which are subverted by the use of monkey patching, which tends to create hidden coupling and unpredictable side effects that make systems harder to scale, refactor, and maintain. The historical evolution of the practice across dynamic languages tells a consistent story: an initial period of enthusiastic adoption for its flexibility, followed by a gradual shift toward cautious, restrained application as teams encountered the painful realities of its impact on large-scale systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security Considerations
&lt;/h2&gt;

&lt;p&gt;While the architectural debates surrounding monkey patching often focus on maintainability and complexity, the security implications are equally, if not more, critical, particularly in environments that handle sensitive data or act as a gateway to production systems. The very nature of monkey patching—the ability to alter code behavior at runtime—creates a potent attack surface that can bypass traditional security controls and static analysis tools. For engineers leveraging this technique in staging and QA, a deep understanding of the associated risks, real-world attack vectors, and effective mitigation strategies is not just best practice; it is an absolute necessity to prevent these pre-production environments from becoming a weak link in the organization's security posture.&lt;/p&gt;

&lt;p&gt;The vulnerabilities introduced by monkey patching can be categorized into several distinct classes. The most direct threat is &lt;strong&gt;code poisoning and injection&lt;/strong&gt;. By providing a mechanism for runtime code modification, monkey patching opens a door for malicious actors to alter critical system logic. An attacker who gains access to a dependency or the application's deployment pipeline could inject a patch that modifies authentication methods to bypass login checks, alters data serialization routines to exfiltrate sensitive information, or overrides security validation functions to allow malicious input. Because these modifications happen dynamically, they can be designed to evade static code analysis, making them exceptionally difficult to detect through standard code reviews. In JavaScript ecosystems, a related vulnerability known as "prototype pollution," where an attacker modifies &lt;code&gt;Object.prototype&lt;/code&gt;, can have a devastatingly broad impact, injecting malicious behavior into nearly every object in an application.&lt;/p&gt;

&lt;p&gt;Another significant risk involves the subversion of language-level security features, such as Ruby's tainted data mechanism. Tainted mode is designed to track data originating from untrusted external sources (like user input) and prevent it from being used in security-sensitive operations. However, several documented Common Vulnerabilities and Exposures (CVEs) have shown how metaprogramming and dynamic modification can undermine these protections. For example, &lt;strong&gt;CVE-2018-16396&lt;/strong&gt; revealed that tainted flags were not properly propagated in Ruby's &lt;code&gt;Array#pack&lt;/code&gt; and &lt;code&gt;String#unpack&lt;/code&gt; methods, allowing untrusted input to bypass security checks. Similarly, &lt;strong&gt;CVE-2015-7551&lt;/strong&gt; highlighted unsafe tainted string usage in the Fiddle and DL libraries, which could lead to arbitrary code execution. These incidents demonstrate that monkey patching can create subtle holes in a language's built-in security model.&lt;/p&gt;

&lt;p&gt;Perhaps the most pervasive and modern threat is the role of monkey patching in &lt;strong&gt;supply chain attacks&lt;/strong&gt;. When an application relies on dozens or hundreds of third-party dependencies, each of those dependencies becomes a potential vector for attack. An attacker who compromises a popular open-source library can inject a malicious monkey patch that will then be propagated to every application that uses it. This was a key concern in incidents like the SolarWinds attack, where the update mechanism itself was abused to distribute compromised code. The MITRE supply chain attack framework explicitly identifies malicious code insertion during the build or deployment process as a common pattern, a threat that is significantly amplified by the dynamic nature of monkey patching. In staging and QA environments, these risks are often heightened. These environments may have more relaxed security controls, may contain production-like (or even real, poorly sanitized) data, and can serve as a valuable testing ground for an attacker to validate a malicious patch before attempting to deploy it to production. A compromised staging environment can become a pivot point for lateral movement into the production network.&lt;/p&gt;

&lt;p&gt;The most infamous real-world example of monkey patching being weaponized was the 2009 conflict between the Firefox extensions NoScript and Adblock Plus. The developer of NoScript allegedly used monkey patching to inject code that would disable Adblock Plus on his own websites, effectively forcing users to see his ads. This escalated into a digital arms race, with each extension pushing updates to override the other's patches, trapping users in the middle. The incident served as a stark demonstration of how monkey patching could be used to sabotage competing software and manipulate user experience without consent, ultimately eroding trust in the entire ecosystem. More recent Ruby CVEs, such as &lt;strong&gt;CVE-2019-16255&lt;/strong&gt; (a code injection vulnerability in &lt;code&gt;Shell#[]&lt;/code&gt;) and &lt;strong&gt;CVE-2016-2098&lt;/strong&gt; (an arbitrary code execution flaw in Rails' &lt;code&gt;render&lt;/code&gt; method), further underscore how the metaprogramming features that enable monkey patching can be exploited to create critical security flaws.&lt;/p&gt;

&lt;p&gt;Mitigating these substantial risks requires a multi-layered approach. Rigorous &lt;strong&gt;code review and static analysis&lt;/strong&gt;, while not foolproof, can help by scanning for common patching patterns and auditing dependencies for suspicious modifications. Maintaining a Software Bill of Materials (SBOM) provides a clear inventory of all components and their versions, aiding in vulnerability management. Since static analysis is insufficient, &lt;strong&gt;runtime monitoring and dynamic analysis&lt;/strong&gt; become paramount. Dynamic Application Security Testing (DAST) tools, behavior monitoring, and anomaly detection can identify unexpected changes in application behavior at runtime that may indicate a malicious patch. In terms of process, enforcing principles of &lt;strong&gt;least privilege&lt;/strong&gt;, requiring cryptographic code signing, and mandating security reviews for any monkey patch are essential controls. Ultimately, the most effective mitigation is to prioritize safer alternatives whenever possible. Language features like Ruby's scoped Refinements, architectural patterns like Decorators and Dependency Injection, and configuration-based tools like feature flags can often achieve the same testing goals without introducing the profound security risks associated with global, runtime code modification.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion to Part 1
&lt;/h2&gt;

&lt;p&gt;In this first installment of our series, we have established the theoretical and security foundations necessary to understand monkey patching as a tool for fault injection. We've explored its contentious position in software design—straddling the line between pragmatic necessity and dangerous anti-pattern—and examined how it relates to metaprogramming and contrasts with classical design patterns. More importantly, we've confronted the significant security implications that arise when code can be altered at runtime, from supply chain attacks to the subversion of language-level security features.&lt;/p&gt;

&lt;p&gt;Understanding these risks and the theoretical context is crucial because it shapes how we approach the practical application of this technique. Armed with this knowledge, we can now move forward with appropriate caution and respect for the power we're wielding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In Part 2&lt;/strong&gt;, we will transition from theory to practice, diving deep into concrete Ruby and Rails implementation patterns. You'll learn how to use &lt;code&gt;Module#prepend&lt;/code&gt; to create clean, effective monkey patches that simulate real-world failures like API timeouts, database deadlocks, race conditions, and memory leaks—all within the controlled environment of the Rails console. We'll also explore real-world case studies from companies like DoorDash and Shopify, learning from both their successes and their cautionary tales.&lt;/p&gt;

&lt;p&gt;&lt;a href=""&gt;Continue to Part 2: Implementation &amp;amp; Practice → LATER&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Design Patterns and Theory
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gbracha.blogspot.com/2008/03/monkey-patching.html" rel="noopener noreferrer"&gt;Monkey Patching - Gilad Bracha's Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/5626193/what-is-monkey-patching" rel="noopener noreferrer"&gt;What is monkey patching? - Stack Overflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://softwareengineering.stackexchange.com/questions/144652/is-monkeypatching-considered-good-programming-practice" rel="noopener noreferrer"&gt;Is monkeypatching considered good programming practice? - Stack Exchange&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://python-patterns.guide/gang-of-four/decorator-pattern/" rel="noopener noreferrer"&gt;The Decorator Pattern - Python Patterns Guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Security
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.ruby-lang.org/en/security/" rel="noopener noreferrer"&gt;Ruby Security - Official Ruby Language Security Page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cvedetails.com/vulnerability-list/vendor_id-7252/product_id-12215/Ruby-lang-Ruby.html" rel="noopener noreferrer"&gt;Ruby-lang Ruby Vulnerabilities - CVE Details&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cvedetails.com/vulnerability-list/vendor_id-12043/product_id-22568/Rubyonrails-Ruby-On-Rails.html" rel="noopener noreferrer"&gt;Ruby on Rails Vulnerabilities - CVE Details&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mitre.org/sites/default/files/publications/supply-chain-attack-framework-14-0228.pdf" rel="noopener noreferrer"&gt;Supply Chain Attack Framework - MITRE&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://owasp.org/www-community/attacks/Code_Injection" rel="noopener noreferrer"&gt;Code Injection - OWASP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.exabeam.com/explainers/information-security/software-supply-chain-attacks-attack-vectors-examples-and-6-defensive-measures/" rel="noopener noreferrer"&gt;Software Supply Chain Attacks: Attack Vectors, Examples and Defensive Measures - Exabeam&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jscrambler.com/blog/an-analysis-of-code-poisoning-monkey-patching-javascript" rel="noopener noreferrer"&gt;An Analysis of Code Poisoning: Monkey Patching JavaScript - Jscrambler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.armosec.io/blog/software-supply-chain-security/" rel="noopener noreferrer"&gt;Software Supply Chain Security - Armo Security&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/software-supply-chain-security/docs/attack-vectors" rel="noopener noreferrer"&gt;Software Supply Chain Security: Attack Vectors - Google Cloud&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rubysec/ruby-advisory-db" rel="noopener noreferrer"&gt;Ruby Advisory Database - GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  General Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.browserstack.com/guide/monkey-patching" rel="noopener noreferrer"&gt;Monkey Patching Guide - BrowserStack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@thomascountz/safer-monkey-patching-in-ruby-1f53efc51f4f" rel="noopener noreferrer"&gt;Safer Monkey Patching in Ruby - Medium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@mojimich2015/monkey-patching-in-python-a-deep-dive-685b8e645603" rel="noopener noreferrer"&gt;Monkey Patching in Python: A Deep Dive - Medium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.reddit.com/r/ruby/comments/n0n6e/whats_the_deal_with_monkeypatching_and_why_is/" rel="noopener noreferrer"&gt;What's the deal with monkeypatching and why is it bad? - Reddit r/ruby&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.codinghorror.com/monkeypatching-for-humans/" rel="noopener noreferrer"&gt;Monkeypatching for Humans - Coding Horror Blog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>designpatterns</category>
      <category>architecture</category>
    </item>
    <item>
      <title>From Script to Library: Building a Firefox Tab Extractor for the Open Source Community</title>
      <dc:creator>Vinicius Porto</dc:creator>
      <pubDate>Mon, 25 Aug 2025 00:50:32 +0000</pubDate>
      <link>https://dev.to/viniciuspuerto/from-script-to-library-building-a-firefox-tab-extractor-for-the-open-source-community-5aa4</link>
      <guid>https://dev.to/viniciuspuerto/from-script-to-library-building-a-firefox-tab-extractor-for-the-open-source-community-5aa4</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;What started as a simple script to organize my browser tabs evolved into a full-fledged Python library with CI/CD, comprehensive testing, and PyPI publishing. This article chronicles the journey of transforming a personal productivity tool into an open-source library that others can benefit from.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Tab Management Chaos
&lt;/h2&gt;

&lt;p&gt;As a developer and researcher, I often find myself with dozens of Firefox tabs open - documentation, tutorials, research papers, and GitHub repositories. The challenge? Keeping track of what's important, what I've already read, and what needs attention.&lt;/p&gt;

&lt;p&gt;My initial solution was a Python script that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extracted Firefox session data from &lt;code&gt;recovery.jsonlz4&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Parsed tab information (title, URL, access time, pinned status)&lt;/li&gt;
&lt;li&gt;Exported to CSV for Notion integration&lt;/li&gt;
&lt;li&gt;Helped organize study materials and research&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But this was just a local script. What if others could benefit from this tool?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Transformation: From Script to Library
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Phase 1: Restructuring the Codebase
&lt;/h3&gt;

&lt;p&gt;The original script was a monolithic file with everything mixed together. The first step was applying software engineering principles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Before: Everything in one file
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract_firefox_tabs&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# 200+ lines of mixed concerns
&lt;/span&gt;
&lt;span class="c1"&gt;# After: Modular architecture
&lt;/span&gt;&lt;span class="n"&gt;firefox_tab_extractor&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;          &lt;span class="c1"&gt;# Data structures
&lt;/span&gt;&lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;extractor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;       &lt;span class="c1"&gt;# Core logic
&lt;/span&gt;&lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;      &lt;span class="c1"&gt;# Error handling
&lt;/span&gt;&lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;cli&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;            &lt;span class="c1"&gt;# Command-line interface
&lt;/span&gt;&lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="n"&gt;tests&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
    &lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="n"&gt;test_extractor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key Technical Decisions:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data Models&lt;/strong&gt;: Used &lt;code&gt;@dataclass&lt;/code&gt; for &lt;code&gt;Tab&lt;/code&gt; and &lt;code&gt;Window&lt;/code&gt; objects with type hints&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error Handling&lt;/strong&gt;: Custom exception hierarchy for specific failure scenarios&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separation of Concerns&lt;/strong&gt;: CLI, core logic, and data models in separate modules&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logging&lt;/strong&gt;: Standard Python logging for debugging and user feedback&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Phase 2: Modern Python Packaging
&lt;/h3&gt;

&lt;p&gt;Gone were the days of &lt;code&gt;setup.py&lt;/code&gt;. Modern Python packaging with &lt;code&gt;pyproject.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[project]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"firefox-tab-extractor"&lt;/span&gt;
&lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0.0"&lt;/span&gt;
&lt;span class="py"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Extract and organize Firefox browser tabs"&lt;/span&gt;
&lt;span class="py"&gt;authors&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="py"&gt;{name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Vinicius Porto"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;email&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"vinicius.alves.porto@gmail.com"&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="py"&gt;["lz4&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;3.1&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="s"&gt;"]&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;
&lt;span class="nn"&gt;[project.optional-dependencies]&lt;/span&gt;
&lt;span class="py"&gt;dev&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"pytest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"black"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"flake8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"mypy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"pre-commit"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nn"&gt;[project.scripts]&lt;/span&gt;
&lt;span class="py"&gt;firefox-tab-extractor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"firefox_tab_extractor.cli:main"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Benefits:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single source of truth for project metadata&lt;/li&gt;
&lt;li&gt;Modern dependency specification&lt;/li&gt;
&lt;li&gt;Entry points for CLI tools&lt;/li&gt;
&lt;li&gt;Tool configurations (Black, MyPy, Pytest)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Phase 3: Quality Assurance
&lt;/h3&gt;

&lt;p&gt;A library needs to be reliable. This meant implementing comprehensive testing and code quality tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Example test structure
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestFirefoxTabExtractor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nd"&gt;@patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;firefox_tab_extractor.extractor.os.path.exists&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_extractor_initialization&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mock_exists&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;mock_exists&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="n"&gt;extractor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FirefoxTabExtractor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;extractor&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Testing Strategy:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unit Tests&lt;/strong&gt;: Mock external dependencies (file system, Firefox profiles)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration Tests&lt;/strong&gt;: Test the complete workflow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error Scenarios&lt;/strong&gt;: Test exception handling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edge Cases&lt;/strong&gt;: Empty profiles, corrupted data, missing files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Code Quality Tools:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Black&lt;/strong&gt;: Consistent code formatting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flake8&lt;/strong&gt;: Linting and style enforcement&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MyPy&lt;/strong&gt;: Static type checking&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pre-commit&lt;/strong&gt;: Automated quality checks&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Phase 4: Continuous Integration/Deployment
&lt;/h3&gt;

&lt;p&gt;Automation is key for open source projects. GitHub Actions workflows handle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/publish.yml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Publish to PyPI&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;release&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;published&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.8"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.9"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.10"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.11"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.12"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="na"&gt;build-and-publish&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build package&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python -m build&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Publish to PyPI&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;TWINE_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.PYPI_API_TOKEN }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;twine upload dist/*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;CI/CD Benefits:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automated testing across Python versions&lt;/li&gt;
&lt;li&gt;Quality checks on every commit&lt;/li&gt;
&lt;li&gt;Automated PyPI publishing on releases&lt;/li&gt;
&lt;li&gt;Consistent deployment process&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Technical Challenges and Solutions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Challenge 1: Firefox Session Data Format
&lt;/h3&gt;

&lt;p&gt;Firefox stores session data in LZ4-compressed JSON files. The technical approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;lz4.frame&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decompress_session_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Decompress Firefox session data from LZ4 format.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;compressed_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Remove Firefox-specific header
&lt;/span&gt;    &lt;span class="n"&gt;json_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;compressed_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;

    &lt;span class="c1"&gt;# Decompress LZ4 data
&lt;/span&gt;    &lt;span class="n"&gt;decompressed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lz4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decompress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Parse JSON
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decompressed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Challenge 2: Cross-Platform Profile Detection
&lt;/h3&gt;

&lt;p&gt;Firefox profiles are stored differently across operating systems:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_firefox_profile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Find Firefox profile directory across different OS.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;darwin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# macOS
&lt;/span&gt;        &lt;span class="n"&gt;base_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expanduser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;~/Library/Application Support/Firefox/Profiles&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;win32&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# Windows
&lt;/span&gt;        &lt;span class="n"&gt;base_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expanduser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;~/AppData/Roaming/Mozilla/Firefox/Profiles&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# Linux
&lt;/span&gt;        &lt;span class="n"&gt;base_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expanduser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;~/.mozilla/firefox&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Find the default profile
&lt;/span&gt;    &lt;span class="n"&gt;profiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*.default*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;profiles&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;profiles&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Challenge 3: Data Model Design
&lt;/h3&gt;

&lt;p&gt;The challenge was creating intuitive data structures:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Tab&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;window_index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;tab_index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;last_accessed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;pinned&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;
    &lt;span class="n"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;

    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Extract domain from URL for categorization.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;urlparse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;netloc&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unknown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;last_accessed_datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Convert timestamp to datetime object.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromtimestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_accessed&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Challenge 4: Error Handling Strategy
&lt;/h3&gt;

&lt;p&gt;Robust error handling was crucial for a library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FirefoxTabExtractorError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Base exception for Firefox tab extractor.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FirefoxProfileNotFoundError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FirefoxTabExtractorError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Raised when Firefox profile cannot be found.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SessionDataError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FirefoxTabExtractorError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Raised when session data cannot be parsed.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LZ4DecompressionError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FirefoxTabExtractorError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Raised when LZ4 decompression fails.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Open Source Journey
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why Open Source Matters
&lt;/h3&gt;

&lt;p&gt;Open source libraries are the backbone of modern software development. They:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Accelerate Development&lt;/strong&gt;: Developers don't reinvent the wheel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improve Quality&lt;/strong&gt;: Community review and contributions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Foster Learning&lt;/strong&gt;: Code becomes documentation and examples&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build Ecosystems&lt;/strong&gt;: Tools that work together&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Documentation and Community
&lt;/h3&gt;

&lt;p&gt;A good open source project needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Clear README&lt;/strong&gt;: Installation, usage, examples&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API Documentation&lt;/strong&gt;: Function signatures, parameters, return values&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contributing Guidelines&lt;/strong&gt;: How others can help&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Issue Templates&lt;/strong&gt;: Structured bug reports and feature requests&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code of Conduct&lt;/strong&gt;: Welcoming environment&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Example: Our Documentation Structure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Firefox Tab Extractor&lt;/span&gt;

&lt;span class="gu"&gt;## Quick Start&lt;/span&gt;
pip install firefox-tab-extractor
firefox-tab-extractor --help

&lt;span class="gu"&gt;## Features&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; 🔍 Smart profile detection
&lt;span class="p"&gt;-&lt;/span&gt; 📁 Multiple output formats (JSON/CSV)
&lt;span class="p"&gt;-&lt;/span&gt; 🏷️ Rich metadata extraction
&lt;span class="p"&gt;-&lt;/span&gt; 📊 Statistics and analytics
&lt;span class="p"&gt;-&lt;/span&gt; 🛠️ Developer-friendly API

&lt;span class="gu"&gt;## Usage Examples&lt;/span&gt;
from firefox_tab_extractor import FirefoxTabExtractor

extractor = FirefoxTabExtractor()
tabs = extractor.extract_tabs()
stats = extractor.get_statistics(tabs)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Start Small, Scale Gradually
&lt;/h3&gt;

&lt;p&gt;The initial script was functional. The library evolved through iterations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First: Modular structure&lt;/li&gt;
&lt;li&gt;Second: Testing and quality tools&lt;/li&gt;
&lt;li&gt;Third: CI/CD and automation&lt;/li&gt;
&lt;li&gt;Fourth: Documentation and community&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Testing is Investment, Not Overhead
&lt;/h3&gt;

&lt;p&gt;Good tests pay dividends:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Confidence in changes&lt;/li&gt;
&lt;li&gt;Documentation of behavior&lt;/li&gt;
&lt;li&gt;Easier refactoring&lt;/li&gt;
&lt;li&gt;Community contributions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Automation Reduces Friction
&lt;/h3&gt;

&lt;p&gt;CI/CD workflows mean:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No manual deployment steps&lt;/li&gt;
&lt;li&gt;Consistent quality standards&lt;/li&gt;
&lt;li&gt;Faster feedback loops&lt;/li&gt;
&lt;li&gt;Reduced human error&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Documentation is Code
&lt;/h3&gt;

&lt;p&gt;Good documentation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduces support burden&lt;/li&gt;
&lt;li&gt;Attracts contributors&lt;/li&gt;
&lt;li&gt;Serves as specification&lt;/li&gt;
&lt;li&gt;Improves user experience&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;What started as a personal script became:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A Python library&lt;/strong&gt; with 1,000+ lines of code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comprehensive testing&lt;/strong&gt; with 90%+ coverage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated publishing&lt;/strong&gt; to PyPI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-platform support&lt;/strong&gt; (macOS, Windows, Linux)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple output formats&lt;/strong&gt; (JSON, CSV)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rich metadata extraction&lt;/strong&gt; (domains, timestamps, pinned status)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Command-line interface&lt;/strong&gt; for easy use&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developer-friendly API&lt;/strong&gt; for integration&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Impact and Usage
&lt;/h2&gt;

&lt;p&gt;The library enables workflows like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Study organization
&lt;/span&gt;&lt;span class="n"&gt;tabs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;extractor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extract_tabs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;study_tabs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tab&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tab&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tabs&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tutorial&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
&lt;span class="n"&gt;extractor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save_to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;study_tabs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;study_materials.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Productivity analysis
&lt;/span&gt;&lt;span class="n"&gt;stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;extractor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_statistics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Most visited domain: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;top_domains&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Total reading time: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;estimated_reading_time&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; hours&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Notion integration
&lt;/span&gt;&lt;span class="n"&gt;windows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;extractor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_windows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;window&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;windows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Window &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;window_index&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tab_count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; tabs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Building an open source library is more than just writing code. It's about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Engineering Excellence&lt;/strong&gt;: Clean architecture, testing, documentation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Community Building&lt;/strong&gt;: Welcoming contributors, clear guidelines&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automation&lt;/strong&gt;: CI/CD, quality tools, deployment pipelines&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User Experience&lt;/strong&gt;: Intuitive APIs, helpful error messages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The journey from script to library taught me that open source is about making tools that others can build upon. It's about contributing to the ecosystem that has given us so much.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The code is available at:&lt;/strong&gt; &lt;a href="https://github.com/ViniciusPuerto/firefox-tab-extractor" rel="noopener noreferrer"&gt;github.com/ViniciusPuerto/firefox-tab-extractor&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install with:&lt;/strong&gt; &lt;code&gt;pip install firefox-tab-extractor&lt;/code&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What started as a personal productivity tool became a contribution to the open source community. The next time you find yourself writing a script that others might find useful, consider taking that extra step to make it a proper library. The community will thank you for it.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Hanoi Tower with procs</title>
      <dc:creator>Vinicius Porto</dc:creator>
      <pubDate>Thu, 21 Nov 2024 13:43:52 +0000</pubDate>
      <link>https://dev.to/viniciuspuerto/hanoi-tower-with-procs-43b8</link>
      <guid>https://dev.to/viniciuspuerto/hanoi-tower-with-procs-43b8</guid>
      <description>&lt;p&gt;The Tower of Hanoi is a classic algorithmic problem that can be solved using recursive techniques. We can apply the concepts of procs and lambdas to implement a solution for the Tower of Hanoi problem in Ruby. Here's an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tower_of_hanoi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auxiliary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;move_callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;move_callback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;tower_of_hanoi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auxiliary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;move_callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;move_callback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tower_of_hanoi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auxiliary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;move_callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;move_proc = lambda { |source, destination| puts "Move disk from #{source} to #{destination}" }&lt;/code&gt; &lt;br&gt;&lt;br&gt;
&lt;code&gt;tower_of_hanoi(3, 'A', 'C', 'B', move_proc)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In this implementation, the &lt;code&gt;tower_of_hanoi&lt;/code&gt; method takes the number of disks (n), names of the source (source), destination (destination), and auxiliary (auxiliary) towers, as well as a move_callback proc as arguments.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;tower_of_hanoi&lt;/code&gt; method follows the recursive algorithm for solving the Tower of Hanoi problem. If n is 1, it simply calls the move callback to move the disk from the source to the destination tower. Otherwise, it recursively solves the problem for n-1 disks by moving them from the source tower to the auxiliary tower, then moves the largest disk from the source to the destination, and finally solves the problem for &lt;code&gt;n-1&lt;/code&gt; disks by moving them from the auxiliary tower to the destination tower.&lt;/p&gt;

&lt;p&gt;We define a move_proc lambda that prints the move operations. You can pass any other proc or lambda with a different behavior to customize how the moves are handled.&lt;/p&gt;

&lt;p&gt;Finally, we call the tower_of_hanoi method with &lt;code&gt;n = 3&lt;/code&gt;, source tower 'A', destination tower 'C', auxiliary tower 'B', and the move_proc lambda as the move callback.&lt;/p&gt;

&lt;p&gt;When you run this code, it will print the sequence of moves required to solve the Tower of Hanoi problem with 3 disks. You can adjust the value of n and the tower names to solve the problem for different numbers of disks or with different tower names.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>algorithms</category>
    </item>
    <item>
      <title>Using Value Objects in Ruby on Rails</title>
      <dc:creator>Vinicius Porto</dc:creator>
      <pubDate>Sat, 01 Apr 2023 04:10:00 +0000</pubDate>
      <link>https://dev.to/viniciuspuerto/using-value-objects-in-ruby-on-rails-3d2</link>
      <guid>https://dev.to/viniciuspuerto/using-value-objects-in-ruby-on-rails-3d2</guid>
      <description>&lt;p&gt;In this article, we explore the concept of Value Objects in Ruby on Rails and how they can be used to encapsulate business logic and improve the maintainability of your code. We start by explaining what a Value Object is and how it differs from a Rails Model, before diving into how to create custom Value Objects in Ruby. We also provide an example of how to use a custom Value Object as an attribute in a Rails Model, along with the necessary configuration and implementation details. Finally, we discuss how to organize Value Objects within a Rails application for maximum clarity and ease of use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can a Rails Model be Considered a Value Object?
&lt;/h3&gt;

&lt;p&gt;No, a Rails Model is not the same thing as a Value Object. A Value Object is a small, immutable object that represents a simple value or concept, such as a name, address, or monetary amount. In contrast, a Rails Model is a more complex object that represents a database table and includes functionality for querying and updating the associated records.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can We Use Value Object Concepts in a Rails Model?
&lt;/h3&gt;

&lt;p&gt;Yes, you can use Value Object concepts in a Rails Model by creating custom Value Objects and using them as attributes in your Model. This allows you to encapsulate business logic in a separate object and keep your Model lean and focused on database-related concerns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should We Create Separate Models or Use Value Objects?
&lt;/h3&gt;

&lt;p&gt;It depends on the complexity of the business logic you need to represent. If the business logic involves complex relationships or requires extensive querying and updating of associated records, it may be better to create separate Models. If the business logic is relatively simple and can be represented as a standalone value or concept, it may be better to use a Value Object.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a Value Object
&lt;/h3&gt;

&lt;p&gt;Here is an example of how you might create a Value Object for representing monetary amounts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Money&lt;/span&gt;
  &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:currency&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currency&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'USD'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;
    &lt;span class="vi"&gt;@currency&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;currency&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"Mismatched currency"&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;currency&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;currency&lt;/span&gt;

    &lt;span class="no"&gt;Money&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Other methods for performing arithmetic operations, formatting output, etc.&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, we define a &lt;code&gt;Money&lt;/code&gt; class that represents a monetary amount with an associated currency. The class includes methods for performing arithmetic operations and other functionality related to monetary amounts.&lt;/p&gt;

&lt;p&gt;Another example is a Value Object to represents an address:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Address&lt;/span&gt;
    &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:street&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:complement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:zip_code&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;street&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;complement&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;zip_code&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
      &lt;span class="vi"&gt;@street&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;street&lt;/span&gt;
      &lt;span class="vi"&gt;@number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;
      &lt;span class="vi"&gt;@complement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;complement&lt;/span&gt;
      &lt;span class="vi"&gt;@city&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;
      &lt;span class="vi"&gt;@state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;
      &lt;span class="vi"&gt;@zip_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;zip_code&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;eql?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;street&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;street&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class="n"&gt;city&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;city&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;state&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class="n"&gt;zip_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zip_code&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="kp"&gt;alias_method&lt;/span&gt; &lt;span class="ss"&gt;:==&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:eql?&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hash&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;street&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;complement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;zip_code&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;hash&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;to_s&lt;/span&gt;
      &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;street&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;complement&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; - &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;zip_code&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using a Value Object in a Rails Model
&lt;/h3&gt;

&lt;p&gt;Here is an example of how you might use the &lt;code&gt;Address&lt;/code&gt; Value Object as an attribute in a Rails Model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
    &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="ss"&gt;:shipping_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;AddressType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;  
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;using this method you need to make some aditional configurations on &lt;code&gt;config/initializers/types.rb&lt;/code&gt; to register the new custom type like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:active_record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt; &lt;span class="ss"&gt;:address_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;AddressType&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you still have to create a class, that inherits from &lt;code&gt;ActiveRecord::Type::Value&lt;/code&gt; , that describes the convertion of the  &lt;code&gt;Address&lt;/code&gt; value object into a really usable type.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AddressType&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Value&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;type&lt;/span&gt;
    &lt;span class="ss"&gt;:jsonb&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blank?&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;value&lt;/span&gt;
    &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="no"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;symbolize_keys&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;ArgumentError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Invalid address value: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inspect&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blank?&lt;/span&gt;

    &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;deserialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;==&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;AddressType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;In the &lt;code&gt;type&lt;/code&gt; method we are defining the real type that ‘ll be saved on the database&lt;/li&gt;
&lt;li&gt;In the &lt;code&gt;cast&lt;/code&gt; method has the responsability to  casts a value from user input (e.g. from a setter). This value may be a string from the form builder, or a ruby object passed to a setter. There is currently no way to differentiate between which source it came from. The return value of this method will be returned from &lt;code&gt;[ActiveRecord::AttributeMethods::Read#read_attribute](https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Read.html#method-i-read_attribute)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;serialize&lt;/code&gt; method casts a value from the ruby type to a type that the database knows how to understand. The returned value from this method should be a &lt;code&gt;String&lt;/code&gt;, &lt;code&gt;Numeric&lt;/code&gt;, &lt;code&gt;Date&lt;/code&gt;, &lt;code&gt;Time&lt;/code&gt;, &lt;code&gt;Symbol&lt;/code&gt;, &lt;code&gt;true&lt;/code&gt;, &lt;code&gt;false&lt;/code&gt;, or &lt;code&gt;nil&lt;/code&gt; .&lt;/li&gt;
&lt;li&gt;and the &lt;code&gt;deserialize&lt;/code&gt; method converts a value from database input to the appropriate ruby type. The return value of this method will be returned from &lt;code&gt;ActiveRecord::AttributeMethods::Read#read_attribute&lt;/code&gt;. The default implementation just calls &lt;code&gt;[Value#cast](https://api.rubyonrails.org/classes/ActiveModel/Type/Value.html#method-i-cast)&lt;/code&gt; .&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Organizing Value Objects in a Rails Application
&lt;/h3&gt;

&lt;p&gt;To organize your Value Objects in a Rails application, you can create a separate directory for them within the app directory. Here is an example directory structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/
  ├── controllers/
  ├── models/
  │   ├── order.rb
  ├── value_objects/
  │   └── money.rb
      └── address.rb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, we create a value_objects directory within the app directory to store our custom Value Objects. We then create a &lt;code&gt;money.rb&lt;/code&gt; file within the value_objects directory to define our Money Value Object.&lt;/p&gt;

&lt;h3&gt;
  
  
  We can also use &lt;code&gt;composed_of&lt;/code&gt; to use the value object
&lt;/h3&gt;

&lt;p&gt;You can use the composed_of method in Rails to simplify the use of the Money value object in the &lt;code&gt;Order&lt;/code&gt; model.&lt;/p&gt;

&lt;p&gt;Here’s an example of how you could use composed_of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;composed_of&lt;/span&gt; &lt;span class="ss"&gt;:total_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s1"&gt;'Money'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="ss"&gt;mapping: &lt;/span&gt;&lt;span class="sx"&gt;%w[total_value amount currency]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="ss"&gt;converter: &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;Money&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
              &lt;span class="ss"&gt;allow_nil: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, the &lt;code&gt;composed_of&lt;/code&gt; method is used to define a &lt;code&gt;total_value&lt;/code&gt; attribute in the &lt;code&gt;Order&lt;/code&gt; model. The &lt;code&gt;:class_name&lt;/code&gt; option specifies the name of the value object class (in this case, Money). The &lt;code&gt;:mapping&lt;/code&gt; option maps the &lt;code&gt;total_value&lt;/code&gt; attribute to the cents attribute of the Money object. The &lt;code&gt;:converter&lt;/code&gt; option specifies a lambda that converts the &lt;code&gt;total_value&lt;/code&gt; attribute to a Money object. The &lt;code&gt;:allow_nil&lt;/code&gt; option specifies that the &lt;code&gt;total_value&lt;/code&gt; attribute can be set to nil.&lt;/p&gt;

&lt;p&gt;With this setup, you can treat the &lt;code&gt;total_value&lt;/code&gt; attribute as a Money object in your code, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;total_value: &lt;/span&gt;&lt;span class="no"&gt;Money&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'usd'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;total_value&lt;/span&gt; &lt;span class="c1"&gt;# returns a Money object with usd100&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach allows you to abstract away the details of how the &lt;code&gt;Money&lt;/code&gt; object is stored in the database and provides a simpler interface for working with the &lt;code&gt;total_value&lt;/code&gt; attribute.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;In conclusion, using value objects in a Ruby on Rails application can help you encapsulate business logic in a separate object and keep your models focused on database-related concerns. By using value objects to represent simple values or concepts, such as monetary amounts or addresses, you can make your code more readable, testable, and maintainable.&lt;/p&gt;

&lt;p&gt;Creating a value object is relatively easy in Ruby, and you can use it as an attribute in your Rails models. You can also create custom types to handle the conversion of your value object to a database column. By organizing your value objects in a separate directory, you can make it easier to maintain and reuse them across your application.&lt;/p&gt;

&lt;p&gt;references:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ActiveRecord::Aggregations::ClassMethods&lt;/code&gt;. Ruby on Rails API Documentation. Retrieved March 31, 2023, from &lt;a href="https://api.rubyonrails.org/classes/ActiveRecord/Aggregations/ClassMethods.html"&gt;https://api.rubyonrails.org/classes/ActiveRecord/Aggregations/ClassMethods.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ActiveModel::Type::Value&lt;/code&gt;. Ruby on Rails API Documentation. Retrieved March 31, 2023, from &lt;a href="https://api.rubyonrails.org/classes/ActiveModel/Type/Value.html#method-i-serialize"&gt;https://api.rubyonrails.org/classes/ActiveModel/Type/Value.html#method-i-serialize&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Fowler, M. (2003, August 20). ValueObject. Martin Fowler. Retrieved March 31, 2023, from &lt;a href="https://martinfowler.com/bliki/ValueObject.html"&gt;https://martinfowler.com/bliki/ValueObject.html&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>designpatterns</category>
      <category>rails</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
