Ruby on Rails Modernisierung für Schweizer Unternehmen

Seit 2009 spezialisiert auf Ruby on Rails. Schweizer Marktpartner seit 2012. Wir modernisieren Legacy-Rails-Anwendungen statt sie neu zu schreiben.

Projekt unverbindlich anfragen

Wer wir sind

USEO ist ein auf Ruby on Rails spezialisiertes Softwarehaus mit über 15 Jahren Erfahrung. Wir konzentrieren uns auf das, was die meisten Agenturen vermeiden: die Modernisierung bestehender, geschäftskritischer Rails-Anwendungen. Statt riskanter Komplett-Neuentwicklung stabilisieren, refaktorieren und upgraden wir Legacy-Systeme schrittweise.

Unsere Verbindung zur Schweiz ist langjährig und konkret: Seit 2012 betreuen wir die führenden Schweizer Karriereplattformen Yousty.ch und Professional.ch. Im Jahr 2025 haben wir für das Schweizer Startup Versus (getversus.app) einen mobilen MVP für iOS und Android entwickelt. Wir kommunizieren auf Englisch und Deutsch und arbeiten als verlängerte Werkbank Schweizer Teams.

Inhaltlich konzentrieren wir uns auf vier Bereiche: Legacy-Rails-Modernisierung, Code-Audits, dedizierte Senior-Teams und Fractional-CTO-Beratung. Wir nehmen keine Junior-Entwickler in Kundenprojekte. Alle Engineering-Entscheidungen werden von Senior-Engineers mit Schweiz-Erfahrung getroffen.

Schweizer Datenschutz (nDSG) & Ruby on Rails

Das revidierte Schweizer Datenschutzgesetz (nDSG) hat technische Auswirkungen auf jede Rails-Anwendung, die Personendaten verarbeitet. Drei Bereiche stehen besonders im Fokus: Audit-Protokolle, Einwilligungsmanagement und Datenportabilität. Rails bietet für jeden Bereich saubere Implementierungsmuster.

1. Audit-Protokolle für Datenzugriffe

Das nDSG verlangt nachvollziehbare Protokolle darüber, welche Personendaten wann und durch wen verändert wurden. In Rails lösen wir dies typischerweise mit dem audited-Gem, das auf ActiveRecord-Callbacks aufbaut. Es protokolliert jede Änderung, den auslösenden Benutzer und einen Zeitstempel ohne Boilerplate-Code in jedem Modell.

Synthetic Engineering Context: Integration von audited

# Gemfile
gem 'audited'

# db/migrate/_install_audited.rb (vom Generator erzeugt)
rails generate audited:install
rails db:migrate

# app/models/user_profile.rb
class UserProfile < ApplicationRecord
  audited only: [:email, :phone, :address]
  # Nur sensible Felder protokollieren, nicht alle Spalten
end

# Abfrage des Audit-Trails
profile = UserProfile.find(42)
profile.audits.last.audited_changes
# => {"email" => ["old@example.ch", "new@example.ch"]}

profile.audits.last.user
# => #

2. Einwilligungsmanagement (Consent Management)

Das nDSG erfordert eine granulare, jederzeit widerrufbare Einwilligung pro Verarbeitungszweck. Ein einzelnes accepted_terms_at-Feld auf dem User-Modell reicht nicht aus. Wir empfehlen ein separates Consent-Modell mit Verarbeitungszweck, Status und Widerrufszeitpunkt.

Synthetic Engineering Context: Consent-Modell

# db/migrate/_create_consents.rb
create_table :consents do |t|
  t.references :user, null: false, foreign_key: true
  t.string :purpose, null: false # 'marketing', 'analytics', 'newsletter'
  t.boolean :granted, default: false, null: false
  t.datetime :granted_at
  t.datetime :revoked_at
  t.string :source # 'signup_form', 'preferences_page', 'cookie_banner'
  t.timestamps
end

# app/models/user.rb
class User < ApplicationRecord
  has_many :consents

  def consented_to?(purpose)
    consents.where(purpose: purpose, granted: true, revoked_at: nil).exists?
  end
end

# Vor jeder Marketing-Aktion prüfen
if user.consented_to?('marketing')
  MarketingMailer.with(user: user).campaign.deliver_later
end

3. Datenportabilität

Das nDSG gibt Personen das Recht, ihre Daten in einem strukturierten, maschinenlesbaren Format zu erhalten. Diese Exporte sollten asynchron erfolgen, da sie Datenbankabfragen über mehrere Modelle hinweg auslösen.

Synthetic Engineering Context: Asynchroner Datenexport

# app/services/data_portability_export.rb
class DataPortabilityExport
  def initialize(user)
    @user = user
  end

  def to_json
    {
      profile: profile_data,
      consents: consents_data,
      activities: activities_data,
      exported_at: Time.current.iso8601
    }.to_json
  end

  private

  def profile_data
    @user.as_json(only: [:email, :full_name, :created_at])
  end

  def consents_data
    @user.consents.as_json(only: [:purpose, :granted, :granted_at, :revoked_at])
  end

  def activities_data
    @user.activities.last(1000).as_json
  end
end

# app/jobs/data_export_job.rb
class DataExportJob < ApplicationJob
  def perform(user_id)
    user = User.find(user_id)
    export = DataPortabilityExport.new(user).to_json
    DataExportMailer.with(user: user, export: export).ready.deliver_later
  end
end

Rails Modernisierung in 3 Phasen

Eine erfolgreiche Modernisierung ist kein Big-Bang-Rewrite, sondern ein schrittweiser, kontrollierter Prozess. Unser Vorgehen unterteilt die Arbeit in drei klar abgegrenzte Phasen, die jede für sich messbaren Geschäftswert liefern.

Phase 1: Audit & Stabilisierung

Bevor wir eine einzige Zeile Code anfassen, schaffen wir Sicherheit. Wir containerisieren die bestehende Anwendung mit Docker, um eine reproduzierbare Entwicklungsumgebung herzustellen. Wir messen die Testabdeckung mit SimpleCov und identifizieren die kritischsten Code-Pfade ohne Testschutz. Wir profilieren die Anwendung in Produktion (Scout APM, rack-mini-profiler), um die echten Performance-Bottlenecks zu finden, nicht die vermuteten.

Synthetic Engineering Context: SimpleCov-Schwelle in CI

# spec/spec_helper.rb
require 'simplecov'
SimpleCov.start 'rails' do
  add_group 'Services', 'app/services'
  add_group 'Background Jobs', 'app/jobs'
  minimum_coverage 70 # Anfangsschwelle für Legacy-Apps
  minimum_coverage_by_file 50
end

# .github/workflows/ci.yml
- name: Run tests with coverage gate
  run: |
    bundle exec rspec
    # SimpleCov gibt Exit-Code 1 zurück, wenn Schwelle unterschritten

Phase 2: Inkrementelle Upgrades

Wir überspringen niemals Rails-Major-Versionen. Stattdessen nutzen wir das next_rails-Gem für einen Dual-Boot-Ansatz: Die Anwendung läuft gleichzeitig unter der alten und neuen Rails-Version. Tests werden gegen beide Versionen ausgeführt. Deprecation Warnings werden behoben, bevor die Migration finalisiert wird. So upgraden wir typischerweise 4.2 → 5.2 → 6.1 → 7.x in separaten, jederzeit zurückrollbaren Schritten.

Synthetic Engineering Context: Dual-Boot mit next_rails

# Gemfile
gem 'next_rails', group: :development

if ENV['NEXT_RAILS']
  gem 'rails', '7.0.8'
else
  gem 'rails', '6.1.7'
end

# Bash: Beide Versionen installieren
$ bundle install
$ NEXT_RAILS=1 bundle install

# Tests gegen beide Versionen
$ bundle exec rspec
$ NEXT_RAILS=1 bundle exec rspec

# Production läuft weiter mit alter Version,
# Staging mit neuer Version. Wechsel erst nach grünem CI.

Phase 3: Refactoring & Architekturverbesserung

Nach erfolgreichem Upgrade beginnt die nachhaltige Verbesserung. Fat Controllers werden in Service Objects extrahiert. N+1-Queries werden mit includes, preload und Counter-Caches eliminiert. Veraltete Background-Job-Frameworks (DelayedJob) werden durch Sidekiq ersetzt. Jede Refactoring-Maßnahme wird durch Tests abgesichert, die bereits in Phase 1 entstanden sind.

Synthetic Engineering Context: Service Object Pattern

# Vorher: Fat Controller (45 Zeilen create-Action)
class OrdersController < ApplicationController
  def create
    # 45 Zeilen mit Validierung, Stripe-Aufruf, E-Mail-Versand,
    # Inventory-Update, Loyalty-Punkten und Slack-Notification
  end
end

# Nachher: Service Object kapselt Geschäftslogik
# app/services/orders/place_order.rb
module Orders
  class PlaceOrder
    Result = Struct.new(:success?, :order, :errors, keyword_init: true)

    def initialize(user:, params:)
      @user = user
      @params = params
    end

    def call
      ActiveRecord::Base.transaction do
        order = build_order
        return failure(order) unless order.save
        charge_payment(order)
        notify_warehouse(order)
        award_loyalty_points(order)
        Result.new(success?: true, order: order)
      end
    rescue Stripe::CardError => e
      Result.new(success?: false, errors: [e.message])
    end

    # ... private methods
  end
end

# Controller schrumpft auf 7 Zeilen
class OrdersController < ApplicationController
  def create
    result = Orders::PlaceOrder.new(user: current_user, params: order_params).call
    if result.success?
      redirect_to result.order, notice: 'Bestellung erfolgreich'
    else
      render :new, status: :unprocessable_entity
    end
  end
end

Schweizer Erfolgsgeschichten

Theorie ist gut, Praxis ist besser. Unsere langjährigen Partnerschaften mit Schweizer Unternehmen zeigen unseren Modernisierungsansatz in Aktion.

Yousty & Professional.ch: Schweizer Karriereplattformen seit 2012

Yousty.ch ist die grösste Lehrstellenplattform der Schweiz, Professional.ch verbindet junge Berufseinsteiger mit Arbeitgebern. Beide Plattformen gehören zur gleichen Schweizer Unternehmensgruppe. Seit 2012 unterstützen wir die Plattformen mit einem dedizierten Senior-Rails-Team aus Polen, das eng mit dem Schweizer Team zusammenarbeitet.

Konkrete Ergebnisse aus der bisherigen Zusammenarbeit: 33% Einsparung bei den Lohnkosten pro Jahr gegenüber lokaler Anstellung in der Schweiz, 15% Einsparung bei der Administration und 90% Einsparung bei Personalbeschaffung. Die durchschnittliche Verweildauer der Team-Mitglieder liegt 40% über dem IT-Branchendurchschnitt.

"We have been working with USEO since 2012. I have absolute trust in them in terms of conditions, reliability, and quality."

— Urs Casty, CEO and Founder at Professional.ch (LinkedIn)

"We've been working with USEO for over 14 years. The cooperation is always professional, straight forward and fun."

— Christian Hagmann, COO & Digital Strategist at Yousty AG (LinkedIn)
Vollständige Yousty Case Study lesen →

Versus (getversus.app): Mobile MVP für ein Schweizer Startup, 2025

Versus ist eine Mobile-App für wettbewerbsbasierte soziale Vernetzung. Der Schweizer Gründer brauchte einen Partner, der den MVP schnell entwickeln, Beta-Versionen für interne Tests bereitstellen und finale iOS- und Android-Releases liefern konnte. Wir haben das gesamte Projekt zwischen März und Mai 2025 umgesetzt: Design, Entwicklung, Beta-Releases und Launch auf beiden Plattformen.

Geliefert wurden: ein vollständiger MVP mit sozialen Features, Onboarding-Flow, Einlade-Prozess und Scoring-System. Alle Meilensteine wurden termingerecht oder vorzeitig abgeschlossen. Die Zusammenarbeit nach dem Launch läuft weiter.

"Most impressed by the quality and speed of the work as well as their commitment to good customer service and relationship."

— James Raper, Co-Founder & Head of Product at Versus (LinkedIn)
Vollständige Versus Case Study lesen →

Bereit für den nächsten Schritt?

Lassen Sie uns gemeinsam analysieren, wie wir Ihre Rails-Anwendung modernisieren und für die Zukunft rüsten können. Keine Verkaufsgespräche, sondern ein technisches Gespräch über Ihre konkrete Situation.

Projekt starten