<?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: Yousef</title>
    <description>The latest articles on DEV Community by Yousef (@codalio).</description>
    <link>https://dev.to/codalio</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%2F3538012%2Fcdc5a3d2-a0b9-4b97-b4da-d0155dee9b16.jpg</url>
      <title>DEV Community: Yousef</title>
      <link>https://dev.to/codalio</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/codalio"/>
    <language>en</language>
    <item>
      <title>The "Vibe-Coding" Epidemic</title>
      <dc:creator>Yousef</dc:creator>
      <pubDate>Thu, 13 Nov 2025 15:19:35 +0000</pubDate>
      <link>https://dev.to/codalio/the-vibe-coding-epidemic-c86</link>
      <guid>https://dev.to/codalio/the-vibe-coding-epidemic-c86</guid>
      <description>&lt;h2&gt;
  
  
  The Rise of "Vibe-Coding" Platforms
&lt;/h2&gt;

&lt;p&gt;The AI development landscape has seen an explosion of platforms promising to turn ideas into code instantly. Platforms like &lt;strong&gt;Lovable&lt;/strong&gt;, &lt;strong&gt;Bolt&lt;/strong&gt;, and &lt;strong&gt;V0&lt;/strong&gt; have popularized what's become known as "vibe-coding". An approach where you describe what you want in natural language and get code generated immediately, often skipping traditional planning, documentation, and structured development processes.&lt;/p&gt;

&lt;p&gt;While these platforms can be impressive for quick prototypes and demos, they represent a fundamental misunderstanding of how software should be built. The "vibe" approach leads to significant problems that become apparent as projects grow beyond simple demos.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Happens When You Skip Planning
&lt;/h2&gt;

&lt;p&gt;Code is generated based on immediate prompts without understanding the full context of your product. There's no systematic gathering of requirements, no consideration of edge cases, and no comprehensive planning.&lt;/p&gt;

&lt;p&gt;Features are built in isolation without understanding how they fit into the larger system. Critical requirements are missed because they weren't explicitly mentioned in the prompt. There's no consideration of user flows, business logic, or technical constraints. Projects become a collection of disconnected features rather than a cohesive product.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture Problem
&lt;/h2&gt;

&lt;p&gt;When code is generated reactively, responding to individual prompts without considering overall system architecture, each feature is built independently. This leads to inconsistent patterns, duplicated logic, and architectural debt.&lt;/p&gt;

&lt;p&gt;As you add more features, inconsistencies emerge. Code patterns vary across the application. Functionality gets duplicated. Separation of concerns breaks down. Without systematic architecture, technical debt accumulates rapidly. Every new feature compounds the problem. Refactoring becomes necessary but risky, because there's no clear understanding of how the system should be structured.&lt;/p&gt;

&lt;h2&gt;
  
  
  Missing Business Context
&lt;/h2&gt;

&lt;p&gt;The focus is on "what" (the feature) without understanding "why" (the business need) or "who" (the target user). There's no consideration of business objectives, user personas, market positioning, or success metrics.&lt;/p&gt;

&lt;p&gt;This leads to features that don't align with business goals. You might build something that looks impressive but doesn't solve real problems. Without understanding your target users and their needs, you're building in the dark. There's no clear success criteria or metrics to measure whether what you've built is actually working.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Iteration Challenge
&lt;/h2&gt;

&lt;p&gt;These platforms do provide versioning, but the way they handle it creates significant problems. Platforms like V0 (as they currently stand at the time of writing this blog) create a new version of your app, design, or code with every single prompt. This linear versioning approach means you're constantly generating new versions as you iterate.&lt;/p&gt;

&lt;p&gt;You end up with a long chain of versions, each representing a different attempt or fix. You ask for a change, the AI makes a mistake, you prompt again to fix it, and another version is created. Before you know it, you have dozens of versions and no clear sense of which one actually worked or got you what you wanted.&lt;/p&gt;

&lt;p&gt;This linear versioning makes it difficult to compare different approaches. You can't easily explore alternative implementations side-by-side. You can't branch off from a working version to try something different. Instead, you're stuck in a linear progression where each version builds on the previous one, even if the previous one was a mistake.&lt;/p&gt;

&lt;p&gt;You spend more time navigating through versions and trying to remember which one had the feature you wanted than actually building new functionality. You get lost in the version history, unsure which version represents your actual product vision.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Frustration of Random Changes
&lt;/h2&gt;

&lt;p&gt;One of the most frustrating aspects of working with these platforms is the tendency for the AI to make random, unsolicited changes to your design. You ask for a small adjustment to a button, and suddenly the entire layout has been rearranged. You request a color change, and the AI decides to restructure your component hierarchy.&lt;/p&gt;

&lt;p&gt;This happens because the AI is only trying to "solve" your immediate request without any consideration for the project as a whole. It doesn't understand the broader context of your application, the design system you've established, or the architectural decisions you've made. Each prompt is treated in isolation, so the AI makes assumptions and "improvements" that you never asked for.&lt;/p&gt;

&lt;p&gt;This is partially a limitation of how these AI models are trained. They're optimized to generate code that works, but they lack the context and constraints that come from understanding a complete project. Without a comprehensive PRD, design system, or architectural documentation, the AI has no reference point for what should remain unchanged.&lt;/p&gt;

&lt;p&gt;The result is an iterative process that feels like two steps forward, one step back. You make progress on one feature, but unrelated parts of your application get modified. You spend time reverting unwanted changes instead of building new features.&lt;/p&gt;

&lt;p&gt;This is where a structured methodology bridges the gap. By establishing comprehensive documentation—PRDs, design systems, architectural specifications—the AI has the context it needs to make changes that respect the project as a whole. Changes are made within the constraints of the established system, not as isolated solutions to individual prompts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quality Assurance Gaps
&lt;/h2&gt;

&lt;p&gt;Code is generated without systematic quality checks. There's no review process, no consideration of best practices, and no validation that the generated code meets production standards.&lt;/p&gt;

&lt;p&gt;Security vulnerabilities go unnoticed. Performance issues aren't considered. Code doesn't follow best practices. There's no systematic testing approach. Production-ready code is assumed but not verified.&lt;/p&gt;

&lt;p&gt;As projects grow, these quality gaps become critical. Security issues can expose user data. Performance problems can make applications unusable. Code that doesn't follow best practices becomes unmaintainable.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Projects Hit the Wall
&lt;/h2&gt;

&lt;p&gt;The approach works well for simple, isolated features but breaks down as projects grow. Without systematic planning and architecture, adding features becomes increasingly difficult, and the codebase becomes unmaintainable.&lt;/p&gt;

&lt;p&gt;The pattern is familiar: quick start, rapid initial progress, then a gradual slowdown as complexity increases. Eventually, the codebase becomes so tangled that progress grinds to a halt. Teams spend more time fighting the codebase than building features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Knowledge Loss
&lt;/h2&gt;

&lt;p&gt;Code is generated but documentation explaining why decisions were made, what the architecture is, or how the system works isn't created. Knowledge exists only in the code (if at all), making it difficult for teams to understand or maintain.&lt;/p&gt;

&lt;p&gt;There's no record of why features were built. It's difficult for new team members to understand the system. There's no documentation of architecture or design decisions. Knowledge is lost when original developers move on. Maintenance becomes guesswork.&lt;/p&gt;

&lt;p&gt;This creates a knowledge debt that compounds over time. As team members change, understanding of the system is lost. New developers struggle to understand why things were built the way they were. Changes become risky because the impact is unclear.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Cost
&lt;/h2&gt;

&lt;p&gt;The problems become apparent as projects grow. In the short term, quick demos and prototypes work fine. But as you add features, inconsistencies emerge. Architecture becomes messy. Technical debt accumulates. Projects become unmaintainable. Adding features becomes difficult. Refactoring becomes necessary but risky.&lt;/p&gt;

&lt;p&gt;The hidden costs are significant: time spent fixing issues that proper planning would have prevented, technical debt that requires expensive refactoring, features that don't align with business goals, lost productivity from inconsistent architecture, and difficulty scaling or maintaining the codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Methodology Matters
&lt;/h2&gt;

&lt;p&gt;Software development isn't just about generating code—it's about building systems that work, scale, and maintain. This requires systematic planning, proper architecture, business context, and quality assurance.&lt;/p&gt;

&lt;p&gt;A structured methodology ensures that every aspect of your product is considered before code is generated. Data models and system architecture are designed before implementation. Features are grounded in business objectives and user needs. Multiple review processes validate output for completeness and quality.&lt;/p&gt;

&lt;p&gt;Versioning systems enable exploration of different directions while preserving work. Projects are designed to grow from MVP to enterprise-scale without hitting architectural walls. Comprehensive documentation serves as living records of your product.&lt;/p&gt;

&lt;p&gt;The right approach combines the speed of AI automation with the rigor of a proven methodology. It doesn't skip steps or take shortcuts. Instead, it automates the entire process from requirements gathering through deployment, while ensuring every aspect is properly considered.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Codalio Addresses These Problems
&lt;/h2&gt;

&lt;p&gt;&lt;a href="//app.codalio.com"&gt;Codalio&lt;/a&gt; was built to solve exactly these problems. Instead of generating code from isolated prompts, Codalio follows a structured, four-phase methodology that ensures comprehensive planning before any code is written.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Comprehensive Planning First&lt;/strong&gt;: Codalio begins with conversational requirements gathering through specialized AI agents. The Interviewer agent asks targeted questions to understand your vision, target audience, and business objectives. This ensures all aspects of your product are considered before code generation begins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Systematic Architecture&lt;/strong&gt;: The Architect agent designs comprehensive data models and system architecture before any code is generated. The Rhino Framework ensures consistent patterns, proper separation of concerns, and scalable architecture from the start. This prevents the architectural debt that accumulates with reactive code generation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Business Context Built-In&lt;/strong&gt;: The Project Manager and Product Manager agents create comprehensive PRDs that include business objectives, target audience analysis, user personas, success metrics, and competitive positioning. Every feature is grounded in business context, not just technical capability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Non-Linear Versioning&lt;/strong&gt;: Codalio's versioning system allows you to create multiple PRD versions, compare different approaches, revert to previous concepts, and merge ideas. You can explore alternatives without getting lost in a linear chain of versions. This enables systematic iteration rather than reactive prompting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context-Aware Changes&lt;/strong&gt;: By establishing comprehensive documentation, design systems, and architectural specifications, Codalio's AI agents have the context they need to make changes that respect the project as a whole. Changes are made within the constraints of the established system, preventing the random, unsolicited modifications that plague vibe-coding platforms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Living Documentation&lt;/strong&gt;: Comprehensive PRDs serve as living documentation of your product. Every aspect is documented from business strategy to technical architecture to user flows. This documentation evolves with your product and provides a complete record of decisions and rationale, preventing knowledge loss.&lt;/p&gt;

&lt;p&gt;The Codalio Method doesn't just generate code. It builds software the right way. Combining the speed of AI automation with the rigor of a proven methodology. It addresses every problem that vibe-coding creates, ensuring that your project can scale from MVP to enterprise without hitting the walls that derail projects built on ad-hoc prompting.&lt;/p&gt;

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

&lt;p&gt;Vibe-coding platforms promise magic, instant code from simple prompts. But software development isn't magic. It requires systematic planning, proper architecture, business context, and quality assurance.&lt;/p&gt;

&lt;p&gt;The problems aren't immediately apparent. They emerge as projects grow. What starts as a quick prototype becomes increasingly difficult to maintain. Features don't align with business goals. Architecture becomes inconsistent. Technical debt accumulates.&lt;/p&gt;

&lt;p&gt;The solution isn't to avoid AI-powered development. It's to use it within a structured methodology that ensures quality, scalability, and maintainability. By combining AI automation with proven development practices, you can build software that's both fast to develop and built to last.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>vibecoding</category>
      <category>automation</category>
      <category>webdev</category>
    </item>
    <item>
      <title>From Vibe Coding to Informed Development: How Codalio PRD Transforms Your Cursor Workflow</title>
      <dc:creator>Yousef</dc:creator>
      <pubDate>Tue, 04 Nov 2025 23:00:18 +0000</pubDate>
      <link>https://dev.to/codalio/from-vibe-coding-to-informed-development-how-codalio-prd-transforms-your-cursor-workflow-2cd2</link>
      <guid>https://dev.to/codalio/from-vibe-coding-to-informed-development-how-codalio-prd-transforms-your-cursor-workflow-2cd2</guid>
      <description>&lt;p&gt;There's a growing trend in the developer community that we might call "vibe coding". Jumping into implementation with AI coding assistants like Cursor, typing out prompts, and hoping the generated code is close enough to what you need. It feels productive. It's fast. And it often works... until it doesn't.&lt;/p&gt;

&lt;p&gt;The problem emerges in that last 20%: when you need to understand the "why" behind a feature, when architectural decisions matter, when business logic intersects with technical implementation. That's when AI coding tools hit a wall. They're brilliant at generating code, but they lack the product context that makes code truly useful.&lt;/p&gt;

&lt;p&gt;This challenge has become so common that development agencies are seeing a new category of work: "rescue" projects—codebases that were built with AI assistance but need expert intervention to align with actual product requirements. The code works, but it doesn't solve the right problem.&lt;/p&gt;

&lt;p&gt;The solution isn't to abandon AI coding tools. They're incredibly powerful. The solution is to give them the structured context they need to generate code that aligns with your product vision. That's where &lt;a href="https://codalio.com/" rel="noopener noreferrer"&gt;Codalio&lt;/a&gt; PRD comes in.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cursor Challenge: Why "Vibe Coding" Fails
&lt;/h2&gt;

&lt;p&gt;When you ask Cursor to "create a REST API endpoint for product reviews," it generates functional code. But functional isn't the same as complete. Here's what gets missed:&lt;/p&gt;

&lt;h3&gt;
  
  
  Lack of Product Context
&lt;/h3&gt;

&lt;p&gt;AI doesn't know the "why" behind features. Without understanding user personas, security requirements, or business rules, Cursor makes assumptions. For example, building a user authentication endpoint without understanding:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who your users are (verified purchasers vs. anonymous visitors)&lt;/li&gt;
&lt;li&gt;What security requirements apply (GDPR compliance, data retention policies)&lt;/li&gt;
&lt;li&gt;What business rules exist (only purchasers can review products they've bought)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The generated code might work, but it won't match your product's actual needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Architectural Blind Spots
&lt;/h3&gt;

&lt;p&gt;AI generates code without understanding your system architecture. It doesn't know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your existing data model structure&lt;/li&gt;
&lt;li&gt;Design principles that guide your implementation&lt;/li&gt;
&lt;li&gt;Integration points with other systems&lt;/li&gt;
&lt;li&gt;Scalability requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This leads to endpoints that don't fit your data model, violate design principles, or create architectural inconsistencies that require refactoring later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Incomplete Requirements
&lt;/h3&gt;

&lt;p&gt;When requirements are vague, AI fills in the gaps with assumptions. Missing validation rules, edge cases, and integration points become technical debt. The code works for the happy path, but fails when real users interact with it in unexpected ways.&lt;/p&gt;

&lt;h3&gt;
  
  
  The "Rescue" Problem
&lt;/h3&gt;

&lt;p&gt;This is where the "rescue" cycle begins. Agencies receive projects where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code was generated quickly but doesn't match product requirements&lt;/li&gt;
&lt;li&gt;Features are implemented but miss critical business logic&lt;/li&gt;
&lt;li&gt;Validation rules are incomplete or incorrect&lt;/li&gt;
&lt;li&gt;The architecture doesn't align with the product vision&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The code works, but it needs expert intervention to become production-ready. This is expensive, time-consuming, and frustrating for everyone involved.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Codalio PRD Solves This
&lt;/h2&gt;

&lt;p&gt;Codalio PRD provides the structured product context that transforms AI coding from "vibe coding" to "informed development." Here's how:&lt;/p&gt;

&lt;h3&gt;
  
  
  Structured Product Context
&lt;/h3&gt;

&lt;p&gt;Codalio's PRD builder uses specialized AI agents (Project Manager, Designer, Architect, Product Manager) that ask targeted questions to populate PRD sections. As you chat with these agents in the canvas, they generate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Elevator Pitch&lt;/strong&gt;: The core value proposition&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Problem Statement&lt;/strong&gt;: What problem you're solving&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solution&lt;/strong&gt;: How your product addresses it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vision&lt;/strong&gt;: Where you're heading&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These agents ensure completeness by scoring each section from their unique perspectives. This collaborative planning process ensures code aligns with product goals, not just technical requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  User-Centric Design
&lt;/h3&gt;

&lt;p&gt;User personas, journeys, and flows inform implementation. Understanding user needs prevents over-engineering or missing critical features. When Cursor generates code, it can reference:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who the user is (persona details)&lt;/li&gt;
&lt;li&gt;What they're trying to accomplish (user journeys)&lt;/li&gt;
&lt;li&gt;How they'll interact with the feature (user flows)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This prevents creating features users don't need or missing features they do.&lt;/p&gt;

&lt;h3&gt;
  
  
  Technical Specification
&lt;/h3&gt;

&lt;p&gt;ERD diagrams, schema definitions, and sample data provide technical context. This prevents architectural mismatches. When Cursor generates endpoints, it knows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The exact data model structure&lt;/li&gt;
&lt;li&gt;Relationships between entities&lt;/li&gt;
&lt;li&gt;Required fields and constraints&lt;/li&gt;
&lt;li&gt;Sample data patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Code aligns with your architecture from the start.&lt;/p&gt;

&lt;h3&gt;
  
  
  Design Principles
&lt;/h3&gt;

&lt;p&gt;Navigation design, sitemap, and design principles guide implementation. This ensures consistency across features. When generating code, Cursor can reference:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Design patterns used throughout the app&lt;/li&gt;
&lt;li&gt;Navigation structure&lt;/li&gt;
&lt;li&gt;UI component conventions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Features feel cohesive, not bolted-on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Versioned Planning
&lt;/h3&gt;

&lt;p&gt;PRD versions allow iterative refinement before coding. This prevents the "code now, fix later" anti-pattern.&lt;/p&gt;

&lt;p&gt;You can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Refine requirements in the PRD&lt;/li&gt;
&lt;li&gt;Get stakeholder feedback&lt;/li&gt;
&lt;li&gt;Score completeness (Project Manager, Designer, Architect, Product Manager perspectives)&lt;/li&gt;
&lt;li&gt;Ensure readiness before generating code&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Real Example: Building a REST API Endpoint
&lt;/h2&gt;

&lt;p&gt;Let's see how this works in practice with a concrete example: implementing a "Create Product Review" endpoint.&lt;/p&gt;

&lt;h3&gt;
  
  
  Without PRD (Cursor alone)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Developer asks:&lt;/strong&gt; "Create a REST API endpoint for product reviews"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cursor generates:&lt;/strong&gt;&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;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/api/reviews&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;POST&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;create_review&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;
    &lt;span class="n"&gt;review&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Review&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;product_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;user_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;rating&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rating&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;comment&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&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;review&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&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;jsonify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;review&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_dict&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt; &lt;span class="mi"&gt;201&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What's missing:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validation: Can any user review any product? Only purchasers?&lt;/li&gt;
&lt;li&gt;Business rules: What's the minimum rating? Can users review products multiple times?&lt;/li&gt;
&lt;li&gt;Data relationships: Is there a Purchase model? How do we verify purchase?&lt;/li&gt;
&lt;li&gt;Error handling: What happens if the product doesn't exist? If the user already reviewed?&lt;/li&gt;
&lt;li&gt;Edge cases: What if the product is discontinued? What if the user is banned?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Code that works but doesn't match product requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  With Codalio PRD (Prevention)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;PRD provides:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;User personas:&lt;/strong&gt; "Verified Purchasers" who can leave reviews&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User flows:&lt;/strong&gt; Review submission flow with validation steps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schema:&lt;/strong&gt; Review model with relationships (Product, User, Rating)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Business rules:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Reviews must be from verified purchasers&lt;/li&gt;
&lt;li&gt;Ratings 1-5, required&lt;/li&gt;
&lt;li&gt;Comments minimum 20 characters&lt;/li&gt;
&lt;li&gt;One review per product per user&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User stories:&lt;/strong&gt; "As a verified purchaser, I want to leave a review so that other buyers can make informed decisions"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Cursor uses PRD context to generate:&lt;/strong&gt;&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;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/api/reviews&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nd"&gt;@require_auth&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_review&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;

    &lt;span class="c1"&gt;# Business rule: Only verified purchasers can review
&lt;/span&gt;    &lt;span class="n"&gt;purchase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Purchase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;product_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;completed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;purchase&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;jsonify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Only verified purchasers can review products&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt;

    &lt;span class="c1"&gt;# Business rule: One review per product per user
&lt;/span&gt;    &lt;span class="n"&gt;existing_review&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Review&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;product_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&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;existing_review&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;jsonify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;You have already reviewed this product&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="mi"&gt;409&lt;/span&gt;

    &lt;span class="c1"&gt;# Validation: Rating 1-5, required
&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;rating&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rating&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;5&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;jsonify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Rating must be between 1 and 5&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;

    &lt;span class="c1"&gt;# Validation: Comment minimum 20 characters
&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;comment&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;data&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;comment&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;20&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;jsonify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Comment must be at least 20 characters&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;

    &lt;span class="c1"&gt;# Create review
&lt;/span&gt;    &lt;span class="n"&gt;review&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Review&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;product_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;rating&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rating&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;comment&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&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;review&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Update product average rating
&lt;/span&gt;    &lt;span class="nf"&gt;update_product_rating&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;product_id&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="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;review&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_dict&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt; &lt;span class="mi"&gt;201&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Code that aligns with product requirements, includes proper validation, respects business rules, and handles edge cases.&lt;/p&gt;

&lt;h3&gt;
  
  
  With Codalio PRD (Rescue)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; Existing code has issues (missing validation, wrong permissions)&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;Load PRD into Cursor context&lt;/li&gt;
&lt;li&gt;PRD reveals: missing purchaser verification, incorrect rating validation&lt;/li&gt;
&lt;li&gt;Refactor code using PRD as source of truth&lt;/li&gt;
&lt;li&gt;Result: Code aligns with product requirements&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The PRD acts as a single source of truth, making it clear what needs to be fixed and why.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices: Using PRD with Cursor
&lt;/h2&gt;

&lt;p&gt;Here's how to get the most out of this workflow:&lt;/p&gt;

&lt;h3&gt;
  
  
  Start with PRD
&lt;/h3&gt;

&lt;p&gt;Always generate or refine your PRD before coding. In Codalio, you interact with specialized AI agents in the canvas—each with their own perspective. The Project Manager asks about business goals and success metrics. The Designer explores visual style and user experience. The Architect probes technical constraints and data models. The Product Manager focuses on user needs and market positioning.&lt;/p&gt;

&lt;p&gt;Answer their questions thoroughly—they're designed to uncover requirements you might miss. As you respond, the PRD populates in real-time, showing your progress with scores from each agent's perspective. Aim for scores above 8 before moving to code generation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reference PRD in Cursor
&lt;/h3&gt;

&lt;p&gt;Include PRD sections in Cursor context. When asking Cursor to generate code, reference specific PRD sections:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Based on the user personas in the PRD..."&lt;/li&gt;
&lt;li&gt;"Following the user flow for review submission..."&lt;/li&gt;
&lt;li&gt;"Using the ERD diagram from the PRD..."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gives Cursor the context it needs to generate aligned code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Iterative Refinement
&lt;/h3&gt;

&lt;p&gt;Update PRD when requirements change, then regenerate code. Don't just patch code—update the PRD first, then let Cursor regenerate with the new context. This maintains consistency and prevents drift.&lt;/p&gt;

&lt;h3&gt;
  
  
  Version Control
&lt;/h3&gt;

&lt;p&gt;Track PRD versions to understand requirements evolution. When you need to understand why code was written a certain way, check the PRD version it was based on. This creates a clear audit trail.&lt;/p&gt;

&lt;h3&gt;
  
  
  Collaborative Planning
&lt;/h3&gt;

&lt;p&gt;PRD scoring (project manager, designer, architect, product manager) ensures completeness. Get scores above 8 before coding. This prevents generating code based on incomplete requirements.&lt;/p&gt;

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

&lt;p&gt;The future of development isn't "AI replaces developers" or "developers ignore AI." It's "AI + structured planning = better outcomes."&lt;/p&gt;

&lt;p&gt;Codalio PRD transforms AI coding from "vibe coding" to "informed development." By providing structured product context—user personas, business rules, technical specifications, and design principles—PRD enables AI coding tools like Cursor to generate code that aligns with your product vision from the start.&lt;/p&gt;

&lt;p&gt;This prevents the "rescue" cycle. It reduces technical debt. It ensures code solves the right problems. And it makes development faster and more reliable.&lt;/p&gt;

&lt;p&gt;Planning prevents problems. Structured context enables better AI assistance. Try Codalio PRD with your next Cursor project and experience the difference.&lt;/p&gt;

</description>
      <category>vibecoding</category>
      <category>softwaredevelopment</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Synthetic Attributes in Rhino</title>
      <dc:creator>Yousef</dc:creator>
      <pubDate>Thu, 23 Oct 2025 14:01:52 +0000</pubDate>
      <link>https://dev.to/codalio/synthetic-attributes-in-rhino-4l1a</link>
      <guid>https://dev.to/codalio/synthetic-attributes-in-rhino-4l1a</guid>
      <description>&lt;p&gt;Synthetic attributes are one of Rhino's most powerful yet underutilized features, allowing you to create computed properties that enhance your API responses without cluttering your database. In this comprehensive guide, we'll explore how to implement synthetic attributes in a real-world blog application, demonstrating their practical benefits and advanced usage patterns.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are Synthetic Attributes?
&lt;/h2&gt;

&lt;p&gt;Synthetic attributes are computed properties that don't exist in your database but are calculated on-the-fly and included in your API responses. They're perfect for derived data, analytics, and computed fields that would be expensive to store or frequently change.&lt;/p&gt;

&lt;p&gt;Unlike regular database columns, synthetic attributes are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Computed dynamically&lt;/strong&gt; - calculated each time they're accessed
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read-only&lt;/strong&gt; - cannot be set directly through the API
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context-aware&lt;/strong&gt; - can access related models and associations
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type-safe&lt;/strong&gt; - declared with specific data types for proper serialization&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Building Our Blog Application
&lt;/h2&gt;

&lt;p&gt;Let's start by setting up a complete blog application with three core models: Category, Blog, and BlogPost. This will give us a rich foundation to demonstrate various synthetic attribute patterns.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Data Model
&lt;/h3&gt;

&lt;p&gt;Our blog application follows a straightforward hierarchy:&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="c1"&gt;# Category Model&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Category&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;has_many&lt;/span&gt; &lt;span class="ss"&gt;:blogs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;dependent: :destroy&lt;/span&gt;
  &lt;span class="n"&gt;rhino_owner_global&lt;/span&gt;
  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Blog Model&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Blog&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;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:user&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:category&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:blog_posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;dependent: :destroy&lt;/span&gt;
  &lt;span class="n"&gt;rhino_owner_base&lt;/span&gt;
  &lt;span class="n"&gt;rhino_references&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:category&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# BlogPost Model&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BlogPost&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;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:blog&lt;/span&gt;
  &lt;span class="n"&gt;rhino_owner&lt;/span&gt; &lt;span class="ss"&gt;:blog&lt;/span&gt;
  &lt;span class="n"&gt;rhino_references&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:blog&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &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;h2&gt;
  
  
  Implementing Synthetic Attributes
&lt;/h2&gt;

&lt;p&gt;Now let's add synthetic attributes to each model, demonstrating different use cases and patterns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Blog Model: Analytics and Metrics
&lt;/h3&gt;

&lt;p&gt;The Blog model is perfect for demonstrating analytics-focused synthetic attributes:&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;Blog&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;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:user&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:category&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:blog_posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;dependent: :destroy&lt;/span&gt;
  &lt;span class="n"&gt;rhino_owner_base&lt;/span&gt;
  &lt;span class="n"&gt;rhino_references&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:category&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;

  &lt;span class="c1"&gt;# Synthetic attributes&lt;/span&gt;
  &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="ss"&gt;:word_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:integer&lt;/span&gt;
  &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="ss"&gt;:reading_time_minutes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:integer&lt;/span&gt;
  &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="ss"&gt;:is_recent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:boolean&lt;/span&gt;

  &lt;span class="c1"&gt;# Calculate total word count across all blog posts&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;word_count&lt;/span&gt;
    &lt;span class="n"&gt;blog_posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/\s+/&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;count&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;# Calculate estimated reading time (average 200 words per minute)&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reading_time_minutes&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word_count&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;200.0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Check if blog was published recently (within last 30 days)&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_recent&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;published_at&lt;/span&gt;
    &lt;span class="n"&gt;published_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ago&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Property restrictions for synthetic attributes&lt;/span&gt;
  &lt;span class="n"&gt;rhino_properties_create&lt;/span&gt; &lt;span class="ss"&gt;except: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:word_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:reading_time_minutes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:is_recent&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;rhino_properties_update&lt;/span&gt; &lt;span class="ss"&gt;except: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:word_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:reading_time_minutes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:is_recent&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  BlogPost Model: Content Analysis
&lt;/h3&gt;

&lt;p&gt;Individual blog posts benefit from content-focused synthetic attributes:&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;BlogPost&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;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:blog&lt;/span&gt;
  &lt;span class="n"&gt;rhino_owner&lt;/span&gt; &lt;span class="ss"&gt;:blog&lt;/span&gt;
  &lt;span class="n"&gt;rhino_references&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:blog&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;

  &lt;span class="c1"&gt;# Synthetic attributes&lt;/span&gt;
  &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="ss"&gt;:word_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:integer&lt;/span&gt;
  &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="ss"&gt;:reading_time_minutes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:integer&lt;/span&gt;
  &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="ss"&gt;:excerpt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;
  &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="ss"&gt;:is_long_post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:boolean&lt;/span&gt;

  &lt;span class="c1"&gt;# Calculate word count for this post&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;word_count&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/\s+/&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Calculate estimated reading time (average 200 words per minute)&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reading_time_minutes&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word_count&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;200.0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Generate excerpt (first 150 characters)&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;excerpt&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;truncate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;separator: &lt;/span&gt;&lt;span class="s1"&gt;' '&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;# Check if post is considered "long" (over 1000 words)&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_long_post&lt;/span&gt;
    &lt;span class="n"&gt;word_count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Property restrictions for synthetic attributes&lt;/span&gt;
  &lt;span class="n"&gt;rhino_properties_create&lt;/span&gt; &lt;span class="ss"&gt;except: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:word_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:reading_time_minutes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:excerpt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:is_long_post&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;rhino_properties_update&lt;/span&gt; &lt;span class="ss"&gt;except: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:word_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:reading_time_minutes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:excerpt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:is_long_post&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Category Model: Statistical Insights
&lt;/h3&gt;

&lt;p&gt;Categories can provide valuable statistical information:&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;Category&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;has_many&lt;/span&gt; &lt;span class="ss"&gt;:blogs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;dependent: :destroy&lt;/span&gt;
  &lt;span class="n"&gt;rhino_owner_global&lt;/span&gt;
  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;

  &lt;span class="c1"&gt;# Synthetic attributes&lt;/span&gt;
  &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="ss"&gt;:blog_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:integer&lt;/span&gt;
  &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="ss"&gt;:total_posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:integer&lt;/span&gt;
  &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="ss"&gt;:is_popular&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:boolean&lt;/span&gt;

  &lt;span class="c1"&gt;# Count blogs in this category&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;blog_count&lt;/span&gt;
    &lt;span class="n"&gt;blogs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Count total blog posts across all blogs in this category&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;total_posts&lt;/span&gt;
    &lt;span class="n"&gt;blogs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:blog_posts&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Check if category is popular (has more than 5 blogs)&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_popular&lt;/span&gt;
    &lt;span class="n"&gt;blog_count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Property restrictions for synthetic attributes&lt;/span&gt;
  &lt;span class="n"&gt;rhino_properties_create&lt;/span&gt; &lt;span class="ss"&gt;except: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:blog_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:total_posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:is_popular&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;rhino_properties_update&lt;/span&gt; &lt;span class="ss"&gt;except: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:blog_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:total_posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:is_popular&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  API Response in Action
&lt;/h2&gt;

&lt;p&gt;With these synthetic attributes implemented, our API responses become incredibly rich. Here's what a blog response looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"My Amazing Blog"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"published_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-01-13T04:00:00.000Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-01-13T17:44:49.893Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"updated_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-01-13T17:44:49.893Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"word_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reading_time_minutes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"is_recent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Doe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"john@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"display_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Doe"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Technology"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"blog_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total_posts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"is_popular"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"display_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Technology"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"display_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"My Amazing Blog"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"can_current_user_edit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"can_current_user_destroy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Advanced Patterns and Best Practices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Performance Optimization
&lt;/h3&gt;

&lt;p&gt;For expensive calculations, consider caching:&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;Blog&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;:expensive_calculation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:integer&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;expensive_calculation&lt;/span&gt;
    &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"blog_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_expensive_calc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;expires_in: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="c1"&gt;# Your expensive calculation here&lt;/span&gt;
      &lt;span class="n"&gt;perform_heavy_computation&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;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Conditional Attributes
&lt;/h3&gt;

&lt;p&gt;You can make synthetic attributes conditional based on user permissions or other factors:&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;BlogPost&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;:analytics_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:json&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;analytics_data&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;unless&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;admin?&lt;/span&gt;

    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;views: &lt;/span&gt;&lt;span class="n"&gt;view_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;engagement: &lt;/span&gt;&lt;span class="n"&gt;engagement_score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;trending: &lt;/span&gt;&lt;span class="n"&gt;trending_score&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;h3&gt;
  
  
  3. Type Safety and Validation
&lt;/h3&gt;

&lt;p&gt;Always declare your attribute types for proper serialization:&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;Blog&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="c1"&gt;# Good: explicit type declaration&lt;/span&gt;
  &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="ss"&gt;:word_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:integer&lt;/span&gt;
  &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="ss"&gt;:reading_time_minutes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:integer&lt;/span&gt;
  &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="ss"&gt;:is_recent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:boolean&lt;/span&gt;
  &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="ss"&gt;:metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:json&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Property Restrictions: Keeping Synthetic Attributes Read-Only
&lt;/h2&gt;

&lt;p&gt;One of the most important aspects of synthetic attributes is ensuring they remain read-only. Rhino provides powerful property restriction methods:&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="c1"&gt;# Exclude from create and update operations&lt;/span&gt;
&lt;span class="n"&gt;rhino_properties_create&lt;/span&gt; &lt;span class="ss"&gt;except: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:word_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:reading_time_minutes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:is_recent&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;rhino_properties_update&lt;/span&gt; &lt;span class="ss"&gt;except: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:word_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:reading_time_minutes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:is_recent&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Or be more specific about what's allowed&lt;/span&gt;
&lt;span class="n"&gt;rhino_properties_create&lt;/span&gt; &lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:published&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;rhino_properties_update&lt;/span&gt; &lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:published&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Real-World Use Cases
&lt;/h2&gt;

&lt;p&gt;Synthetic attributes shine in several scenarios:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;Analytics and Metrics&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;User engagement scores
&lt;/li&gt;
&lt;li&gt;Content performance metrics
&lt;/li&gt;
&lt;li&gt;Reading time estimates
&lt;/li&gt;
&lt;li&gt;Popularity indicators&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. &lt;strong&gt;Content Processing&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Word counts and readability scores
&lt;/li&gt;
&lt;li&gt;Auto-generated excerpts
&lt;/li&gt;
&lt;li&gt;Content summaries
&lt;/li&gt;
&lt;li&gt;Tag suggestions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;Business Logic&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Status calculations
&lt;/li&gt;
&lt;li&gt;Permission checks
&lt;/li&gt;
&lt;li&gt;Workflow states
&lt;/li&gt;
&lt;li&gt;Conditional data display&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. &lt;strong&gt;User Experience&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Display names and labels
&lt;/li&gt;
&lt;li&gt;Formatted data (currency, dates)
&lt;/li&gt;
&lt;li&gt;Progress indicators
&lt;/li&gt;
&lt;li&gt;Status messages&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Synthetic attributes in Rhino provide a powerful way to enhance your API responses with computed data without cluttering your database schema. By following the patterns demonstrated in this guide, you can create rich, dynamic APIs that provide valuable insights to your frontend applications.&lt;/p&gt;

&lt;p&gt;The key is to remember that synthetic attributes should be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Computed efficiently&lt;/strong&gt; - avoid expensive operations
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Properly typed&lt;/strong&gt; - declare attribute types
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read-only&lt;/strong&gt; - use property restrictions
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Meaningful&lt;/strong&gt; - provide real value to your API consumers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With these principles in mind, synthetic attributes can transform your Rhino applications from simple CRUD APIs into intelligent, data-rich interfaces that delight your users.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>opensource</category>
      <category>ruby</category>
      <category>rails</category>
    </item>
    <item>
      <title>Your First Rhino Contribution: Making MVP Development Even Faster</title>
      <dc:creator>Yousef</dc:creator>
      <pubDate>Fri, 10 Oct 2025 16:20:18 +0000</pubDate>
      <link>https://dev.to/codalio/your-first-rhino-contribution-making-mvp-development-even-faster-17dc</link>
      <guid>https://dev.to/codalio/your-first-rhino-contribution-making-mvp-development-even-faster-17dc</guid>
      <description>&lt;h2&gt;
  
  
  From Reading to Contributing
&lt;/h2&gt;

&lt;p&gt;If you've been following our Rhino Framework series, you've seen how this framework can make MVP development 90% faster. You've learned about its architecture, understood how it works, and maybe even set up your own local environment. Now it's time to take the next step: actually contributing to the project.&lt;/p&gt;

&lt;p&gt;This isn't just about getting your name in the commit history (though that's pretty cool too). Contributing to Rhino means you're helping shape the future of how we build web applications. Every contribution, from fixing a typo to implementing a new feature, makes the framework more powerful for developers around the world.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the Rhino Contribution Landscape
&lt;/h2&gt;

&lt;p&gt;Before we dive into specific examples, let's talk about what kinds of contributions Rhino needs. The framework is built on familiar technologies (Rails and React), so if you know these, you can start contributing immediately.&lt;/p&gt;

&lt;p&gt;The most valuable contributions fall into three categories: code improvements, documentation, and testing. Code contributions include bug fixes, new features, and performance optimizations. Documentation contributions help other developers understand how to use the framework. Testing contributions make the framework more reliable for everyone.&lt;/p&gt;

&lt;p&gt;When you're looking for your first contribution, keep an eye out for issues labeled with &lt;code&gt;good first issue&lt;/code&gt; or &lt;code&gt;help wanted&lt;/code&gt;. These are specifically chosen to be well-documented, limited in scope, and good learning opportunities that won't take weeks to complete.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Example: &lt;a href="https://github.com/rhino-project/rhino-project/issues/232" rel="noopener noreferrer"&gt;Issue #232&lt;/a&gt; - Organizations Factory for Testing
&lt;/h2&gt;

&lt;p&gt;Let's look at a real issue that's perfect for first-time contributors.&lt;/p&gt;

&lt;h3&gt;
  
  
  What This Issue Is Asking For
&lt;/h3&gt;

&lt;p&gt;Issue #232 is asking for the &lt;code&gt;rhino_project_organizations&lt;/code&gt; gem to install an organizations factory for testing. Here's what that means in plain English:&lt;/p&gt;

&lt;p&gt;The Rhino framework has a modular architecture with different gems for different features. One of these gems is &lt;code&gt;rhino_project_organizations&lt;/code&gt;, which handles multi-tenant functionality (think multiple companies using the same application). &lt;/p&gt;

&lt;p&gt;Currently, when developers want to test code that uses organizations, they have to manually create test data. This issue is asking for a FactoryBot factory to be automatically installed when someone includes the organizations gem, making testing much easier.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why This Matters
&lt;/h3&gt;

&lt;p&gt;Testing is crucial for any framework. When developers can't easily create test data for organizations, they either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Skip writing tests (bad)&lt;/li&gt;
&lt;li&gt;Write complex, hard-to-maintain test setup code (also bad)&lt;/li&gt;
&lt;li&gt;Avoid using the organizations feature entirely (really bad)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By providing a factory out of the box, we make the organizations feature more accessible and encourage better testing practices.&lt;/p&gt;

&lt;p&gt;To tackle this issue, you'd need to understand FactoryBot (how to write factories for test data), Ruby gems (how gems are structured and install files), Rails testing (how tests are organized), and multi-tenancy (basic understanding of how organizations work).&lt;/p&gt;

&lt;p&gt;You'd start by exploring the &lt;code&gt;rhino_project_organizations&lt;/code&gt; gem structure, looking at how other gems install factories (like &lt;code&gt;rhino_project_core&lt;/code&gt;), examining the existing test setup in the template project, and studying &lt;code&gt;FactoryBot&lt;/code&gt; documentation and best practices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Example: &lt;a href="https://github.com/rhino-project/rhino-project/issues/215" rel="noopener noreferrer"&gt;Issue #215&lt;/a&gt; - Refactoring Timestamp Attributes
&lt;/h2&gt;

&lt;p&gt;Here's another great example of a contribution opportunity.&lt;/p&gt;

&lt;h3&gt;
  
  
  What This Issue Is Asking For
&lt;/h3&gt;

&lt;p&gt;Issue #215 is about refactoring how Rhino handles timestamp attributes in ActiveRecord models. Currently, the code uses &lt;code&gt;send(:all_timestamp_attributes_in_model)&lt;/code&gt; which is a private method. The issue asks to use the public &lt;code&gt;all_timestamp_attributes_in_model&lt;/code&gt; method instead.&lt;/p&gt;

&lt;p&gt;This is a Rails API change. In newer versions of Rails, this method became public, so we no longer need to use the &lt;code&gt;send()&lt;/code&gt; workaround to access it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why This Matters
&lt;/h3&gt;

&lt;p&gt;Using private methods (even through &lt;code&gt;send()&lt;/code&gt;) is generally not a good practice because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It can break when Rails updates&lt;/li&gt;
&lt;li&gt;It's harder to understand what the code is doing&lt;/li&gt;
&lt;li&gt;It makes the codebase less maintainable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By switching to the public API, we make the code more robust and easier to understand.&lt;/p&gt;

&lt;p&gt;For this issue, you'd need Rails knowledge (understanding of ActiveRecord and Rails API changes), Ruby metaprogramming (understanding of &lt;code&gt;send()&lt;/code&gt; and method visibility), version compatibility (how to check what Rails versions support which methods), and testing (how to verify the change doesn't break anything).&lt;/p&gt;

&lt;p&gt;You'd focus on &lt;code&gt;gems/rhino_project_core/lib/rhino/resource/active_record_extension/properties.rb&lt;/code&gt;, Rails documentation for the &lt;code&gt;all_timestamp_attributes_in_model&lt;/code&gt; method, the test suite to understand how this method is used, and Rails version compatibility requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Contribution Process Step-by-Step
&lt;/h2&gt;

&lt;p&gt;Now that you understand what these issues are asking for, let's walk through the actual process of making a contribution.&lt;/p&gt;

&lt;p&gt;First, you'll need your own copy of the Rhino project. Fork the &lt;a href="https://github.com/rhino-project/rhino-project" rel="noopener noreferrer"&gt;repository on GitHub&lt;/a&gt;, then clone it locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/your-username/rhino-project.git
&lt;span class="nb"&gt;cd &lt;/span&gt;rhino-project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Getting the codebase running locally is crucial for understanding how it works. Install dependencies, set up the database, and start the development servers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;install
&lt;/span&gt;npm &lt;span class="nb"&gt;install
&lt;/span&gt;rails db:setup
rails s  &lt;span class="c"&gt;# In another terminal: npm start&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Never work directly on the main branch. Create a feature branch with a descriptive name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; fix/organizations-factory-installation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before making changes, run the existing tests to make sure everything works. This gives you a baseline to ensure your changes don't break anything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rails &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where the real work happens. Start small and test frequently. Make one small change at a time, run tests after each change, use &lt;code&gt;rails console&lt;/code&gt; to experiment with your changes, and read existing code to understand patterns.&lt;/p&gt;

&lt;p&gt;When you're ready to submit, write clear, descriptive commit messages and push your changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"fix: install organizations factory when rhino_project_organizations gem is included"&lt;/span&gt;
git push origin fix/organizations-factory-installation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then go to GitHub and create a pull request with a clear title, detailed description of the problem and solution, and a link to the original issue.&lt;/p&gt;

&lt;p&gt;Code review is a learning opportunity, not criticism. Read feedback carefully, ask questions if you don't understand, make requested changes promptly, and thank reviewers for their time and input.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools and Commands You'll Use
&lt;/h2&gt;

&lt;p&gt;Here are the essential tools you'll become familiar with during your contribution journey.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Git Workflow Basics&lt;/strong&gt; - You'll use &lt;code&gt;git status&lt;/code&gt; to check your changes, &lt;code&gt;git diff&lt;/code&gt; to see what you've modified, &lt;code&gt;git add&lt;/code&gt; to stage files, and &lt;code&gt;git commit&lt;/code&gt; with descriptive messages. Always push to your fork with &lt;code&gt;git push origin branch-name&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rails Console for Testing&lt;/strong&gt; - The Rails console is your best friend for experimenting. Start it with &lt;code&gt;rails console&lt;/code&gt;, then test your changes with commands like &lt;code&gt;Organization.create(name: "Test Org")&lt;/code&gt; or &lt;code&gt;FactoryBot.create(:organization)&lt;/code&gt;. You can also check if methods exist with &lt;code&gt;Organization.respond_to?(:all_timestamp_attributes_in_model)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Running the Test Suite&lt;/strong&gt; - Use &lt;code&gt;bundle exec rails test&lt;/code&gt; to run all tests, &lt;code&gt;bundle exec rails test --verbose&lt;/code&gt; for more output, or &lt;code&gt;bundle exec rails test test/models/organization_test.rb&lt;/code&gt; for specific files. You can also run tests matching a pattern with &lt;code&gt;bundle exec rails test -n test_organization_creation&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Debugging Techniques&lt;/strong&gt; - When things go wrong (and they will), add debug statements with &lt;code&gt;puts "Debug: #{variable.inspect}"&lt;/code&gt;, use the debugger with &lt;code&gt;require 'debug'; debugger&lt;/code&gt;, check Rails logs with &lt;code&gt;tail -f log/development.log&lt;/code&gt;, or use interactive debugging with &lt;code&gt;require 'pry'; binding.pry&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Do When You're Stuck
&lt;/h2&gt;

&lt;p&gt;Getting stuck is normal. Here's how to get unstuck:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reading Existing Code for Patterns&lt;/strong&gt; - The best way to understand how to implement something is to see how similar things are already implemented. Search for similar patterns with &lt;code&gt;grep -r "factory.*install" gems/&lt;/code&gt; or &lt;code&gt;grep -r "all_timestamp_attributes" gems/&lt;/code&gt;. Look at how other gems handle similar functionality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using GitHub Search&lt;/strong&gt; - GitHub's search is powerful for finding examples. Search for "factory installation" in the repository, look at similar pull requests, and check how other Rails gems handle similar problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Asking for Help&lt;/strong&gt; - Don't suffer in silence. Ask questions in GitHub Discussions, join our &lt;a href="https://discord.gg/jvnAZrUX7k" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; for real-time help, comment on the issue you're working on, or open a draft PR early to get feedback.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Breaking Down the Problem&lt;/strong&gt; - When an issue seems overwhelming, read the issue carefully to understand what's being asked for, find similar implementations to see how similar problems have been solved, start with the simplest case (don't try to solve everything at once), test your understanding by explaining the problem to someone else, and ask questions rather than guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ready to Get Started?
&lt;/h2&gt;

&lt;p&gt;Contributing to Rhino is easier than you might think. The framework is built on familiar technologies, the community is welcoming, and there are plenty of opportunities to make a meaningful impact.&lt;/p&gt;

&lt;p&gt;Here's your action plan: Pick an issue (start with #232 or #215, or find another "good first issue"), set up your environment (get the codebase running locally), understand the problem (read the issue carefully and explore the codebase), make a small change (don't try to solve everything at once), ask for help (use Discord or GitHub discussions when you're stuck), and submit your contribution (open a pull request and celebrate!).&lt;/p&gt;

&lt;p&gt;Remember, every expert was once a beginner. The Rhino community is here to help you learn and grow. Your first contribution might be small, but it's the first step toward becoming a valuable member of the open source community.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post is part of our Hacktoberfest 2025 series. Follow along as we explore Rhino Framework through real examples and community contributions.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>hacktoberfest</category>
      <category>opensource</category>
      <category>ruby</category>
      <category>rails</category>
    </item>
    <item>
      <title>A Deep Dive into the Rhino Framework</title>
      <dc:creator>Yousef</dc:creator>
      <pubDate>Tue, 07 Oct 2025 10:02:28 +0000</pubDate>
      <link>https://dev.to/codalio/a-deep-dive-into-the-rhino-framework-5adf</link>
      <guid>https://dev.to/codalio/a-deep-dive-into-the-rhino-framework-5adf</guid>
      <description>&lt;p&gt;Welcome to our engineering blog series on Rhino, a framework that truly embodies the principles of Model-Driven Development (MDD). As a vibe-coding friendly framework, Rhino is perfect for developers who want to focus on their application's core logic and bring ideas to life with minimal friction. In this series, we'll explore how its architecture accelerates development by providing a robust set of tools and conventions out of the box. This first post will provide a high-level overview of the framework's structure, walk through a practical example, and then dive into the core mechanisms that make it such an intuitive platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Rhino Mono-repo: A Unified Structure&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;At its core, the Rhino framework is organized within a single mono-repo called &lt;a href="https://github.com/rhino-project/rhino-project" rel="noopener noreferrer"&gt;rhino-project&lt;/a&gt;. This approach houses both the backend Ruby gems and frontend JavaScript packages in one place, fostering a cohesive development experience. This structure allows for streamlined dependency management and ensures that all components of the stack are designed to work together seamlessly.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Backend Gems&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The backend functionality is broken down into a collection of modular Ruby gems located in the gems/ directory. This design allows developers to selectively include the features they need. The cornerstone is the &lt;code&gt;rhino_project_core&lt;/code&gt; gem, which delivers foundational capabilities like authentication, authorization, and a dynamic system for generating API endpoints. Building upon this core, specialized gems such as &lt;code&gt;rhino_project_notifications&lt;/code&gt;, &lt;code&gt;rhino_project_organizations&lt;/code&gt;, and &lt;code&gt;rhino_project_subscriptions&lt;/code&gt; offer targeted functionalities for notifications, multi-tenancy, and payment processing, creating a layered and extensible architecture.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Frontend Packages&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The frontend consists of a suite of JavaScript/TypeScript packages located in the packages/directory. These packages offer a variety of tools and reusable components for constructing the user interface. Notable packages include @rhino-project/core, which contains essential UI components and hooks that integrate directly with the backend API, and @rhino-project/config, which provides shareable configurations for development tools like ESLint and Prettier. To simplify project setup, the create-rhino-app package provides a command-line tool to quickly scaffold a new application.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;A Practical Example: The Tutorial Application&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;To understand how these pieces fit together, let’s look at the official Rhino tutorial, which guides you through building a simple blog application. This example leverages the rhino-project-template, a pre-configured project that includes CI/CD setup and demonstrates the core functionalities of the framework without any additional modules like organizations.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Data Model&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The blog application is built around three primary models: Category, Blog, and &lt;code&gt;BlogPost&lt;/code&gt;. Their relationships are straightforward: a Category can have many blogs, a Blog belongs to a category and a user, and a &lt;code&gt;BlogPost&lt;/code&gt; belongs to a blog.&lt;/p&gt;

&lt;p&gt;Here is how these models are defined in the application:&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="c1"&gt;# Category Model:  &lt;/span&gt;
&lt;span class="c1"&gt;# File Path: app/models/category.rb  &lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Category&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;has_many&lt;/span&gt; &lt;span class="ss"&gt;:blogs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;dependent: :destroy&lt;/span&gt;

  &lt;span class="c1"&gt;# Rhino specific code  &lt;/span&gt;
  &lt;span class="n"&gt;rhino_owner_global&lt;/span&gt;

  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;  
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Blog Model:  &lt;/span&gt;
&lt;span class="c1"&gt;# File Path: app/models/blog.rb  &lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Blog&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;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:user&lt;/span&gt;  
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:category&lt;/span&gt;  
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:blog_posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;dependent: :destroy&lt;/span&gt;

  &lt;span class="c1"&gt;# Rhino specific code  &lt;/span&gt;
  &lt;span class="n"&gt;rhino_owner_base&lt;/span&gt;  
  &lt;span class="n"&gt;rhino_references&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:category&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;  
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# BlogPost Model:  &lt;/span&gt;
&lt;span class="c1"&gt;# File Path: app/models/blog_post.rb  &lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BlogPost&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;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:blog&lt;/span&gt;

  &lt;span class="c1"&gt;# Rhino specific code  &lt;/span&gt;
  &lt;span class="n"&gt;rhino_owner&lt;/span&gt; &lt;span class="ss"&gt;:blog&lt;/span&gt;  
  &lt;span class="n"&gt;rhino_references&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:blog&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;  
  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &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;Now that we have our data models, the next question is: how are they exposed through an API? This is where Rhino's controller system comes into play.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Exposing the API: A Deep Dive into Controllers and Policies&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Rhino provides a flexible and powerful system of controllers and policies that work in tandem to secure and expose your API endpoints with minimal boilerplate.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Controller Hierarchy&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The controllers in &lt;code&gt;rhino_project_core&lt;/code&gt; follow a clear inheritance hierarchy, which allows for shared functionality while enabling specialization where needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ActionController::API (from Rails)  
    └── Rhino::BaseController  
        ├── Rhino::AccountController  
        ├── Rhino::ActiveModelExtensionController  
        ├── Rhino::CrudController  
        │   └── Rhino::ActiveRecordDiscardController  
        ├── Rhino::SimpleController  
        └── Rhino::SimpleStreamController
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Rhino::BaseController&lt;/code&gt;:&lt;/strong&gt; This is the foundation for all other controllers in the framework. It integrates essential modules for authentication, authorization (via Pundit), and error handling. It’s an abstract controller and doesn’t handle routes directly.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Rhino::CrudController&lt;/code&gt;:&lt;/strong&gt; This is the default controller for database-backed resources. For the models we created in the tutorial (&lt;code&gt;Category&lt;/code&gt;, &lt;code&gt;Blog&lt;/code&gt;, &lt;code&gt;BlogPost&lt;/code&gt;), this controller provides the standard Create, Read, Update, and Destroy (CRUD) actions out of the box.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Other Controllers&lt;/code&gt;:&lt;/strong&gt; The hierarchy also includes specialized controllers like &lt;code&gt;Rhino::ActiveRecordDiscardController&lt;/code&gt; for soft-deletes, &lt;code&gt;Rhino::AccountController&lt;/code&gt; for managing user-specific data, and others for handling virtual models and simple resources.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Authorization with Policies&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Authorization is seamlessly integrated into the &lt;code&gt;BaseController&lt;/code&gt; via the Pundit gem, meaning every controller action is automatically authorized. The framework provides a default policy, &lt;code&gt;Rhino::CrudPolicy&lt;/code&gt;, which is used by the &lt;code&gt;CrudController&lt;/code&gt; to enforce basic authorization rules. For instance, it might allow any authenticated user to create a resource but permit only the resource owner to update or destroy it.&lt;/p&gt;

&lt;p&gt;The true power of this system is the ability to override these defaults. By creating a &lt;code&gt;BlogPolicy&lt;/code&gt; in your application, the &lt;code&gt;CrudController&lt;/code&gt; will automatically use your custom logic instead of the default policy when handling actions on Blog resources.&lt;/p&gt;

&lt;p&gt;Here is an example from the &lt;code&gt;CrudController&lt;/code&gt; showing how Pundit is used:&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="c1"&gt;# In Rhino::CrudController  &lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;  
  &lt;span class="vi"&gt;@record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;policy_scope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_class&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  
  &lt;span class="n"&gt;authorize&lt;/span&gt; &lt;span class="vi"&gt;@record&lt;/span&gt;

  &lt;span class="c1"&gt;# ... rest of the update logic  &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 snippet, &lt;code&gt;authorize @record&lt;/code&gt; triggers Pundit to find a policy corresponding to the record's class. If a &lt;code&gt;BlogPolicy&lt;/code&gt; exists, its &lt;code&gt;update?&lt;/code&gt; method will be called to determine if the action is permitted.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Dynamic Resource Routing: The Heart of Vibe-Coding&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;One of Rhino's most significant features, and what makes it so conducive to vibe-coding, is its ability to automatically generate API endpoints directly from your models. This Model-Driven Development approach minimizes configuration and lets you focus on your application's logic. Here’s a step-by-step breakdown of how it works.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. The Central List: Rhino.resources&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;At the heart of the system is a simple array of strings, &lt;code&gt;Rhino.resources&lt;/code&gt;, which holds the names of all the models to be exposed as API endpoints. This is defined in &lt;code&gt;rhino-project/gems/rhino_project_core/lib/rhino_project_core.rb&lt;/code&gt;:&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="c1"&gt;# module Rhino  &lt;/span&gt;
&lt;span class="c1"&gt;# ...  &lt;/span&gt;
&lt;span class="n"&gt;mattr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:resources&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;production?&lt;/span&gt;  
                                      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'ActiveStorage::Attachment'&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="s1"&gt;'ActiveStorage::Attachment'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Rhino::OpenApiInfo'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Rhino::InfoGraph'&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;# ...  &lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;2. Registration: Adding Your Resource&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;You add your own resources to this list using the &lt;code&gt;Rhino.setup&lt;/code&gt; block, typically located in an initializer file. In the template project, this is done in &lt;code&gt;rhino-project-template/config/initializers/rhino.rb&lt;/code&gt;:&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="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="no"&gt;Rhino&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setup&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;  
  &lt;span class="c1"&gt;# ...  &lt;/span&gt;
  &lt;span class="c1"&gt;# The list of resources exposed in the API  &lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resources&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"User"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Account"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;  
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;3. Gaining Capabilities: include &lt;code&gt;Rhino::Resource&lt;/code&gt;&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;For a model to be a valid resource, it must include the &lt;code&gt;Rhino::Resource&lt;/code&gt; module. This module, found in &lt;code&gt;rhino-project/gems/rhino_project_core/lib/rhino/resource.rb&lt;/code&gt;, equips the model with the necessary methods to communicate with the routing system.&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="c1"&gt;# app/models/blog.rb  &lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Blog&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="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Rhino&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Resource&lt;/span&gt;

&lt;span class="c1"&gt;# ...  &lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By including this module, the Blog model now has methods like &lt;code&gt;route_key&lt;/code&gt; and &lt;code&gt;controller_name&lt;/code&gt; that the router will use.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;4. The Router: Generating the Routes&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The final step occurs in the routes file, &lt;code&gt;rhino-project/gems/rhino_project_core/config/routes.rb&lt;/code&gt;. Here, the code iterates over the resource classes and generates the routes.&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="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;  
  &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="no"&gt;Rhino&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;namespace&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;  
    &lt;span class="c1"&gt;# ...  &lt;/span&gt;
    &lt;span class="no"&gt;Rhino&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resource_classes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;  
      &lt;span class="c1"&gt;# ...  &lt;/span&gt;
      &lt;span class="n"&gt;resources&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;route_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;path: &lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;route_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;controller: &lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;controller_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;rhino_resource: &lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;format: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;   
      &lt;span class="c1"&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="c1"&gt;# ...  &lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key is the call to &lt;code&gt;Rhino.resource_classes&lt;/code&gt;. This method converts the array of strings in &lt;code&gt;Rhino.resources&lt;/code&gt; into an array of class objects, allowing the router to call the methods provided by the &lt;code&gt;Rhino::Resource&lt;/code&gt; module to build the routes dynamically.&lt;/p&gt;

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

&lt;p&gt;The Rhino framework provides a well-architected foundation that champions a Model-Driven Development approach. By combining a mono-repo structure with a powerful system of controllers, policies, and dynamic routing, it facilitates a rapid, 'vibe-coding' workflow that streamlines development. This allows you to build secure and scalable APIs with remarkable efficiency by focusing on what matters: your data model. In our next post, we will build upon this foundation and explore some of the more advanced features Rhino has to offer. Stay tuned!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ruby</category>
      <category>rails</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Contributing to Rhino: Building the Future of MVP Development</title>
      <dc:creator>Yousef</dc:creator>
      <pubDate>Wed, 01 Oct 2025 16:24:30 +0000</pubDate>
      <link>https://dev.to/codalio/contributing-to-rhino-building-the-future-of-mvp-development-17jo</link>
      <guid>https://dev.to/codalio/contributing-to-rhino-building-the-future-of-mvp-development-17jo</guid>
      <description>&lt;h2&gt;
  
  
  Why Contribute to Rhino?
&lt;/h2&gt;

&lt;p&gt;So you've read about Rhino Framework and you're thinking, "This sounds interesting, but how can I actually help?" Great question. Contributing to open source projects like Rhino isn't just about writing code - it's about building the tools that make development better for everyone.&lt;/p&gt;

&lt;p&gt;When you contribute to Rhino, you're not just fixing bugs or adding features. You're helping shape the future of how we build web applications. Every contribution, from fixing a typo in documentation to implementing a new feature, makes the framework more powerful and accessible for developers around the world.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes Rhino Different for Contributors
&lt;/h2&gt;

&lt;p&gt;Unlike many open source projects that require deep knowledge of complex systems, Rhino is designed to be contributor-friendly. The framework is built on familiar technologies (Rails and React), so if you know these technologies, you can start contributing immediately.&lt;/p&gt;

&lt;p&gt;The codebase is structured with clear patterns and conventions, making it easy to understand how different parts work together. Plus, the framework's focus on higher-level abstractions means you can work on meaningful features without getting lost in low-level implementation details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Types of Contributions We Need
&lt;/h2&gt;

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

&lt;p&gt;One of the most valuable contributions you can make is improving documentation. This includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tutorial content&lt;/strong&gt;: Step-by-step guides for common tasks
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API documentation&lt;/strong&gt;: Clear explanations of how different components work
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Example applications&lt;/strong&gt;: Real-world projects that demonstrate best practices
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Video content&lt;/strong&gt;: Screen recordings showing how to use different features&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Bug Fixes and Improvements
&lt;/h3&gt;

&lt;p&gt;The Rhino project has several open issues that are perfect for contributors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Good first issues&lt;/strong&gt;: Simple fixes that help you learn the codebase
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance improvements&lt;/strong&gt;: Optimizing existing functionality
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UI/UX enhancements&lt;/strong&gt;: Making the admin interface more user-friendly
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing&lt;/strong&gt;: Adding tests for existing features or improving test coverage&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  New Features and Integrations
&lt;/h3&gt;

&lt;p&gt;As the framework evolves, we need contributors to help with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;New components&lt;/strong&gt;: Building reusable UI components
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integrations&lt;/strong&gt;: Connecting with third-party services
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Advanced features&lt;/strong&gt;: Multi-tenancy improvements, authentication enhancements
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI integration&lt;/strong&gt;: Making the framework work better with AI tools&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started with Your First Contribution
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Set Up Your Development Environment
&lt;/h3&gt;

&lt;p&gt;First, you'll need to get the Rhino codebase running locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Fork the repository on GitHub&lt;/span&gt;
git clone https://github.com/your-username/rhino-project.git
&lt;span class="nb"&gt;cd &lt;/span&gt;rhino-project

&lt;span class="c"&gt;# Install dependencies&lt;/span&gt;
bundle &lt;span class="nb"&gt;install
&lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="c"&gt;# Set up the database&lt;/span&gt;
rails db:setup

&lt;span class="c"&gt;# Start the development servers&lt;/span&gt;
rails s
&lt;span class="c"&gt;# In another terminal:&lt;/span&gt;
npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Find an Issue to Work On
&lt;/h3&gt;

&lt;p&gt;Look for issues labeled with "good first issue" or "help wanted". These are specifically chosen to be beginner-friendly and well-documented. Some current examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Issue #232&lt;/strong&gt;: Add organizations factory for testing
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Issue #215&lt;/strong&gt;: Refactor timestamp attributes method
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation&lt;/strong&gt;: Improve getting started guide
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Examples&lt;/strong&gt;: Create simple blog model example&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Understand the Codebase Structure
&lt;/h3&gt;

&lt;p&gt;Rhino follows a clear structure that makes it easy to navigate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backend&lt;/strong&gt;: Rails application with models, controllers, and services
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend&lt;/strong&gt;: React components with TypeScript support
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Admin&lt;/strong&gt;: Auto-generated admin interfaces
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing&lt;/strong&gt;: Comprehensive test suite with examples&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Make Your Changes
&lt;/h3&gt;

&lt;p&gt;Start with small, focused changes. Don't try to fix everything at once. Make sure your changes are well-tested and documented.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Submit a Pull Request
&lt;/h3&gt;

&lt;p&gt;When you're ready, submit a pull request with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clear description of what you changed
&lt;/li&gt;
&lt;li&gt;Tests for new functionality
&lt;/li&gt;
&lt;li&gt;Updated documentation if needed
&lt;/li&gt;
&lt;li&gt;Screenshots for UI changes&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Community and Support
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Getting Help
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Discussions&lt;/strong&gt;: Ask questions and share ideas
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Discord Community&lt;/strong&gt;: Real-time chat with other contributors and developers in our &lt;a href="https://discord.gg/e5hPVsY5s9" rel="noopener noreferrer"&gt;Discord&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code Reviews&lt;/strong&gt;: Learn from feedback on your contributions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have something in mind that's not an existing GitHub issue, reach out to our Discord community to talk directly with developers who can help guide you through the contribution process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recognition and Growth
&lt;/h3&gt;

&lt;p&gt;Contributing to Rhino isn't just about the code. It's about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Building your portfolio&lt;/strong&gt;: Showcase your skills to potential employers
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Learning new technologies&lt;/strong&gt;: Gain experience with modern development practices
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Networking&lt;/strong&gt;: Connect with other developers and industry professionals
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Making an impact&lt;/strong&gt;: Help shape the future of web development&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Impact of Your Contributions
&lt;/h2&gt;

&lt;p&gt;Every contribution to Rhino makes a difference. Whether you're fixing a small bug, improving documentation, or adding a new feature, you're helping developers around the world build better applications faster.&lt;/p&gt;

&lt;p&gt;The framework is used by startups, enterprises, and individual developers who need to move quickly without sacrificing code quality. Your contributions help these developers focus on what matters most - building the features that make their applications unique.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ready to Get Started?
&lt;/h2&gt;

&lt;p&gt;Contributing to Rhino is easier than you might think. The framework is designed to be accessible, the community is welcoming, and there are plenty of opportunities to make a meaningful impact.&lt;/p&gt;

&lt;p&gt;Start by exploring the &lt;a href="https://github.com/rhino-project/rhino-project" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;, reading through some issues, and setting up your development environment. Don't worry about making mistakes - that's how we all learn. The important thing is to get started and be part of building the future of web development.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post is part of our Hacktoberfest 2025 series. Follow along as we explore Rhino Framework through real examples and community contributions.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>hacktoberfest</category>
      <category>opensource</category>
      <category>rails</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Rhino Framework: The Real-Code Solution for MVP Development</title>
      <dc:creator>Yousef</dc:creator>
      <pubDate>Wed, 01 Oct 2025 16:23:43 +0000</pubDate>
      <link>https://dev.to/codalio/rhino-framework-the-real-code-solution-for-mvp-development-n1m</link>
      <guid>https://dev.to/codalio/rhino-framework-the-real-code-solution-for-mvp-development-n1m</guid>
      <description>&lt;h2&gt;
  
  
  The MVP Development Problem (And Why It Sucks)
&lt;/h2&gt;

&lt;p&gt;Look, building an MVP shouldn't be this hard. But here we are, spending weeks setting up authentication, admin dashboards, and all the boring stuff before we can even start building what actually matters.&lt;/p&gt;

&lt;p&gt;Every web app needs the same basic stuff:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User authentication (because apparently we can't just trust people)
&lt;/li&gt;
&lt;li&gt;Admin dashboards (because someone needs to manage all this chaos)
&lt;/li&gt;
&lt;li&gt;Database setup (the fun part where nothing works the first time)
&lt;/li&gt;
&lt;li&gt;Frontend build tools (because why make it simple?)
&lt;/li&gt;
&lt;li&gt;Testing frameworks (that we'll probably ignore anyway)
&lt;/li&gt;
&lt;li&gt;Background jobs (for all the stuff that takes forever)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These "commodity" features eat up like 50% of your development budget. That's insane. You're spending half your time and money on stuff that every app needs, instead of building the features that make your product actually interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Current Development Landscape
&lt;/h2&gt;

&lt;p&gt;When building MVPs, developers typically choose between two approaches:&lt;/p&gt;

&lt;h3&gt;
  
  
  Traditional Framework Development
&lt;/h3&gt;

&lt;p&gt;Most developers start with Rails, React, or similar frameworks. This gives you complete control and flexibility, but comes with a significant time investment. You need to set up authentication, build admin interfaces, configure databases, and handle all the infrastructure yourself. While this approach works well for experienced teams, it can take weeks just to get to a working prototype.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rapid Development Tools
&lt;/h3&gt;

&lt;p&gt;Various tools promise faster development through visual builders, AI generation, or pre-built components. These can be great for prototyping, but often come with limitations around customization, scalability, or long-term maintenance. The challenge is finding the right balance between speed and flexibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Rhino Framework
&lt;/h2&gt;

&lt;p&gt;Rhino Framework is what happens when you take Ruby on Rails and React, and actually make them work together properly. It's real code that you can see, test, and modify. No black boxes, no vendor lock-in, no "trust us, it works."&lt;/p&gt;

&lt;h3&gt;
  
  
  What Makes Rhino Different
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Complete Application Out of the Box&lt;/strong&gt; Rhino comes with a fully functional application template that includes user authentication, admin dashboards, and multi-tenant architecture. Instead of spending weeks setting up these commodity features, you get a working application in minutes. The authentication system handles user registration, login, password reset, and email verification. The admin interface automatically generates CRUD operations for your models, complete with filtering, searching, and bulk actions. Multi-tenancy is built-in with account-based organization structure, so you can support multiple customers from day one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;90% Faster MVP Development&lt;/strong&gt; Traditional Rails development requires you to set up authentication (usually with Devise), configure admin interfaces (often with ActiveAdmin), set up multi-tenancy, configure frontend build tools, and handle deployment. With Rhino, all of this is pre-configured and working. You can focus on building your unique business logic instead of reinventing the same infrastructure components. The framework provides higher-level abstractions that reduce boilerplate code, making it easier to build and maintain complex applications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI-Ready Architecture&lt;/strong&gt; Rhino is designed to work seamlessly with AI development tools. The framework uses structured patterns and higher-level abstractions that AI can understand and generate. Instead of asking AI to write low-level Rails code, you can describe your business requirements in natural language, and AI can generate the appropriate Rhino components. This makes AI-assisted development more reliable and maintainable, as the generated code follows consistent patterns and best practices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Benefits for Developers
&lt;/h2&gt;

&lt;h3&gt;
  
  
  For Junior Developers
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Minimal Learning Curve&lt;/strong&gt;: Since Rhino is built on Rails and React, you're learning industry-standard technologies. The framework provides clear patterns and conventions, so you don't have to figure out how to structure your application. The pre-configured setup means you can see how authentication, admin interfaces, and multi-tenancy work in a real application, giving you hands-on experience with production-ready code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Instant Productivity&lt;/strong&gt;: Instead of spending weeks learning how to set up authentication or build admin interfaces, you can start building your unique features immediately. The framework handles all the commodity functionality, so you can focus on learning the business logic and user experience aspects of development.&lt;/p&gt;

&lt;h3&gt;
  
  
  For Senior Developers
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Time Savings&lt;/strong&gt;: Senior developers often find themselves rebuilding the same infrastructure components across projects. With Rhino, you get a proven foundation that handles authentication, admin interfaces, multi-tenancy, and deployment. This lets you focus on the complex business logic and user experience that differentiates your application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scalability&lt;/strong&gt;: Rhino is built on Rails and React, so you get all the scalability benefits of these mature frameworks. The multi-tenant architecture is designed to handle growth, and the codebase is structured to support enterprise-level features as your application evolves.&lt;/p&gt;

&lt;h3&gt;
  
  
  For Startups
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Faster Time-to-Market&lt;/strong&gt;: Startups need to validate their ideas quickly. With Rhino, you can go from idea to working prototype in days instead of months. The pre-configured infrastructure means you can focus on building the features that matter to your customers, not the technical foundation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lower Development Costs&lt;/strong&gt;: By eliminating weeks of infrastructure setup, Rhino reduces development costs significantly. You can allocate more resources to user research, feature development, and customer acquisition instead of technical setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started with Rhino
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Ruby 3.3.4 or newer
&lt;/li&gt;
&lt;li&gt;Node.js 20.14.0 or newer
&lt;/li&gt;
&lt;li&gt;PostgreSQL 15 or newer&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Quick Setup
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Clone the template&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/rhino-project/rhino-project-template.git
&lt;span class="nb"&gt;cd &lt;/span&gt;rhino-project-template
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Set up environment&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rails rhino:dev:setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install dependencies&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;install
&lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Set up database&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rails db:setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start development servers&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Terminal 1: Rails server&lt;/span&gt;
rails s

&lt;span class="c"&gt;# Terminal 2: Frontend development server&lt;/span&gt;
npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visit &lt;code&gt;http://localhost:3000&lt;/code&gt; to see your application running!&lt;/p&gt;

&lt;h3&gt;
  
  
  What You Get Out of the Box
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;User Authentication&lt;/strong&gt;: Sign up, sign in, password reset
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Admin Dashboard&lt;/strong&gt;: Access at &lt;code&gt;/admin&lt;/code&gt; for content management
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-tenant Architecture&lt;/strong&gt;: Account-based organization structure
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modern UI&lt;/strong&gt;: Professional interface with Bootstrap and React
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Background Jobs&lt;/strong&gt;: Queue system for long-running tasks
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File Storage&lt;/strong&gt;: Active Storage for file uploads
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing Setup&lt;/strong&gt;: Comprehensive testing framework&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Real-World Example: From Idea to MVP
&lt;/h2&gt;

&lt;p&gt;Let's say you want to build a SaaS application for project management. With traditional Rails development, you'd need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set up authentication (2-3 days)
&lt;/li&gt;
&lt;li&gt;Build admin interface (1-2 weeks)
&lt;/li&gt;
&lt;li&gt;Configure multi-tenancy (2-3 days)
&lt;/li&gt;
&lt;li&gt;Set up frontend build tools (1-2 days)
&lt;/li&gt;
&lt;li&gt;Implement file uploads (1-2 days)
&lt;/li&gt;
&lt;li&gt;Configure background jobs (1-2 days)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Total: 2-3 weeks of setup before you can start building features&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With Rhino Framework, all of this is pre-configured. You can start building your project management features immediately, focusing on what makes your application unique.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Future of Web Development
&lt;/h2&gt;

&lt;p&gt;Rhino Framework represents the future of web development: a middle ground between the flexibility of traditional frameworks and the speed of modern development tools. As AI becomes more capable, frameworks like Rhino will be essential for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AI-Assisted Development&lt;/strong&gt;: Higher-level abstractions that AI can understand
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rapid Prototyping&lt;/strong&gt;: Quick iteration and experimentation
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production Readiness&lt;/strong&gt;: Real code that scales with your business
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Team Collaboration&lt;/strong&gt;: Consistent patterns and best practices&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;This is the first in a series of tutorials that will show you how to build real applications with Rhino Framework. Over the next few weeks, we'll cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Week 2&lt;/strong&gt;: Building your first model and admin interface
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Week 3&lt;/strong&gt;: Creating custom React components
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Week 4&lt;/strong&gt;: Advanced features and deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'll also be participating in Hacktoberfest 2025, so you can contribute to the framework while learning how to use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Join the Community
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/rhino-project/rhino-project" rel="noopener noreferrer"&gt;rhino-project&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation&lt;/strong&gt;: &lt;a href="https://www.rhino-project.org" rel="noopener noreferrer"&gt;rhino-project.org&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Discord&lt;/strong&gt;: &lt;a href="https://discord.gg/e5hPVsY5s9" rel="noopener noreferrer"&gt;Codalio&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Rhino Framework solves the fundamental problem of MVP development: too much time spent on commodity features, not enough time on what makes your product unique. By providing a real-code foundation with all the essential features pre-configured, Rhino lets you focus on building the features that matter.&lt;/p&gt;

&lt;p&gt;Whether you're a junior developer looking to build your first application or a senior developer tired of reinventing the wheel, Rhino Framework provides the perfect balance of speed, flexibility, and production readiness.&lt;/p&gt;

&lt;p&gt;Ready to build your MVP 90% faster? Let's get started with Rhino Framework.&lt;/p&gt;

</description>
      <category>hacktoberfest</category>
      <category>opensource</category>
      <category>rails</category>
      <category>ruby</category>
    </item>
  </channel>
</rss>
