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...
Carrierwave processing facts
- Class-level
processdefinitions are only applied to the original file - Versions are generated based on the processed original file
- Callbacks (
before/after) are applied to original file and each version by itself - Under the hood, a version is an instance of the uploader class that has no versions
- Version uploader and original uploader can be distinguished by checking
#version_name: version uploaders return the version name, whereas the original uploader instance returnsnil - Version instances do not have a re...
Heads up: pg_restore --clean keeps existing tables
When restoring a PostgreSQL dump using pg_restore, you usually add the --clean flag to remove any existing data from tables.
Note that this only removes data from tables that are part of the dump and will not remove any extra tables. You need to do that yourself.
When is this relevant?
As an example: You want to load a staging dump into your development machine. On your development machine, you have run migrations that introduced more tables which do not yet exist on staging. pg_restore with --clean will loa...
Auto-generating plain-text bodies for HTML e-mails in Rails apps
When building an application that sends e-mails to users, you want to avoid those e-mails from being classified as spam. Most obvious scoring issues will not be relevant to you because you are not a spammer.
However, your application must do one thing by itself: When sending HTML e-mails, you should include a plain-text body or tools like SpamAssassin will apply a significant score penalty. Here is how to do that automatically.
- Add
premailer-railsto yourGemfileandbundle. - Done! ...
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 da...
Rails: Prefer parsing dates with Date.strptime()
It is very common to parse dates from strings. It seems obvious to use Date.parse for this job. However this method does not validate the input and tries to guess the format of the string.
This can lead to a very unexpected results:
Date.parse('Foobar_09_2018')
# Tue, 09 Oct 2018
In most of the cases it would be better to use Date.strptime as you can provide a date or time pattern to match against.
Date.strptime('Foobar_09_2018', '%d_%m_%Y')
# ArgumentError (invalid strptime format - `%d_%m_%Y')
Date.strptime('01_09...
How to examine an unknown Ruby object
When debugging your application, you will come across objects created by some gem or framework. You don't have the source code at hand, still need to inspect this object. Here are some tools to do so:
Relevant methods
@object.methods - Object.instance_methods returns a list of methods excluding methods inherited from Object. This makes the methods list drastically more relevant. You can also try subtracting other base classes like ActiveRecord::Base.methods etc.
To further narrow it down you can also just look at public methods...
A quick introduction to CORS
Background
Cross-Site Request Forgery (CSRF) is an attack pattern for websites. A CSRF attack is usually relevant in a browser context, where state is kept for multiple domains (as opposed to independent requests made e.g. with curl). The most common example is authentication via cookies. If a script on https://example.com made requests to https://docs.google.com, the browser would send all cookies for docs.google.com along, effectively given the script access to anythin...
Raising JavaScript errors in Ruby E2E tests (RSpec, Cucumber)
A JavaScript error in an E2E test with Selenium will not cause your test to fail. This may cause you to miss errors in your frontend code.
Using the BrowserConsole helper below you can check your browser's error console from your E2E tests.
The following will raise BrowserConsole::ErrorsPresent if there is an error on the browser console:
BrowserConsole.assert_no_errors!
Ignoring errors
You can ignore errors by their exact message:
BrowserConsole.ignore('Browser is burning')
You can ignore errors with me...
Pretty commit messages via geordi
Geordi provides a pretty neat way to generate beautiful commit messages according to your stories in Linear:
geordi commit
Geordi reads from a .geordi.yml file inside your repo and connects to Linear to list started and finished stories with their title. Choosing one of them generates a commit message including id and title from Linear app and a link to the original issue. For example:
[VW-1337] CRUD Users
Issue: https://linear.app/makandra/issue/VW-1337/crud-user...
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...
Ruby: How to use prepend for cleaner monkey patches
Let's say you have a gem which has the following module:
# within the imaginary super gem
module SuperClient
def self.foo
'Foo'
end
def bar
'Bar'
end
end
For reasons you need to override foo and bar.
Keep in mind: Your code quality is getting worse with with each prepend (other developers are not happy to find many library extensions). Try to avoid it if possible.
- Add a
lib/ext/super_client.rbto your project (see How to organize monkey patches in Ruby on Rails projects)
2...
HTML forms with multiple submit buttons
Most forms have a single submit button that will save the record when pressed.
Sometimes a form needs additional submit buttons like "accept" or "reject". Such buttons usually attempt a state transition while updating the record.
To process a form with multiple buttons, your server-side code will need to know which button was pressed. To do so you can give each submit button a different [formaction] attribute. This will override the ...
Best practices: Large data migrations from legacy systems
Migrating data from a legacy into a new system can be a surprisingly large undertaking. We have done this a few times. While there are significant differences from project to project, we do have a list of general suggestions.
Before you start, talk to someone who has done it before, and read the following hints:
Understand the old system
Before any technical considerations, you need to understand the old system as best as possible. If feasible, do not only look at its API, or database, or frontend, but let a user of the old system sho...
Project management best practices: The story tracker
In general, the tracker should always be the definitive source of truth of what needs to be done as part of a project. If you identify a task that needs to be done, you should create a story. If you learn something that contradicts an existing story, change it.
The tracker can contain two types of stories: Developer stories, and non-developer stories.
Non-developer stories
Non-developer stories should be clearly marked. They usually belong to the PM (or maybe people from the operations team). Those story can take all forms, could just...
Project management best practices: Project team responsibilities
In a project team for a bigger project people have several roles:
- Developer: at makandra
- Project lead: at makandra
- Project manager (PM): at makandra, external, or with the customer. In a smaller project this person is also the project lead.
- Product owner (PO): with the customer
Developer
- Development
- Take responsibility for their stories. This includes always gathering all necessary information from the project lead or the PM, communicate blockers, make sure stories are merged, deployed etc.
- Tell the project lead, if y...
How to make changes to a Ruby gem (as a Rails developer)
At makandra, we've built a few gems over the years. Some of these are quite popular: spreewald (> 1M downloads), active_type (> 1M downloads), and geordi (> 200k downloads)
Developing a Ruby gem is different from developing Rails applications, with the biggest difference: there is no Rails. This means:
- no defined structure (neither for code nor directories)
- no autoloading of classes, i.e. you need to
requireall files yourself - no
active_supportniceties
Also, their scope...
Optimizing images for the web
For webpages to load fast it's recommended to optimize images. Ideally an image's file size should be as small as possible while still being of decent quality. This card demonstrates two command line tools for image optimization
Use identify to fetch information about pictures. convert can change size/quality and strip meta information. Both commands are supplied by ImageMagick.
$ identify in.jpg
in.jpg JPEG 294x440 294x440+0+0 8-bit sRGB 92.8KB 0.000u 0:00.000
$ convert in.jpg -resize x220 -strip -set profile sRGB2014.icc -qua...
Devise: Invalidating all sessions for a user
Background information about session storage in Rails
Rails has a default mechanism to store the session in the CookieStore. This is a cookie which holds the entire user session hash in the browser. This cookie is serialized, encoded with base64, and signed.
How Devise handles authentication
Devise uses this CookieStore. To track a users session, a salt is stored in the session ...
Whenever: Don't forget leading zeros for hours!
Whenever is a Ruby gem that provides a nicer syntax for writing and deploying cron jobs.
Leading zeros are important for whenever if you use the 24-hours format!
This schedule.rb:
every 1.day, at: '3:00', roles: [:primary_cron] do
runner 'Scheduler.delay.do_things'
end
will lead to this crontab entry (crontab -l) with the default configuration:
0 15 * * * /bin/bash -l -c 'cd /var/www/my-project/releases/20180607182518 && bin/rails runner -e production '\''Scheduler.delay.do_things'\'''
Which would run on 3...
When you want to format only line breaks, you probably do not want `simple_format`
For outputting a given String in HTML, you mostly want to replace line breaks with <br> or <p> tags.
You can use simple_format, but it has side effects like keeping some HTML.
If you only care about line breaks, you might be better off using a small, specialized helper method:
def format_linebreaks(text)
safe_text = h(text)
paragraphs = split_paragraphs(safe_text).map(&:html_safe)
html = ''.html_safe
paragraphs.each do |paragraph|
html << content_tag(:p, paragraph)
end
html
end
Full di...
Don't sum up columns with + in a SQL query if NULL-values can be present.
Don't sum up columns with + in a sql-query if NULL-Values can be present.
MySQL and PostgreSQL cannot sum up NULL values with the + value. The sum value will be NULL.
MySQL:
mysql> select 1 + 2 + 3;
+-----------+
| 1 + 2 + 3 |
+-----------+
| 6 |
+-----------+
1 row in set (0,00 sec)
mysql> select 1 + NULL + 3;
+--------------+
| 1 + NULL + 3 |
+--------------+
| NULL |
+--------------+
1 row in set (0,00 sec)
Postgres:
test_database=# select 1 + 2 + 3;
?column?
----------
6
(1 row)
t...
Writing strings as Carrierwave uploads
When you have string contents (e.g. a generated binary stream, or data from a remote source) that you want to store as a file using Carrierwave, here is a simple solution.
While you could write your string to a file and pass that file to Carrierwave, why even bother? You already have your string (or stream).
However, a plain StringIO object will not work for Carrierwave's ActiveRecord integration:
>> Attachment.create!(file: StringIO.new(contents))
TypeError: no implicit conversion of nil into String
This is because Carrierwav...
HTML5: disabled vs. readonly form fields
Form fields can be rendered as noneditable by setting the disabled or the readonly attribute. Be aware of the differences:
disabled fields
- don’t post to the server
- don’t get focus
- are skipped while tab navigation
- available for
button,fieldset,input,select,textarea,command,keygen,optgroup,option
Browser specific behavior:
- IE 11: text inputs that are descendants of a disabled fieldset appear disabled but the user can still interact with them
- Firefox: selecting text in a disabled text field is no...