OSBN – Sebastians Blog https://sgaul.de Neues aus den Softwareminen Sat, 09 Feb 2019 21:22:22 +0000 de-DE hourly 1 https://wordpress.org/?v=6.1.1 https://sgaul.de/wp-content/uploads/2019/02/cropped-sgaul-2-1-32x32.jpg OSBN – Sebastians Blog https://sgaul.de 32 32 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 Herausfinden, wo eine Methode definiert wurde weiterlesen]]> 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 Rails-Konfiguration in Engine auslagern weiterlesen]]> 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 Bundler überspringt Gems mit Todo-Beschreibung weiterlesen]]> 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
Ubuntu-Update kränkelt nach Owncloud-Installation https://sgaul.de/2015/11/07/ubuntu-update-kraenkelt-nach-owncloud-installation/ https://sgaul.de/2015/11/07/ubuntu-update-kraenkelt-nach-owncloud-installation/#comments Sat, 07 Nov 2015 11:25:34 +0000 https://sgaul.de/?p=2839 Ubuntu-Update kränkelt nach Owncloud-Installation weiterlesen]]> Nach der Installation des Owncloud-Clients über eine zusätzliche Paketquelle erhalte ich vom Ubuntu-Update-Manager Fehlermeldungen. Ein Update in der Konsole jammert:

sudo apt-get update
...
Paketlisten werden gelesen... Fertig
W: GPG-Fehler: http://download.opensuse.org Release: Die folgenden Signaturen konnten nicht überprüft werden, weil ihr öffentlicher Schlüssel nicht verfügbar ist: NO_PUBKEY 977C43A8BA684223

Das Problem wurde bereits auf Ubuntuusers diskutiert und die vorgeschlagene Lösung funktioniert problemlos:

wget -q http://download.opensuse.org/repositories/isv:/ownCloud:/community/xUbuntu_14.04/Release.key -O- | sudo apt-key add -

Die Updates laufen wieder durch.

]]>
https://sgaul.de/2015/11/07/ubuntu-update-kraenkelt-nach-owncloud-installation/feed/ 3
ActiveRecord: Klasse einer Model-Instanz ändern https://sgaul.de/2015/09/26/activerecord-klasse-einer-model-instanz-aendern/ Sat, 26 Sep 2015 11:42:18 +0000 https://sgaul.de/?p=2815 ActiveRecord: Klasse einer Model-Instanz ändern weiterlesen]]> ActiveRecord erlaubt das direkte Ändern der Klasse einer Model-Instanz mittels #becomes. Dies kann zum Beispiel hilfreich sein, wenn man für einen Spezialfall weitere Funktionen oder Validierungen zu einem Model hinzufügen will:

> company = Company.last
> company.persisted?
 => true
> company.valid?
 => true 
> company = company.becomes(RestrictedCompany)
> company.is_a?(RestrictedCompany)
 => true 
> company.is_a?(Company)
 => true 
> company.persisted?
 => true
> company.valid?
 => false

Ähnliches ließe sich zwar auch mit bedingten Validierungen erreichen, würde aber ab einem gewissen Umfang schlicht nicht mehr skalieren.

Ein weiterer, oft gesehener Ansatz wäre RestrictedCompany.find(company.id). Dieser ist jedoch von der Datenbank abhängig und somit vermutlich weniger performant. Zudem benötigt er wirklich persistierte Instanzen. Dies ist vor allem nervig, wenn man in Tests gerne Stubbing nutzt.

Zum Speichern der Klassenänderung kann man #becomes! verwenden. Dies passt auch die STI-Vererbungsspalte (in aller Regel type) an.

]]>
Action-Mailer-Previews im Spec-Verzeichnis https://sgaul.de/2015/09/25/action-mailer-previews-im-spec-verzeichnis/ Fri, 25 Sep 2015 15:02:06 +0000 https://sgaul.de/?p=2813 Action-Mailer-Previews im Spec-Verzeichnis weiterlesen]]> Die Mailer-Previews in Rails 4 sind ein Segen. Wer Rspec statt der üblichen Testsuite verwendet kann das Verzeichnis von test in spec ändern, so dass alle Testdateien zusammen bleiben:

# config/application.rb

# ...
config.action_mailer.preview_path = "#{Rails.root}/spec/mailers/previews"

Nach einem Serverneustart sollten die Previews gefunden werden:

# spec/mailers/previews/user_mailer_preview.rb

class UserMailerPreview < ActionMailer::Preview
  def send_invitation
    UserMailer.send_invitation(Invitation.first!)
  end
end

Die Vorschau kann man dann unter den üblichen URLs betrachten, z.B.:

http://localhost:3000/rails/mailers/user_mailer/send_invitation
]]>
Suchstatus in ActiveAdmin deaktivieren https://sgaul.de/2015/09/23/suchstatus-in-activeadmin-deaktivieren/ Wed, 23 Sep 2015 14:47:03 +0000 https://sgaul.de/?p=2808 Suchstatus in ActiveAdmin deaktivieren weiterlesen]]> Der Suchstatus des Active-Admin-Masters funktioniert momentan fehlerhaft. Weder Scope-Namen noch Ransack-Suchen werden korrekt übersetzt.

Suchstatus in ActiveAdmin

Da ich das Konzept ohnehin nicht sonderlich gewinnbringend finde, schalte ich es zentral im Active-Admin-Initializer ab:

Wer das Konzept, wie ich, nicht sonderlich gewinnbringend und eines umständlichen Workarounds wert findet, kann es zentral im Initializer von ActiveAdmin abschalten:

# config/initializers/active_admin.rb

ActiveAdmin.setup do |config|
  # ...
  config.current_filters = false
end
]]>
ActiveAdmin: Authentifizierung für Browser und API https://sgaul.de/2015/09/22/activeadmin-authentifizierung-fuer-browser-und-api/ Tue, 22 Sep 2015 14:37:09 +0000 https://sgaul.de/?p=2805 ActiveAdmin: Authentifizierung für Browser und API weiterlesen]]> Eine einfache API-Authentifizierung lässt sich in ActiveAdmin durch Wiederverwendung des Standard-Admin-Users im Initializer erreichen:

 # config/initializers/active_admin.rb
 ActiveAdmin.setup do |config|
   config.prepend_before_filter do
     if active_admin_config.namespace.name == :api
       authenticate_or_request_with_http_basic('API') do |name, password|
         user = AdminUser.find_by_email!(name)
         sign_in(:admin_user, user) if user.valid_password?(password)
       end
     end
   end

Dies erlaubt die Angabe des Benutzernamens und Passworts im Browser als Popup (HTTP-Basic-Authentication) und als Header für APIs:

curl -u admin@example.com:password http://localhost:3000/api/my_resources.json

Will man dies mit Capybara ohne JS testen, hat sich für mich der folgende Ansatz bewährt:

page.driver.browser.basic_authorize(user.email, user.password)
visit api_my_resources_path

Bei anderen Treibern (zum Beispiel bei Verwendung von Javascript) kann die Schnittstelle abweichen.

]]>
API-Namespace für ActiveAdmin https://sgaul.de/2015/09/21/api-namespace-fuer-activeadmin/ Mon, 21 Sep 2015 14:55:55 +0000 https://sgaul.de/?p=2803 In ActiveAdmin kann man neben dem Standard weitere Namespaces definieren. Hierfür wird bei der Ressourcen-Registrierung die entsprechende Option angegeben:

# app/admin/api/my_resource.rb

ActiveAdmin.register MyResource, namespace: :api do
 # ...

Das Namespace-Verzeichnis kann unter app/admin abgelegt werden, ohne das „admin“ Teil der URL, des Controller-Namens o. ä. wird:

rake routes
# ...
api_my_resources GET  /api/my_resources(.:format)  api/my_resources#index
# ...
]]>