Sebastian Gaul – Sebastians Blog https://sgaul.de Neues aus den Softwareminen Fri, 30 Dec 2022 22:39:00 +0000 de-DE hourly 1 https://wordpress.org/?v=6.1.7 https://sgaul.de/wp-content/uploads/2019/02/cropped-sgaul-2-1-32x32.jpg Sebastian Gaul – Sebastians Blog https://sgaul.de 32 32 Pagy-Gem für Paginierung https://sgaul.de/2022/12/30/pagy-gem-fuer-paginierung/ Fri, 30 Dec 2022 22:39:00 +0000 https://sgaul.de/?p=2970 Pagy-Gem für Paginierung weiterlesen]]> Seit Jahren verwende ich privat wie beruflich will_paginate. Da es stets gute Dienste leistete habe ich dies auch nie hinterfragt und bin nur durch die mangelnde Weiterentwicklung und Limitation-Warnung auf pagy gestoßen. Dieses punktet nicht mit Bescheidenheit, sondern mit den folgenden Aussagen:

~ 40x Faster!
~ 36x Lighter!
~ 35x Simpler!
~ 1,410x More Efficient!

Der Ansatz ist im Vergleich zu will_paginate etwas anders. So fällt auf, dass die erzeugten Active-Record-Relations nicht erweitert werden, sondern die eigentliche Collection und Paginierungs-Metadaten getrennt werden. Mein Interesse ist geweckt.

Pagy-Gem installieren:

bundle add pagy
# Installing pagy 6.0.0

An zentraler Stelle für Controller includen…

class ApplicationController < ActionController::Base
  include Pagy::Backend
end

Und als Helper verfügbar machen.

module ApplicationHelper
  include Pagy::Frontend
end

Inititalizer config/initializers/pagy.rb anlegen um z.B. die Standard-Seitengröße festzulegen, Bootstrap-basierte Helper zu aktivieren und die Sprache festzulegen (Vorlage mit allen Einstellungsmöglichkeiten).

# Instance variables
# See https://ddnexus.github.io/pagy/docs/api/pagy#instance-variables
Pagy::DEFAULT[:page]   = 1                                  # default
Pagy::DEFAULT[:items]  = 10                                 # default
Pagy::DEFAULT[:outset] = 0                                  # default

# Bootstrap extra: Add nav, nav_js and combo_nav_js helpers and templates for Bootstrap pagination
# See https://ddnexus.github.io/pagy/docs/extras/bootstrap
require 'pagy/extras/bootstrap'

# I18n
Pagy::I18n.load(locale: 'de')

# When you are done setting your own default freeze it, so it will not get changed accidentally
Pagy::DEFAULT.freeze

Im Controller dann die entsprechende Methode verwenden, die ein Array mit zwei Werten zurückgibt.

  # GET /users
  def index
    @pagy, @users = pagy(User.all)
  end

Im passenden View dann den die Liste der User und den Paginator anzeigen. Leider ist die Rückgabe von Pagy nicht html_safe und muss entsprechend eingefügt werden (im Beispiel HAML).

= render @users
!= pagy_bootstrap_nav(@pagy)
Ergebnis mit ein bisschen Nacharbeit…

Dies ist natürlich nur ein erster Ansatz. Das Gem kommt mit vielen nativen Erweiterungen und hat vor allem im Bereich Performance so einiges zu bieten. So ließe sich z.B. ein Endless-Scroller ohne aufwendige Eintragszählung umsetzen oder eine Suche mit Searchkick/Elasticsearch realisieren.

]]>
Programm-Verknüpfung erstellen und ändern https://sgaul.de/2020/03/13/programmverknuepfung-erstellen-und-aendern/ Fri, 13 Mar 2020 11:54:31 +0000 https://sgaul.de/?p=2957 Programm-Verknüpfung erstellen und ändern weiterlesen]]> Um in Gnome und Co. .desktop-Dateien für Programme und Skripte anzulegen, die beispielsweise im Home-Verzeichnis liegen, kann man auch heute noch alacarte nutzen. Mir ist als hätten wir damit das Hauptmenü in Gnome 2 konfiguriert, aber es erfüllt auch heute noch seinen Zweck, ohne dass man sich jedes mal mit der Syntax von .desktop-Dateien rumschlagen muss:

sudo apt install alacarte
Android Studio aus dem Hauptmenü starten
]]>
JSON-Warnungen in Rails-Projekten https://sgaul.de/2020/02/21/json-warnungen-in-rails-projekten/ Fri, 21 Feb 2020 09:56:59 +0000 https://sgaul.de/?p=2954 Immer wieder tauchen bei mir folgende Warnungen auf:

.../lib/ruby/2.5.0/json/version.rb:4: warning: already initialized constant JSON::VERSION
.../gems/json-2.3.0/lib/json/version.rb:4: warning: previous definition of VERSION was here
.../lib/ruby/2.5.0/json/version.rb:5: warning: already initialized constant JSON::VERSION_ARRAY
.../gems/json-2.3.0/lib/json/version.rb:5: warning: previous definition of VERSION_ARRAY was here

Eine schnelle (vermutlich nicht dauerhafte) Lösung ist bundle clean:

bundle clean --force
bundle

]]>
Code-Zeile in VSCode duplizieren https://sgaul.de/2019/06/17/code-zeile-in-vscode-duplizieren/ Mon, 17 Jun 2019 12:24:05 +0000 https://sgaul.de/?p=2948 Code-Zeile in VSCode duplizieren weiterlesen]]> Bei der Umstellung von Sublime auf Virtual Studio Code hatte ich lange Probleme mit dem duplizieren der aktuellen Zeile, vor allem weil einige offizielle Shortcuts mit Gnome kollidieren.

Während ich geneigt war Sublimes Strg+Shift+d zu konfigurieren, fiel mir ein interessantes Feature beim Standard-Copy-and-Paste auf: Ist der Cursor ohne Markierung in der Zeile, wird eben diese vollständig in die Zwischenablage kopiert. Fügt man diese nun wieder ein, wir diese sauer darunter eingefügt. Ein sehr eleganter Ansatz mit Standardmitteln, an den ich mich sicher schnell gewöhnen werde.

]]>
Vue-Code-Formatierung in VSCode https://sgaul.de/2019/06/01/vue-code-formatierung-in-vscode/ Sat, 01 Jun 2019 18:13:19 +0000 https://sgaul.de/?p=2940 Vue-Code-Formatierung in VSCode weiterlesen]]> Mit .vue-Dateien und Visiual Studio Code hatte ich oft Probleme, nun aber endlich eine Konfiguration gefunden, die sowohl Javascript- wie auch HTML-Segmente automatisch überprüft und formatiert. Als Plugins kommen dabei ESLint und Vetur zum Einsatz, meine settings.json sieht folgendermaßen aus:

{
  "git.autofetch": true,
  "eslint.autoFixOnSave": true,
  "eslint.validate": [
    {
      "language": "vue",
      "autoFix": true
    },
    {
      "language": "html",
      "autoFix": true
    },
    {
      "language": "javascript",
      "autoFix": true
    },
  ]
}

Problem ist nun, dass das HTML nicht eingerückt wird, wenn ich die Vue-Standardeinstellungen verwende. Um dies zu beheben, wechsele ich in der .eslintrc im Extends-Teil von plugin:vue/essential auf plugin:vue/recommended:

module.exports = {
  root: true,
  env: {
    node: true
  },
  'extends': [
    'plugin:vue/recommended',
    'standard'
  ],
  rules: {
    'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
  },
  parserOptions: {
    parser: 'babel-eslint'
  }
}

Ggf. müssen hier noch Pakete installiert werden. Die Extension standard sorgt dafür, dass simplere Javascript-Regeln wie Einrückungen automatisch vorgenommen werden.

Dies ist ein sehr viel strengerer Linter, der viele Fehlerquellen ausschließt, aber auch einige subjektive Entscheidungen trifft. Dass HTML-Tags immer mehrzeilig werden, sobald mehr als ein Attribut definiert wird, finde ich gewöhnungsbedürftig. Dass hingegen die Standardeigenschaften einer Vue-Komponente immer konsistent sortiert werden, weiß zu gefallen.

]]>
Youtube-dl mit PIP aktuell halten https://sgaul.de/2019/05/01/youtube-dl-mit-pip-aktuell-halten/ Wed, 01 May 2019 07:23:02 +0000 https://sgaul.de/?p=2937 Das nette Download-Tool youtube-dl muss sich schnell den aktuellen Gegebenheiten von Google und Co. anpassen. Die Variante aus den Paketquellen ist daher nicht zu empfehlen. Glücklicherweise steht das Tool über PIP zur Verfügung:

sudo apt install pip
pip install youtube-dl

Kommt es später wieder zu einem Fehler, aktualisiert folgender Befehl das Programm:

pip install youtube-dl --ugprade

]]>
Vollständige Dependent-Einstellungen in Rails-Models testen https://sgaul.de/2019/03/04/vollstaendige-dependent-einstellungen-in-rails-models-testen/ Mon, 04 Mar 2019 15:38:13 +0000 https://sgaul.de/?p=2933 Vollständige Dependent-Einstellungen in Rails-Models testen weiterlesen]]> Ein selten, aber leider regelmäßig wiederkehrendes Problem sind Fremdschlüsselbeziehungen beim Löschen. In Rails muss auf Seite des Schlüsselziels definiert werden, ob ein Fremdschlüssel auf null gesetzt werden darf oder ob das ganze Model gelöscht werden muss. Vergisst man diese Konfiguration wirft die Datenbank beim Löschen einen Fehler. Um dies zu vermeiden möchte ich alle Fremdschlüssel, die auf eine Model-Tabelle zeigen, überprüfen, ob sie entsprechend konfiguriert sind. Hierfür verwende ich ein shared Example in Rspec.

Fremdschlüssel finden

Die Schlüssel für das beschriebene Model lassen sich in zumindest mit Postgres folgendermaßen auflisten:

def foreign_keys(to_table)
  ActiveRecord::Base.connection.tables.map do |table_name|
    ActiveRecord::Base.connection.foreign_keys(table_name).select{ |index| index.to_table == to_table }
  end.flatten
end

Konfigurierte Assoziationen finden

Die Assoziationen eines Models finden wir mit folgendem Ansatz. Wir ignorieren Assoziationen mit Scopes, da hier nicht ohne weiteres festgestellt werden kann, ob diese alle möglichen Verbindungen abdecken. Auch beschränken wir uns auf Has-many- und Has-one-Beziehungen, da diese zumindest aktuell die einzig relevanten sind. Sollte hier etwas fehlen ist dies auch nicht weiter tragisch, da der Test im Zweifel lieber zu viel als zu wenig bemängeln soll.

def safe_associations(target_class)
  associations =
    target_class.reflect_on_all_associations(:has_many) +
    target_class.reflect_on_all_associations(:has_one)
  associations.select{ |assoc| assoc.options[:dependent].in?(%i{destroy nullify}) && assoc.scope.nil? }
end

Shared Example für verdächtige Keys

Nun können die gültigen Konfigurationen von den Fremdschlüsseln abziehen. Nur wenn die Differenz leer ist, sind alle Schlüssel konfiguriert. Da wir bei Scopes etwas übervorsichtig sind, erlauben wir aber auch eine ignore-Option, um bestimmte Fremdschlüssel zu ignorieren:

shared_examples 'a dependent model' do  |options = {}|
  it 'has a dependent option for has_* associations' do
    foreign_keys = foreign_keys(described_class.table_name)
    associations = safe_associations(described_class)
    ignore = options[:ignore] || []

    suspicious_keys = 
      foreign_keys.map{ |key| "#{key.from_table}.#{key.column}" } -
      associations.map{ |assoc| "#{assoc.klass.table_name}.#{assoc.foreign_key}" } -
      ignore

    expect(suspicious_keys).to be_empty
  end

  # ...
end

Nun noch die Fehlermeldungen modifizieren und etwas Doku ergänzen und schon kann das Example in Model-Specs eingebunden werden:

describe BookWithChapters do
  it_behaves_like 'a dependent model'
end
]]>
Vue 2.6 mit neuer Syntax für Scope-Context https://sgaul.de/2019/02/10/vue-2-6-mit-neuer-syntax-fuer-scope-context/ Sun, 10 Feb 2019 17:09:42 +0000 https://sgaul.de/?p=2929 Die bisherige Syntax für Daten aus der Kindkomponente wirkte immer etwas gedoppelt, wenn der Scope einen Namen hatte:

<todo-list v-bind:todos="todos">
  <template slot="label" slot-scope="{ todo }">
    {{ todo.text }}
  </template>
</todo-list>

Mit Version 2.6 kann dies nun in einem Attribut vereint werden:

<todo-list v-bind:todos="todos">
  <template slot:label="{ todo }">
    {{ todo.text }}
  </template>
</todo-list>

Dank den neuen Dynamic Directive Arguments ist es auch kein Problem, wenn der Slot-Name dynamisch bestimmt werden muss:

<todo-list v-bind:todos="todos">
  <template slot:[slotName]="{ todo }">
    {{ todo.text }}
  </template>
</todo-list>
]]>
Datenbeschaffung in Vue-Router-Views vereinfachen https://sgaul.de/2019/02/09/datenbeschaffung-in-vue-router-views-vereinfachen/ Sat, 09 Feb 2019 20:45:55 +0000 https://sgaul.de/?p=2911 Datenbeschaffung in Vue-Router-Views vereinfachen weiterlesen]]> created und watch

Ein typisches Vue-Router-Szenario: Daten müssen initialisiert und bei Routen-Updates zurückgesetzt und geladen werden, da die Komponente nicht neu erzeugt sondern nur intern aktualisiert wird.

data () {
  return {
    user: null,
    loading: true
  }
},
created () {
  this.fetchData()
},
watch: {
  '$route': 'fetchData'
},
methods: {
  async fetchData () {
    this.loading = true
    this.user = await this.$network.getUser(this.$route.params.userId)
    this.loading = false
  }
}

Watchers und immediate: true

Die Dopplung in created und watch kann durch einen Watcher mit der Eigenschaft immediate eliminiert werden:

watch: {
  '$route': {
    handler: 'fetchData',
    immediate: true
  }
}

Neuladen statt Updaten

Zudem kann man ein Neu-Rendern der Komponente bei Routen-Änderung erzwingen, um die Unübersichtlichkeit und das Fehlerpotential eines Updates zu vermeiden.

data () {
  return {
    user: null,
    loading: true
  }
},
async created () {
  this.loading = true
  this.user = await this.$network.getUser(this.$route.params.userId)
  this.loading = false
}

Hierfür muss im Template, welches den Router-View einfügt, einfach ein Key übergeben werden, der idealerweise die gesamte URL darstellt:

<router-view :key="$route.fullPath" />
]]>
Verwischte Font-Awesome-Icons in Phantom JS https://sgaul.de/2016/07/07/verwischte-font-awesome-icons-in-phantom-js/ Thu, 07 Jul 2016 15:48:15 +0000 https://sgaul.de/?p=2874 Verwischte Font-Awesome-Icons in Phantom JS weiterlesen]]> Wir reichern unsere auf Capybara und Phantom JS basierenden Feature-Tests gerne mit Screenshots an. Hierbei kam es regelmäßig zu einem Problem mit Font Awesome, wodurch der automatische Abgleich fehl schlug. Bei einzelnen Specs tritt das Problem nicht auf, erst wenn mehrere Tests mit unterschiedlichen Seiten abgelichtet werden zeigte sich folgendes Phänomen:

font-awesome-phantomjs

Alle Icons, wie hier das Such-Icon rechts, wurden wie links zu sehen verzerrt. Den Lösungsansatz brachte dieser Eintrag auf Stackoverflow: Ist die Schriftart nativ auf dem System vorhanden, tritt die Verzerrung nicht mehr auf. Unter Ubuntu ist das schnell gemacht:

sudo apt-get install fonts-font-awesome
sudo fc-cache -f -v
]]>