Capybara: Testing file downloads

Download buttons can be difficult to test, especially with Selenium. Depending on browser, user settings and response headers, one of three things can happen:

  • The browser shows a "Save as..." dialog. Since it is a modal dialog, we can no longer communicate with the browser through Selenium.
  • The browser automatically downloads the file without prompting the user. For the test it looks like nothing has happened.
  • The browser shows a binary document in its own window, like a PDF. Capybara/Selenium freaks out because there is no HTML docum...

Self-expiring URLs with Apache

When delivering non-public uploaded files (images, documents etc), one has to decide whether and how to do authorization. The usual approaches are:

  • Using send_file with a regular controller. This is secure, but potentially slow, especially for large collections of images.
  • Using unguessable URLs. This is fast (because Apache can deliver assets without going through Rails), but less secure.

When going with the "unguessable URL" approach, it is possible to somewhat increase security by using expiring URLs. The idea is to encode the expi...

Vortrag: Elasticsearch Grundlagen und Rails-Integration mit searchkick

Was ist Elastic?

  • Suchmaschine, basierend auf Apache Lucene
  • größtenteils Open-Source
  • einige kommerzielle Features ("Elastic Stack", früher "X-Pack")
    • Zugriffsrechte (bis vor kurzen)
    • Monitoring
    • Reporting
    • Graph-Unterstützung
    • Machine Learning
  • REST-Api (JSON über HTTP)

Grundlagen

Elastic antwortet per Default auf Port 9200

http GET :9200
{
  "name": "ntK2ZrY",
  "cluster_name": "elasticsearch",
  "cluster_uuid": "Bbc-ix5bQZij5vfFU29-Cw",
  "version": {
    "number": "6.7.1",
    "build_flavor": "...

Tod: A Gem for handling daytime without a date

Tod is a gem for working with daytimes.

Another additional gem?

Thus SQL has a time datatype for storing time of day in the format hh:mm:ss, neither Ruby nor Rails themselves offer an elegant way to deal with day times.

Time and DateTime both handle daytime values AND calendar date, using them to only store the time of day will end in inconsistent and thus confusing data, e. g. Time.new will initialize with the current Time in your Timezone, DateTime.new initializes at January 1, at an undefined year, without a timezone o...

When reading model columns during class definition, you must handle a missing/empty database

When doing some meta-programming magic and you want to do something for all attributes of a class, you may need to access connection or some of its methods (e.g. columns) during class definition.

While everything will be fine while you are working on a project that is in active development, the application will fail to boot when the database is missing or has no tables. This means that Raketasks like db:create or db:migrate fail on a freshly cloned project.

The reason is your environment.rb which is loaded for Raketasks and calls...

ActionMailer: Previewing mails directly in your email client

In Rails, we usually have a mailer setup like this:

class MyMailer < ActionMailer::Base

  def newsletter
    mail to: 'receiver@host.tld',
      from: 'sender@host.tld',
      subject: 'My mail'
  end

end

If you want to preview your mail in the browser, you can use the Action Mailer Preview. To inspect the mail directly in your email client, just create an .eml file and open it with your client:

mail = MyMailer.newsletter
Fil...

Why you can't use timezone codes like "PST" or "BST" for Time objects

Rails' ActiveSupport::TimeWithZone objects have both a timezone code and offset, e.g. Thu, 28 Mar 2019 16:00:00 CET +01:00. Ruby's stdlib TZInfo also has time zones, but with different identifiers.
Unfortunately, not all timezone codes can be used to parse strings or to move time objects into another time zone.

Some timezone codes like CET are supported by ActiveSupport extensions like String#in_time_zone, while many codes will actually not work:

>> '2019-03-01 12:00'.in_time_zone('PST')
ArgumentError (Invalid Timezone: PST)
...

rails_state_machine 1.1.2 released

Fixed bugs where calling valid? would change the record's state.

How to fix parallel_tests with Redis on powerful machines

When you have a powerful machine with many CPU cores, you might run into an error like

ERR DB index is out of range (Redis::CommandError)

This is because Redis defaults to at most 16 databases (0 to 15) and running tests in parallel might exceed that (your tests might run on databases 1..n or 2..(n+1)).

You can increase that limit:

  1. Get number of CPUs of your machine.

    nproc --all
    
  2. Open up Redis configuration file.

    sudo vim /etc/redis/redis.conf
    
  3. Find databases row and increase it, e.g. set to CPU cou...

Rails: How to get URL params without routing parameters (or vice versa)

Rails' params hash contains any request parameters (URL parameters or request payload) as well as routing parameters like :controller, :action, or :id.

To access only URL parameters, use request.query_parameters. Routing params are available through request.path_parameters.

# On /users?query=Bob&page=2

>> request.params
=> {"page"=>"2", "query"=>"Bob", "controller"=>"users", "action"=>"index"}

>> request.query_parameters
=> {"page"=>"2", "query"=>"Bob"}

>> request.path_parameters
=> {:controller=>"users", :action=>"i...

Carrierwave processing facts

  1. Class-level process definitions are only applied to the original file
  2. Versions are generated based on the processed original file
  3. Callbacks (before/after) are applied to original file and each version by itself
  4. Under the hood, a version is an instance of the uploader class that has no versions
  5. Version uploader and original uploader can be distinguished by checking #version_name: version uploaders return the version name, whereas the original uploader instance returns nil
  6. Version instances do not have a re...

Trigram indexing as an alternative to PostgreSQL fulltext search

For searching in large database tables we usually use PostgreSQL's fulltext search capabilities.

While this works reasonably well for content primarily consisting of prose, it is not necessarily a good solution for all use cases. The main issue is that it is only possible to search for prefixes of text tokens, which can potentially be unexpected for users.

One example are dates:

If you index the text 2019-01-23 15:16, PostgreSQL will create the following tokens: 2019, -01, -23, 15 16. A user searching for 01-23 wi...

JavaScript without jQuery (presentation from 2019-01-21)

Summary

  • We want to move away from jQuery in future projects
  • Motivations are performance, bundle size and general trends for the web platform.
  • The native DOM API is much nicer than it used to be, and we can polyfill the missing pieces
  • Unpoly 0.60.0 works with or without jQuery

Is jQuery slow?

From: Sven
To: unpoly@googlegroups.com
Subject: performance on smartphones and tablets

Hello

I just used your framework in one project and must say,
I am really pleased with it -- but only on a desktop computer.

Have you benchm...

Regular tasks for long-running projects

When projects run for many years, they require special regular maintenance to stay fresh. This kind of maintenance is usually not necessary for small or quick projects, but required to keep long-running projects from getting stale.

You should be able to fit this into a regular development block.

Quarterly

Check which libraries need updating

As time goes by, libraries outdate. Check your software components and decide if any of it needs an update. Your main components (e.g. Ruby, Rails, Unpoly) should always be reasonably up to d...

ActiveJob Inline can break the autoloading in development

We figured out, that ActiveJob Inline might lead to autoloading problems in development. The result was an exception when running an import script, which delivers async mails.

A copy of XXX has been removed from the module tree but is still active! (ArgumentError)

Our fix was to use .deliver_now and not .deliver_later (this is not a general fix, but it was okey for us). Below there are some debug hints which helped us to locate the problem:

  • We placed a pry debugger in [ActiveSupport#clear](https://github.com/rails/rails/blob...

Minify Font Awesome fonts with webpack

Font Awesome 5 is a comprehensive solution for vector icons on your website.

Originally, Font Awesome came as an icon font (plus stylesheets), but recently it can also be used as a pure JavaScript solution (which will render icons as inline <svg> tags), or even as SVG sprites.

All solutions have their pros and cons:

Icon font:

  • little CPU load (no JavaScript)
  • fonts are relatively large
  • 1 extra HTTP request

Javascript + inline SVG:

  • higher CPU load (needs to watch the DOM via mutation observers to ad...

An incomplete guide to migrate a Rails application from paperclip to carrierwave

In this example we assume that not only the storage gem changes but also the file structure on disc.

A general approach

Part A: Create a commit which includes a script that allows you to copy the existing file to the new file structure.

Part B: Create a commit which removes all paperclip logic and replace it with the same code you used in the first commit

Part A

Here are some implementation details you might want to reuse:

  • Use the existing models to read the files from
  • Use your own carrierwave models to write t...

Geordi: How to rerun failed features

Geordi's cucumber command has a --rerun option that reruns failing tests the given number of times. Usage:

geordi cucumber path/to/features --rerun=2
geordi cucumber path/to/features -r2

Background and how to rerun manually

Cucumber will save a file tmp/parallel_cucumber_failures.log containing the filenames and line number of the failed scenarios after a full test run. Normally you can say cucumber -p rerun (rerun is a profile defined by default in config/cucumber.yml) to rerun all failed scenarios.

Here are a few al...