# Property Models - Quick Reference Guide\n\n## The Three-Layer Model Architecture\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│                         APPLICATION LAYER                       │\n├─────────────────────────────────────────────────────────────────┤\n│  Controllers & Views read from Pwb::Property (Materialized View)│\n│  Controllers & Services write to RealtyAsset/Listings            │\n└─────────────────────────────────────────────────────────────────┘\n                              │\n         ┌────────────────────┼────────────────────┐\n         │                    │                    │\n         ▼                    ▼                    ▼\n┌──────────────────┐  ┌──────────────────┐  ┌──────────────────┐\n│  Pwb::Property   │  │Pwb::RealtyAsset  │  │  Pwb::Prop       │\n│  (Read-Only)     │  │  (Writable)      │  │  (Translations)  │\n│  Materialized    │  │  Physical Data   │  │  via Mobility    │\n│  View (UUID)     │  │  Asset (UUID)    │  │  JSONB Column    │\n└──────────────────┘  └──────────────────┘  └──────────────────┘\n         │                    │                    │\n         └────────────────────┼────────────────────┘\n                              │\n         ┌────────────────────┼────────────────────┐\n         │                    │                    │\n         ▼                    ▼                    ▼\n  [JOIN LOGIC]    ┌──────────────────┐  ┌──────────────────┐\n  at View Level   │Pwb::SaleListing  │  │Pwb::RentalListing│\n                  │  (Writable)      │  │  (Writable)      │\n                  │  Sale Data (UUID)│  │  Rental Data(UUID)│\n                  └──────────────────┘  └──────────────────┘\n                              │\n         ┌────────────────────┼────────────────────┐\n         │                    │                    │\n         ▼                    ▼                    ▼\n    ┌────────────┐       ┌────────────┐      ┌─────────────┐\n    │PropPhotos  │       │Features    │      │Translations │\n    │(PG TABLE)  │       │(PG TABLE)  │      │ (PG TABLE)  │\n    └────────────┘       └────────────┘      └─────────────┘\n      (by Asset)          (by Asset)            (from Prop)\n```\n\n## The Four Primary Models\n\n### 1. Pwb::RealtyAsset - Physical Property Data\n\n**What it is**: The single source of truth for physical property characteristics\n\n**Primary Key**: UUID\n\n**When to use**:\n- Creating new properties\n- Updating property attributes (bedrooms, bathrooms, location, etc.)\n- Managing photos and features\n- Admin/backend operations\n\n**Key Fields**:\n```ruby\nreference              # Property ID (e.g., \"PROP-001\")\ncountry, city, region  # Location\nstreet_address         # Full address\nlatitude, longitude    # Geocoding\ncount_bedrooms         # 0, 1, 2, 3...\ncount_bathrooms        # 1.0, 1.5, 2.0...\nconstructed_area       # Square meters/feet\nyear_construction      # Year built\nprop_type_key          # Classification key\nprop_state_key         # State/condition key\nprop_origin_key        # Origin key\nwebsite_id             # Multi-tenancy scoping\n```\n\n**Automatic Behaviors**:\n- Geocodes when explicitly requested (`geocode_address!`)\n- Refreshes `pwb_properties` materialized view after commits\n- Returns `title` and `description` by delegating to `Pwb::Prop`\n\n**Code Example**:\n```ruby\n# Create\nasset = Pwb::RealtyAsset.create!(\n  reference: 'VILLA-001',\n  count_bedrooms: 4,\n  city: 'Barcelona',\n  website_id: 1\n)\n\n# Update\nasset.update!(count_bathrooms: 3.0, latitude: 41.3874, longitude: 2.1686)\n\n# Access\nasset.title              # Delegates to Prop model\nasset.prop_photos        # Has many\nasset.features           # Has many\nasset.sale_listings      # Has many\nasset.rental_listings    # Has many\n```\n\n---\n\n### 2. Pwb::SaleListing - Sale Transaction Data\n\n**What it is**: Defines when and how a property is being sold\n\n**Primary Key**: UUID\n\n**When to use**:\n- Creating/updating sale listings\n- Changing sale price or commission\n- Publishing/unpublishing for sale\n- Sale-specific metadata\n\n**Key Fields**:\n```ruby\nrealty_asset_id         # Links to RealtyAsset (UUID)\nreference               # Can be different from asset (e.g., \"SALE-001\")\nvisible                 # Is this listing currently visible?\nhighlighted             # Feature this listing?\narchived                # Soft delete flag\nreserved                # Is it reserved/pending?\nfurnished               # Is property furnished?\nprice_sale_current_cents       # In cents/smallest unit\nprice_sale_current_currency    # Currency code (EUR, USD, etc.)\ncommission_cents        # Agent commission\ncommission_currency     # Currency for commission\n```\n\n**Automatic Behaviors**:\n- Monetizes price fields (handles currency conversion)\n- Refreshes materialized view after commits\n- Delegates property data to associated `realty_asset`\n\n**Code Example**:\n```ruby\n# Create\nsale = asset.sale_listings.create!(\n  visible: true,\n  price_sale_current_cents: 50000000,  # 500,000 EUR\n  price_sale_current_currency: 'EUR',\n  commission_cents: 500000             # 5,000 EUR\n)\n\n# Update\nsale.update!(price_sale_current_cents: 45000000)  # Price reduction\n\n# Query\nasset.sale_listings.visible.first\nasset.for_sale?  # Helper method\n```\n\n---\n\n### 3. Pwb::RentalListing - Rental Transaction Data\n\n**What it is**: Defines when and how a property is being rented\n\n**Primary Key**: UUID\n\n**When to use**:\n- Creating/updating rental listings\n- Managing seasonal pricing (low/high season)\n- Setting rental period (short-term vs. long-term)\n- Publishing/unpublishing for rent\n\n**Key Fields**:\n```ruby\nrealty_asset_id         # Links to RealtyAsset (UUID)\nreference               # Listing identifier (e.g., \"RENT-001\")\nvisible                 # Currently available for rent?\nhighlighted             # Feature this listing?\narchived                # Soft delete flag\nfor_rent_short_term     # Allow vacation/short-term rentals?\nfor_rent_long_term      # Allow long-term rentals?\nreserved                # Is it reserved?\nfurnished               # Furnished or unfurnished?\n\n# Pricing (all in cents)\nprice_rental_monthly_current_cents         # Base monthly rate\nprice_rental_monthly_low_season_cents      # Off-season rate\nprice_rental_monthly_high_season_cents     # Peak season rate\nprice_rental_monthly_current_currency      # Currency code\n```\n\n**Automatic Behaviors**:\n- Monetizes all price fields\n- Refreshes materialized view after commits\n- Includes `vacation_rental?` helper\n- Delegates property data to associated `realty_asset`\n\n**Code Example**:\n```ruby\n# Create\nrental = asset.rental_listings.create!(\n  for_rent_short_term: true,\n  for_rent_long_term: true,\n  visible: true,\n  price_rental_monthly_current_cents: 300000,     # 3,000 EUR/month\n  price_rental_monthly_low_season_cents: 200000,  # 2,000 in low season\n  price_rental_monthly_high_season_cents: 400000  # 4,000 in high season\n)\n\n# Query\nasset.rental_listings.for_rent_short_term\nasset.rental_listings.vacation_rental?\nasset.for_rent?  # Helper method\n```\n\n---\n\n### 4. Pwb::Property - Denormalized Read View\n\n**What it is**: A read-only materialized view combining all property data\n\n**Primary Key**: UUID (same as RealtyAsset.id)\n\n**When to use**:\n- Listing properties in frontend\n- Searching/filtering properties\n- Building property cards\n- Any read-only operations\n- **NEVER for writes**\n\n**Key Fields**:\n```ruby\nid                      # UUID (from RealtyAsset)\nreference               # Property reference\nwebsite_id              # Website/tenant\n\n# All RealtyAsset fields\ncount_bedrooms, count_bathrooms, count_toilets, count_garages\nstreet_address, city, region, country, postal_code\nlatitude, longitude\nprop_type_key, prop_state_key, prop_origin_key\n\n# Computed sale fields\nfor_sale                # Derived from SaleListing.visible + !archived\nsale_listing_id         # ID of associated SaleListing\nprice_sale_current_cents\nprice_sale_current_currency\nsale_reserved, sale_furnished, sale_highlighted\n\n# Computed rental fields\nfor_rent                # Derived from RentalListing.visible + !archived\nfor_rent_short_term, for_rent_long_term\nrental_listing_id       # ID of associated RentalListing\nprice_rental_monthly_current_cents\nprice_rental_monthly_low_season_cents\nprice_rental_monthly_high_season_cents\nrental_reserved, rental_furnished, rental_highlighted\n\n# Computed aggregate fields\nvisible                 # true if for_sale OR for_rent\nhighlighted             # true if sale_highlighted OR rental_highlighted\ncurrency                # Preferred currency\nprice_rental_monthly_for_search_cents  # Lowest rental price\n```\n\n**Read-Only Protection**:\n```ruby\nproperty.readonly?  # Always returns true\nproperty.update!    # Raises ActiveRecord::ReadOnlyRecord\n```\n\n**Backward Compatibility Methods**:\n```ruby\nproperty.title              # From Prop model\nproperty.description        # From Prop model\nproperty.bedrooms           # Alias for count_bedrooms\nproperty.bathrooms          # Alias for count_bathrooms\nproperty.surface_area       # Alias for constructed_area\nproperty.location           # Computed from address fields\nproperty.primary_image_url  # First photo URL\nproperty.prop_photos        # Associated photos\nproperty.features           # Associated features\nproperty.contextual_price   # Price for sale or rent\n```\n\n**Code Example**:\n```ruby\n# List all visible for-sale properties\nvisible_sales = Pwb::Property.visible.for_sale\n\n# Search by bedrooms and price\nresults = Pwb::Property.where(\n  count_bedrooms: 3,\n  website_id: 1\n).where('price_sale_current_cents BETWEEN ? AND ?', 100000000, 500000000)\n\n# Advanced search\nPwb::Property.properties_search(\n  sale_or_rental: 'sale',\n  count_bedrooms: 2,\n  for_sale_price_from: 200000,\n  for_sale_price_till: 600000,\n  property_type: 'apartment'\n)\n\n# Get single property\nproperty = Pwb::Property.find(uuid)\nproperty.title\nproperty.for_sale         # Boolean\nproperty.for_rent         # Boolean\nproperty.price_sale_current  # Money object\nproperty.prop_photos.each { |photo| puts photo.image_url }\n```\n\n---\n\n## The Prop Model (Translations Only)\n\n**What it is**: Legacy model that stores translated titles and descriptions\n\n**When to use**:\n- Accessing or updating `title` and `description` in multiple languages\n- Via Pwb::Prop.find_by(reference: '...')\n\n**Translation Storage**:\n```ruby\n# Stored as JSONB in pwb_props.translations column\nprop.translations\n# => {\"en\"=>{\"title\"=>\"...\", \"description\"=>\"...\"}, \"es\"=>{...}}\n\n# Access via Mobility\nprop.title              # Current locale\nprop.title_en           # English\nprop.title_es           # Spanish\nprop.description_fr     # French description\n\n# Update\nprop.update!(title: \"New Title\", description: \"New Desc\")\n```\n\n**Access from RealtyAsset/Property**:\n```ruby\nasset = Pwb::RealtyAsset.find(uuid)\nasset.prop.title              # Finds Prop via reference\n\nproperty = Pwb::Property.find(uuid)\nproperty.prop.title           # Delegates to RealtyAsset.prop\nproperty.title_es             # Locale-specific accessor\n```\n\n---\n\n## Quick Decision Table\n\n| Scenario | Use This Model | Code Pattern |\n|----------|----------------|---------------|\n| List properties for site | `Pwb::Property` | `Pwb::Property.visible.for_sale` |\n| Search with filters | `Pwb::Property` | `Pwb::Property.properties_search(...)` |\n| Create new property | `Pwb::RealtyAsset` | `Pwb::RealtyAsset.create!(...)` |\n| Edit property location | `Pwb::RealtyAsset` | `asset.update!(street_address: '...')` |\n| Create sale listing | `Pwb::SaleListing` | `asset.sale_listings.create!(...)` |\n| Create rental listing | `Pwb::RentalListing` | `asset.rental_listings.create!(...)` |\n| Update sale price | `Pwb::SaleListing` | `sale.update!(price_sale_current_cents: ...)` |\n| Add photos | `Pwb::RealtyAsset` | `asset.prop_photos.create!(image: ...)` |\n| Translate title | `Pwb::Prop` | `prop.update!(title: '...')` |\n| Is property for sale? | `Pwb::Property` | `property.for_sale` (boolean) |\n| Get all features | `Pwb::Property` | `property.get_features` |\n| Refresh view | (System) | `Pwb::Property.refresh` |\n\n---\n\n## Important Scopes\n\n### Pwb::Property Scopes\n```ruby\nPwb::Property.visible              # visible = true\nPwb::Property.for_sale             # for_sale = true\nPwb::Property.for_rent             # for_rent = true\nPwb::Property.highlighted          # highlighted = true\n\nPwb::Property.property_type('villa')           # by prop_type_key\nPwb::Property.property_state('new')            # by prop_state_key\n\nPwb::Property.for_sale_price_from(100000)\nPwb::Property.for_sale_price_till(500000)\nPwb::Property.for_rent_price_from(2000)\nPwb::Property.for_rent_price_till(5000)\n\nPwb::Property.bedrooms_from(3)\nPwb::Property.bathrooms_from(2)\n```\n\n### Pwb::SaleListing Scopes\n```ruby\nPwb::SaleListing.visible\nPwb::SaleListing.highlighted\nPwb::SaleListing.archived\nPwb::SaleListing.active         # visible && !archived\n```\n\n### Pwb::RentalListing Scopes\n```ruby\nPwb::RentalListing.visible\nPwb::RentalListing.highlighted\nPwb::RentalListing.archived\nPwb::RentalListing.for_rent_short_term\nPwb::RentalListing.for_rent_long_term\nPwb::RentalListing.active       # visible && !archived\n```\n\n---\n\n## Materialized View Refresh\n\n### Automatic (Built-in)\nHappens automatically via `after_commit` callbacks on:\n- `Pwb::RealtyAsset.save` → refreshes view\n- `Pwb::SaleListing.save` → refreshes view\n- `Pwb::RentalListing.save` → refreshes view\n\n### Manual Refresh (if needed)\n```ruby\n# Synchronous refresh (brief lock)\nPwb::Property.refresh\n\n# Concurrent refresh (no lock, requires unique index)\nPwb::Property.refresh(concurrently: true)\n\n# Async refresh (if job defined)\nPwb::Property.refresh_async\n```\n\n---\n\n## Common Patterns\n\n### Create a Complete Property Listing\n```ruby\nActiveRecord::Base.transaction do\n  # 1. Create physical property\n  asset = Pwb::RealtyAsset.create!(\n    reference: 'PROP-001',\n    count_bedrooms: 3,\n    count_bathrooms: 2,\n    street_address: '123 Main St',\n    city: 'Barcelona',\n    country: 'Spain',\n    website_id: 1\n  )\n\n  # 2. Add sale listing\n  asset.sale_listings.create!(\n    visible: true,\n    price_sale_current_cents: 50000000,\n    price_sale_current_currency: 'EUR'\n  )\n\n  # 3. Add rental listing\n  asset.rental_listings.create!(\n    visible: true,\n    for_rent_short_term: true,\n    price_rental_monthly_current_cents: 300000,\n    price_rental_monthly_current_currency: 'EUR'\n  )\n\n  # View auto-refreshes after transaction commits\nend\n```\n\n### Update Property Visibility\n```ruby\n# Hide from sale, keep rental\nasset = Pwb::RealtyAsset.find(uuid)\nasset.sale_listings.first.update!(visible: false)\nasset.rental_listings.first.update!(visible: true)\n\n# Now check from read view\nproperty = Pwb::Property.find(uuid)\nproperty.for_sale   # false\nproperty.for_rent   # true\nproperty.visible    # true (because for_rent is true)\n```\n\n### Search for Properties\n```ruby\n# Find 3-bed villas under 500k EUR for sale\nresults = Pwb::Property.visible.for_sale.where(\n  count_bedrooms: 3,\n  prop_type_key: 'villa',\n  website_id: 1\n).where('price_sale_current_cents <= ?', 50000000)\n\n# Using built-in search\nresults = Pwb::Property.properties_search(\n  sale_or_rental: 'sale',\n  count_bedrooms: 3,\n  for_sale_price_till: 500000,\n  property_type: 'villa'\n)\n```\n\n---\n\n**Last Updated**: December 4, 2025\n