Sebastians Blog https://sgaul.de Neues aus den Softwareminen... Wed, 01 May 2019 07:23:02 +0000 de-DE hourly 1 https://wordpress.org/?v=5.1.1 https://sgaul.de/wp-content/uploads/2019/02/cropped-sgaul-2-1-32x32.jpg Sebastians Blog https://sgaul.de 32 32 Youtube-dl mit PIP aktuell halten https://sgaul.de/2019/05/01/youtube-dl-mit-pip-aktuell-halten/ https://sgaul.de/2019/05/01/youtube-dl-mit-pip-aktuell-halten/#respond 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

]]>
https://sgaul.de/2019/05/01/youtube-dl-mit-pip-aktuell-halten/feed/ 0
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 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 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 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
]]>
Herausfinden, wo eine Methode definiert wurde https://sgaul.de/2016/04/26/herausfinden-wo-eine-methode-definiert-wurde/ Tue, 26 Apr 2016 10:37:57 +0000 https://sgaul.de/?p=2870 In manchen Situationen ist es alles andere als offensichtlich, welche Klasse oder welches Modul eine Methode bereitstellt. Wer implementiert die Get-Methode für Rspec-Controller-Tests? Gerade in solch zusammengewürfelten Umgebungen ist das nur schwer nachvollziehbar. Ruby hilft hierbei: Die Metamethode method gibt Auskunft, woher eine Methodendefinition stammt:

require "rails_helper"

RSpec.describe MyController do
  it do
    puts method(:get)
  end
end

# => #<Method: RSpec::ExampleGroups::MyController(ActionController::TestCase::Behavior)#get>

Das Beispiel zeigt nicht nur die Klasse, sondern auch das includete Modul, welches für die Methode verantwortlich ist. Dies hilft beim Googeln oder man verwendet Ri:

ri ActionController::TestCase::Behavior#get

 

]]>
Factory-Girl-Daten im Development-Modus https://sgaul.de/2016/03/30/factory-girl-daten-im-development-modus/ Wed, 30 Mar 2016 12:20:55 +0000 https://sgaul.de/?p=2867 Testdaten aus Factory-Girl lassen sich in einer Rails-Konsole auch für den Development-Modus (und entsprechend jeden anderen Modus) bereitstellen:

require 'factory_girl'; FactoryGirl.find_definitions
FactoryGirl.create :my_model
]]>
Rails-Konfiguration in Engine auslagern https://sgaul.de/2016/01/30/rails-konfiguration-in-engine-auslagern/ Sat, 30 Jan 2016 09:01:17 +0000 https://sgaul.de/?p=2854 Für die meisten Rails-Projekte verwende ich die gleichen Gems mit ähnlichen Konfigurationen. Um den Projektstart und den Update-Prozess zu vereinheitlichen, möchte ich eine Engine, die diese Abhängigkeiten und Konfigurationen übernimmt.

Für den einfacheren Einstieg erzeuge ich innerhalb einer bestehenden Rails-App eine neue Engine:

rails plugin new m3 --full

Diese wird zu Testzwecken im Gemfile des Elternprojekts verlinkt:

gem 'm3', path: 'm3'

Gem-Abhängigkeiten im Gemspec-File

Im Gemspec-File m3.gemspec steht bereits eine Rails-Abhängigkeit, die ich um die gewünschten Gems erweitere:

s.add_dependency "rails", "~> 4.2.4"
s.add_dependency "simple_form", "~> 3.2.0"
s.add_dependency "draper", "~> 2.1.0"
s.add_dependency "cancancan", "~> 1.13.1"

Damit diese beim Start der Elternapplikation zur Verfügung stehen, müssen diese vom Gem geladen werden. Dies sollte in der Engine-Datei gemacht werden:

# <gem>/lib/<gem>/engine.rb

require 'cancan'
require 'draper'
require 'simple_form'

module M3
  class Engine < ::Rails::Engine
    # ...

Die Elternapplikation kann nun wie gewohnt auf die Gems zugreifen.

Initializer

Erweiterte Konfigurationen für Simple-Form werden in zugehörigen Initializern festgelegt, die man ebenfalls einfach in der Engine anlegen kann:

# <gem>/config/initializers/simple_form.rb

SimpleForm.setup do |config|
  # ...
# <gem>/config/initializers/simple_form_bootstrap.rb

SimpleForm.setup do |config|
 # ...

Controller-Erweiterungen

Grundlegende Controller-Konfigurationen habe ich in einen Application-Controller mit Namespace gepackt:

# <gem>/app/controllers/m3/application_controller.rb

class M3::ApplicationController < ActionController::Base
  # ...

Hier kann man auch wie gewohnt Concerns verwenden, diese werden ebenfalls automatisch gefunden.

Die fertigen Konfigurationen können dann vom Application-Controller der Elternapplikation verwendet werden:

# <app>/app/controllers/application_controller.rb

class ApplicationController < M3::ApplicationController
  # ...

Module und Klassen aus lib bereitstellen

Auch in einer Engine landet unter lib so einiges, was nicht vom Autoloading abgedeckt wird. Diese könnten wie Gem-Abhängigkeiten explizit in der Engine-Datei geladen werden. Diese Lösung ist jedoch ein unschöner Workaround und sei hier nur für den Notfall aufgeführt.

# <gem>/lib/<gem>/engine.rb
# Workaround
require_dependency File.expand_path("../../../app/models/m3", __FILE__)
require_dependency File.expand_path("../../../lib/m3/router", __FILE__)

Wie man sieht lade ich hier auch den Namespace M3, da Autoloading an dieser Stelle noch nicht funktioniert und die Lib-Klasse Routes entweder aufgrund des fehlenden Namespaces knallt oder diesen als leeres Modul anlegt. Letzteres kann zu einem schwer zu findenden Fehler führen, da die Modul-Datei im Verzeichnis app dann gar nicht mehr geladen wird.

Wie angedeutet ist dieser Ansatz wenig elegant. Die Require-Statements sollten besser zu einem Zeitpunkt ausgeführt werden, an dem Autoloading bereits zur Verfügung steht. Dann passiert vieles automatisch und übrige Requires können ohne File-Path-Expansions notiert werden:

# <gem>/lib/m3/router.rb

require_dependency 'm3'

class M3::Router
  # ...
# <gem>/config/routes.rb

require_dependency 'm3/router'

Rails.application.routes.draw do
  M3::Router.draw_routes(self)
end
]]>
Bundler überspringt Gems mit Todo-Beschreibung https://sgaul.de/2015/12/12/bundler-ueberspringt-gems-mit-todo-beschreibung/ https://sgaul.de/2015/12/12/bundler-ueberspringt-gems-mit-todo-beschreibung/#comments Sat, 12 Dec 2015 13:43:56 +0000 https://sgaul.de/?p=2851 Komisches Verhalten von Bundler, welches bei mir vorher nie aufkam:

$ bundle install
...
Source does not contain any versions of 'gem (>= 0) ruby'

Das Problem scheint hier tatsächlich zu sein, dass die Gem-Spezifikation noch Todos enthält:

Gem::Specification.new do |s|
 # ...
 s.summary = "TODO: Summary of Gem."
 s.description = "TODO: Description of Gem."
end

Entferne ich diese, so läuft die Installation durch.

Ironischerweise warnt Bundler sogar, dass das Gem Todos oder Fixmes in der Spezifikation enthält. Dass es das Verzeichnis deshalb nicht ordnungsgemäß ausliest erschließt sich aber nicht. Hier wäre eine Fehlermeldung statt der Warnung geeigneter. Möglicherweise ist es auch nur ein Bug, eine fehlende Beschreibung rechtfertigt kein Ignorieren eines Gems.

]]>
https://sgaul.de/2015/12/12/bundler-ueberspringt-gems-mit-todo-beschreibung/feed/ 2
Hacker-Serie Mr. Robot https://sgaul.de/2015/11/20/hacker-serie-mr-robot/ Fri, 20 Nov 2015 14:42:55 +0000 https://sgaul.de/?p=2842 Mir fällt es nicht leicht, mich an neue Serien zu gewöhnen. Die ersten Folgen muss ich mich meist zwingen, um den Einstieg zu finden. Das ist auch bei Mr. Robot nicht anders, die es jetzt bei Amazon Prime gibt. Ein paar Sachen haben mich dann aber doch positiv gestimmt. „Ich habe kein Facebook.“ und „Ich hasse Facebook.“, ein erstes Kennenlernen bei dem die Gnome-oder-KDE-Frage geklärt wird und recht deutliche Kritik an der gesellschaftlichen Überbewertung von Steve Jobs – nette Details die mich durchaus ansprechen. Ich bin gespannt was die ersten Folgen noch so bringen…

]]>