rails-contact

rails-contact is a mountable Rails engine for Google-Contacts-style contact management.

It provides:

  • rich contact profile fields
  • multi-value contact methods (emails/phones/addresses/websites/events)
  • labels/tags
  • dynamic add/remove nested rows
  • Elasticsearch-backed search with DB fallback
  • Google sync scaffolding
  • merge and bulk-delete operations
  • Devise-style override generators

Quickstart

1) Add gem

gem "rails-contact", "~> 0.1.4"
bundle install

2) Install and generate schema

rails generate rails:contact:install
rails generate rails:contact:contact Contact
rails db:migrate

3) Mount engine routes

rails_contact_for :contacts

or explicit mount:

mount Rails::Contact::Engine => "/contacts", as: "rails_contact"

rails_contact_for :contact is auto-normalized to /contacts.

4) Visit UI

  • /contacts
  • /contacts/new
  • /contacts/:id

Host layout and importmap

By default the engine uses your host app layout named application so contacts pages match the rest of your UI (including Turbo and importmap). Styles for engine markup are included per page via stylesheet_link_tag, and nested “add row” / bulk-selection behavior uses small inline scripts so you do not need to pin gem JavaScript in the host importmap.

To use the engine’s standalone layout and bundled javascript_include_tag "rails/contact/application" instead:

Rails::Contact.configure do |config|
  config.inherit_host_layout = false
end

Generators

rails generate rails:contact:install
rails generate rails:contact:contact Contact
rails generate rails:contact:views
rails generate rails:contact:controllers
  • views copies templates so host apps can customize UI.
  • controllers copies an override-ready contacts controller.

Feature map

Core profile fields

  • Prefix, first, middle, last, suffix, nickname
  • Company, job title, department
  • Labels (comma-separated input)
  • Notes and metadata
  • Starred flag
  • Photo URL

Multi-value sections (dynamic)

  • Emails
  • Phones
  • Addresses
  • Websites
  • Events (birthday/custom)

Rows can be added/removed dynamically in form UI.

  • Search by name/email/phone/company/job title/labels
  • Filter by city/region/sync/starred
  • Sort by recent updates

Actions

  • Bulk delete selected contacts
  • Merge source contact into target contact

Configuration

Initializer: config/initializers/rails_contact.rb

Rails::Contact.configure do |config|
  config.search_backend = :elasticsearch
  config.elasticsearch_url = ENV.fetch("ELASTICSEARCH_URL", "http://127.0.0.1:9200")
  config.google_sync_enabled = false
  config.google_max_contacts = 25_000
  config.rolling_window_sort = :updated_at
  # Optional: appended to familyName in Google People API payloads only (blank = unchanged).
  # config.google_contact_family_name_suffix = "_by_vendor"
  config.default_per_page = 25
end

Google sync UI: gem default vs host override

Recommendation: keep the default panel in the gem so every app gets working buttons when google_sync_enabled is true. You avoid copy-paste and stay aligned with new endpoints.

  • The index template renders rails/contact/_google_sync_panel when google_sync_ui_on_index is true (default).
  • Customize without forking the engine: add app/views/rails/contact/_google_sync_panel.html.erb in the host app; Rails resolves that file instead of the gem’s partial.
  • Hide the default panel: set config.google_sync_ui_on_index = false and render your own UI anywhere (same POST targets below).
  • Custom index only: override rails/contact/index and <%= render "rails/contact/google_sync_panel" %> wherever it fits (e.g. after a CSV upload section).

Endpoints (used by the default partial):

  • google_sync_rolling_window_contacts_pathGoogleSyncJob (rolling window re-sync).
  • google_sync_unsynced_contacts_pathGoogleSyncUnsyncedJob (contacts with no google_resource_name).

ContactsController#index sets @google_contacts_pending_sync when sync is enabled.


Rake tasks

rake rails_contact:reindex
rake rails_contact:sync_google

Testing and coverage

RSpec is the primary framework.

bundle exec rspec

Coverage is enforced with SimpleCov:

  • 100% line coverage target
  • 100% branch coverage target
  • CI fails below thresholds

Release

bundle exec rake build
bundle exec rake release

RubyGems MFA is required for push.


Extended docs

  • docs/parity_matrix.md
  • docs/product_decisions.md
  • docs/roadmap.md
  • docs/migration_guide.md