Gem – 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.1 https://sgaul.de/wp-content/uploads/2019/02/cropped-sgaul-2-1-32x32.jpg Gem – 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.

]]>
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
Require und Rails https://sgaul.de/2015/06/28/require-und-rails/ Sun, 28 Jun 2015 20:13:25 +0000 https://sgaul.de/?p=2761 Require und Rails weiterlesen]]> Rails‘ Autoloading macht einen guten Job, so dass man Abhängigkeiten selten per Hand auflösen muss. Will man aber etwa eine bestehende Modelklasse aus einer Engine öffnen um eigenen Code zu ergänzen, so muss das Original notwendigerweise vorhanden sein:

# userengine/app/models/user

class User < ActiveRecord::Base
  belongs_to :user_group
end
# railsapp/app/models/user

require User::Engine.root.join('app', 'models', 'user')

class User < ActiveRecord::Base
  belongs_to :user_group
end

Dies wird vor allem während der Entwicklung zu Problemen führen, da Rails bei einer Änderung die Klasse zurücksetzt und nur die geänderte Datei neu lädt. Die andere Klassendefinition geht verloren, was zu merkwürdigen Ergebnissen führen kann.

Die Lösung für Rails ist ein spezielles Require, welches sich mit Autoloading verträgt:

# railsapp/app/models/user

require_dependency User::Engine.root.join('app', 'models', 'user')
# ...

Im Rails-Guide wird Single-Table-Inheritance als weiteres Einsatzgebiet genannt.

]]>
CanCanCan ist kein CanCan 2.0 https://sgaul.de/2014/03/25/cancancan-ist-kein-cancan-2-0/ Tue, 25 Mar 2014 21:11:22 +0000 https://sgaul.de/?p=2560 CanCanCan ist kein CanCan 2.0 weiterlesen]]> Außerhalb meiner Wahrnehmung gibt es auf Github seit einiger Zeit ein Projekt, dass CanCan für tot erklärt und dessen Nachfolge antreten will: CanCanCan. 17 Menschen haben zum Projekt der „CanCanCommunity“ bereits beigetragen. So wird das kleine Gem von Ryan Bates, der seit längerer Zeit aufgrund gesundheitlicher Probleme ausfällt, weitergeführt. Wichtige Fixes für Rails 4 finden so ihren Weg ins Projekt.

Keine Entwicklung im CanCan-2.0-Branch

Mit CanCan hat man auch den 2.0-Branch geforkt, leider blieb dieser bisher unangetastet. Die neue Version sollte vor allem die von mir seit langem erwartete Unterstützung für Attribute mitbringen. So lässt sich nicht mehr nur definieren und prüfen ob ein Model insgesamt, sondern auch welche Attribute geändert werden dürfen:

can :update, :projects, [:name, :priority]

Der Verfasser der Readme vertröstet derweil:

I am currently focusing on the 1.x branch for the immediate future, making sure it is up to date as well as ensuring compatibility with Rails 4+. I will take a look into the 2.x branch and try to see what improvements, reorganizations and redesigns Ryan was attempting and go forward from there.

Bleibt zu hoffen, dass einer der Entwickler die Zeit für einen tieferen Blick findet.

]]>
RVM: Gemset erstellen und .versions.conf anlegen https://sgaul.de/2014/03/09/rvm-gemset-erstellen-und-automatisch-konfigurieren/ Sun, 09 Mar 2014 14:21:57 +0000 https://sgaul.de/?p=2473 Memo an mich selbst: Ein neues Gemset einrichten und mittels .versions.conf als Standard für das aktuelle Verzeichnis festlegen:

rvm use --create --versions-conf ruby-2.1@gemset_name

In aller Regel benötige ich auch Bundler:

gem install bundler
]]>