Adding Jasmine JavaScript specs to a Webpack(er) project

The goal is to get Jasmine specs running in a Rails project using Webpacker, with the browser based test runner. Should be easily adaptable to a pure Webpack setup.

Image

Step 1: Install Jasmine

yarn add jasmine-core

Step 2: Add two separate packs

Since we do not want to mix Jasmine into our regular Javascript, we will create two additional packs. The first only contains Jasmine and the test runner. The second will contain our normal application code and the specs themselves.

We cannot...

Cucumber: Identifying slow steps that drag down your test speed

In most projects I know, Cucumber test suite speed is not an issue. Of course, running 350 features takes its time, but still each test for itself is reasonably fast. There is nothing you can do to fundamentally speed up such a test (of course, you should be using parallel_tests).

However, in projects that go beyond clicking around in a web GUI and checking results, there might be various measures to speed things up. Katapult tests for example could be sped up more than 4 times by re...

Generating and streaming ZIP archives on the fly

When your Rails application offers downloading a bunch of files as ZIP archive, you basically have two options:

  1. Write a ZIP file to disk and send it as a download to the user.
  2. Generate a ZIP archive on the fly while streaming it in chunks to the user.

This card is about option 2, and it is actually fairly easy to set up.

We are using this to generate ZIP archives with lots of files (500k+) on the fly, and it works like a charm.

Why stream downloads?

Offering downloads of large archives can be cumbersome:

  • It takes time to b...

Enabling view rendering for controller specs

Views are normally (for good reason) not rendered in controller specs. If you need it to happen, use:

RSpec 1 (Rails 2):

integrate_views

RSpec 2 (Rails 3):

render_views

Note that you can't use that inside it blocks but need to put it in the nesting example group, like this:

    describe '#update' do
      cont...

Carrierwave: How to remove container directories when deleting a record

When deleting a record in your Rails app, Carrierwave automatically takes care of removing all associated files.
However, the file's container directory will not be removed automatically. If you delete records regularly, this may be an annoyance.

Here is a solution which was adapted from the Carrierwave GitHub wiki and cleans up any empty parent directories it can find.

class ExampleUploader < CarrierWave...

Capistrano 3: Running a command on all servers

This Capistrano task runs a command on all servers.

bundle exec cap production app:run cmd='zgrep -P "..." RAILS_ROOT/log/production.log'

Code

# lib/capistrano/tasks/app.rake

namespace :app do

  # Use e.g. to grep logs on all servers:
  #   b cap production app:run_cmd cmd='zgrep -P "..." RAILS_ROOT/log/production.log' 
  #
  # * Use RAILS_ROOT as a placeholder for the remote Rails root directory.
  # * Append ` || test $? =1;` to grep calls in order to avoid exit code 1 (= "nothing found")
  # * To be able to process ...

Heads up: RSpec-Mocks' #stub_const will define intermediate modules that have not been loaded yet

The issue: You are using stub_const to change a constant value for your test.

stub_const "SomeClass::CONST", 'test'

All of a sudden, tests fail with undefined method 'some_method' for #<SomeClass:0x00000000101433a8>.

The reason

When using stub_const before the Class containing the constant has been loaded, a module is automatically created with the name.

Since RSpec does no autoloading, it will create a SomeClass module by itself. This is arguably a good idea.

As a workaround, use stub_const in your Rails specs li...

Why has_many :through associations can return the same record multiple times

An association defined with has_many :through will return the same record multiple times if multiple join models for the same record exist (a n:m relation). To prevent this, you need to add ->{ uniq } as second argument to has_many (below Rails 4 it is a simple option: has_many :xyz, :uniq => true).

Example

Say you have an Invoice with multiple Items. Each Item has a Product:

class Invoice < ActiveRecord::Base
  has_many :items
  has_many :products, :through => :items
end

class Item < ActiveRecord::Base
  ...

How to update the bundler version in a Gemfile.lock

  1. Install the latest bundler version:

    gem install bundler
    Fetching bundler-2.3.5.gem
    Successfully installed bundler-2.3.5
    1 gem installed
    
  2. Update the bundler version in Gemfile.lock:

    bundle update --bundler  
    
  3. Confirm it worked:

    $ tail -n2 Gemfile.lock 
    BUNDLED WITH
      2.3.5
    

Notes:

  • Bundler should automatically detect the latest installed version. If it does not, you can specify your preferred version like so:

    b...
    

Testing ActiveRecord callbacks with RSpec

Our preferred way of testing ActiveRecord is to simply create/update/destroy the record and then check if the expected behavior has happened.

We used to bend over backwards to avoid touching the database for this. For this we used a lot of stubbing and tricks like it_should_run_callbacks.

Today we would rather make a few database queries than have a fragile test full of stubs.

Example

Let's say your User model creates a first Project on cr...

Heads up: Quering array columns only matches equally sorted arrays

Given you have an array column like this:

create_table "users", force: :cascade do |t|
  t.integer "movie_ids", default: [], array: true
end

You might think that the following queries yield the same result:

User.where(movie_ids: [16, 17])
User.where(movie_ids: [17, 16])

Turn's out - they are not! They do care about array ordering more than I do.

To query for identical arrays independent of their order you have to either:

  1. Sort both the query and database content. If you're on Rails 7.1 you can use the new [`normal...

Migration from the Asset Pipeline to Webpacker

This is a short overview of things that are required to upgrade a project from the Asset Pipeline to Webpacker. Expect this upgrade to take a few days even the diff is quite small afterwards.

Preparations

1. Find all libraries that are bundled with the asset pipeline. You can check the application.js and the application.css for require and import statements. The source of a library is most often a gem or a vendor directory.
2. Find an working example for each library in the application and write it down.
3. Find out the ver...

Use DatabaseCleaner with multiple test databases

There is a way to use multiple databases in Rails.
You may have asked yourself how you're able to keep your test databases clean, if you're running multiple databases with full read and write access at the same time. This is especially useful when migrating old/existing databases into a new(er) one.

Your database.yml may look like this:

default: &default
  adapter: postgresql
  encoding: unicode
  username: <%= ENV['DATABASE_USER'] %>
  host: <%= ENV['DATABASE...

Shortcuts for getting ids for an ActiveRecord scope

You can use .ids on an ActiveRecord scope to pluck all the ids of the relation:

# Modern Rails
User.where("users.name LIKE 'Foo Bar'").ids

# Rails 3.2+ equivalent
User.where("users.name LIKE 'Foo Bar'").pluck(:id)

# Edge rider equivalent for Rails 2+
User.where("users.name LIKE 'Foo Bar'").collect_ids

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...

How to search through logs on staging or production environments

We generally use multiple application servers (at least two) and you have to search on all of them if you don't know which one handled the request you are looking for.

Rails application logs usually live in /var/www/<project-environment-name>/shared/log.
Web server logs usually live in /var/www/<project-environment-name>/log.

Searching through single logs with grep / zgrep

You can use grep in this directory to only search the latest logs or zgrep to also search older (already zipped) logs. zgrep is used just like grep ...

Detecting if a Ruby gem is loaded

Detect if a gem has been activated

A gem is activated if it is either in the current bundle (Gemfile.lock), or if you have manually activated it using Kernel#gem (old-school).

To detect if e.g. activerecord has been activated:

if Gem.loaded_specs.has_key?('activerecord')
  # ActiveRecord was activated
end

Detect if a particular gem version has been activated

To detect if e.g. activerecord ma...

MySQL: Disable query cache for database profiling

If you want to see how long your database queries actually take, you need to disable MySQL's query cache. This can be done globally by logging into a database console, run

SET GLOBAL query_cache_type=OFF;

and restart your rails server.

You can also disable the cache on a per query basis by saying

SELECT SQL_NO_CACHE * FROM ...

You also probably want to disable Rails internal (per-request) cache. For this, wrap your code with a call to ActiveRecord::Base.uncached. For example, as an around_filter:

d...

Why two Ruby Time objects are not equal, although they appear to be

So you are comparing two Time objects in an RSpec example, and they are not equal, although they look equal:

expected: Tue May 01 21:59:59 UTC 2007,
     got: Tue May 01 21:59:59 UTC 2007 (using ==)

The reason for this is that Time actually tracks fractions of a second, although #to_s doesn't say so and even though you probably only care about seconds. This means that two consecutive calls of Time.now probably return two inequal values.

Consider freezing time in your tests so it is not dependent on the speed of the executi...

How to fix: Pasting in IRB 1.2+ is very slow

IRB 1.2 (shipped with Ruby 2.7, but works on 2.5+) brings pretty syntax highlighting and multiline cursor navigation. However, pasting longer contents is incredibly slow. You can fix that by disabling said features. [1]

Ruby 3.0.0-pre2 solved the issue (however, the fix does not appear to be included in IRB 1.2.6, it must be Ruby itself).

Option 1:

Add a command line flag when opening an IRB:

irb --nomultiline

This also works on modern Rails...

ES6 imports are hoisted to the top

From Exploring ES6:

Module imports are hoisted (internally moved to the beginning of the current scope). Therefore, it doesn’t matter where you mention them in a module and the following code works without any problems:

foo();
import { foo } from 'my_module';

Footgun example

When you're not aware of import hoisting you may be surprised that your code runs in a different order than you see in the source file.

The example below is taken from the [...

Storing trees in databases

This card compares patterns to store trees in a relation database like MySQL or PostgreSQL. Implementation examples are for the ActiveRecord ORM used with Ruby on Rails, but the techniques can be implemented in any language or framework.

We will be using this example tree (from the acts_as_nested_set docs):

root
|
+-- Child 1
|   |
|   +-- Child 1.1
|   |
|   +-- Child 1.2
|
+-- ...

Vortrag: Content Security Policy: Eine Einführung

Grundidee

CSP hat zum Ziel einen Browser-seitigen Mechanismus zu schaffen um einige Angriffe auf Webseiten zu verhindern, hauptsächlich XSS-Angriffe.

Einschub: Was ist XSS?

XSS = Cross Site Scripting. Passiert wenn ein User ungefiltertes HTML in die Webseite einfügen kann.

<div class="comment">
  Danke für den interessanten Beitrag! <script>alert('you have been hacked')</script>
</div>

Rails löst das Problem weitgehend, aber

  • Programmierfehler weiter möglich
  • manchmal Sicherheitslücken in Gems oder Rails

Lösungsid...

PostgreSQL cheat sheet for MySQL lamers

So you're switching to PostgreSQL from MySQL? Here is some help...

General hints on PostgreSQL

  • \? opens the command overview
  • \d lists things: \du lists users, \dt lists tables etc

Command comparison

Description MySQL command PostgreSQL equivalent
Connect to the database mysql -u $USERNAME -p sudo -u postgres psql
Show databases SHOW DATABASES; \l[ist]
Use/Connect to a database named 'some_database' USE some_database; \c some_dat...