View
Repeats

Designing HTML email

The 90s are calling: they want their tables back. Unfortunately, you'll need them all for laying out your HTML emails. (It is really that bad.)

Email client HTML rendering is way more scattered than browser HTML. While you might have a pretty good understanding of what features and patterns you can use to support all major browsers, I doubt anyone masters this craft for HTML email clients.

The only way to ensure your email looks good (acceptable, at least) in all mail clients, is to check it. Litmus is your go-to solu…

Repeats

count vs. size on ActiveRecord associations

TLDR

  • When counting records in an association, you should use #size in most cases.
  • It will not work if the parent record has never been saved. Also there are finer distinctions between #size and #count. See below.

count

  • Always makes a COUNT(*) query if a counter cache is not set up.
  • If a counter cache is set up on the association, #count will return that cached value instead of executing a new query.

size, if the association has already been loaded

  • Counts elements in the already loaded array.
  • Does not …
Repeats

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):

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 3pm instead of 3am.

Using the l…

Repeats

Ruby < 2.4: Downcasing or upcasing umlauts

Using .downcase or .upcase on strings containing umlauts does not work as expected in Ruby versions before 2.4. It leaves the umlauts unchanged:

"Über".downcase
=> "Über"

"Ärger".downcase
=> "Ärger"

The very same applies for french accents (Thanks Guillaume!):

"Être ou ne pas être, telle est la question".downcase
=> "Être ou ne pas être, telle est la question"

Obviously, this leads to problems when comparing strings:

"Über".downcase == "über"
=> false

In Rails you can use ActiveSupports' [multib…

Logic in media queries

Here is how to model basic logic in media queries.

AND

With keyword and.

# Target viewport widths between 500 and 800px
@media (min-width: 500px) and (max-width: 800px)

OR

Comma-separated.

# Target viewport widths below 500 or above 800px
@media (max-width: 500px), (min-width: 800px)

NOT

Needs a little overhead with not all and.

# Target devices that can't hover
@media not all and (hover)

See CSS: Using interaction media detection on why you'd need this.

Repeats

Concurrency issues with find-as-you-type boxes

Find-as-you-type boxes are usually built by observing changes in a text field, and querying the server via AJAX for search results or suggestions when the field has changed.

A common problem with this implementation is that there is no guarantee that AJAX responses are evaluated in the same order as the original requests. The effect for the user is that the search results are flashing back and forth while the user is typing the query, and when the user has stopped typing the last results don't always match the final query.

You won't notice…

Heads up: Rails offers two similar means for text truncation

Rails defines a #truncate helper as well as a method String#truncate.

= truncate("my string", length: 5)
= "my string".truncate(5)

Both are really similar; in fact, the helper invokes the method and improves it with two niceties: support for passing a block (which could e.g. render a "read on" link), and html_safe knowledge.

Prefer the truncate() helper

The method knows nothing about html_safe and will always return an unsafe string. FWIW, an HTML string may easily become invalid when truncated, e.g. when a closing ta…

External contentRepeats

Tasks, microtasks, queues and schedules - JakeArchibald.com

The way that Javascript schedules timeouts and promise callbacks is more complicated than you think. This can be the reason why callbacks are not executed in the order that they are queued.

Please read this article!


This is an extract of the example in the article which demonstrates the execution order of tasks and microtasks.

```
console.log('script start');

setTimeout(function() {
console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
}); …

Ruby: All Errno::ERROR constants inherit from SystemCallError

To catch all possible exceptions from a network call, we need to rescue many error classes like this:

rescue SocketError, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ECONNABORTED, Errno::EHOSTUNREACH, OpenSSL::SSL::SSLError, MyHttpLib::BadResponse

You can shorten this a bit by rescuing SystemCallError, which is a base class for all Errno:: exceptions:

rescue SocketError, SystemCallError, OpenSSL::SSL::SSLError, MyHttpLib::BadResponse

Some high-level …

External content

Rails' Insecure Defaults - Code Climate Blog

Rails’ reputation as a relatively secure Web framework is well deserved. Out-of-the-box, there is protection against many common attacks: cross site scripting (XSS), cross site request forgery (CSRF) and SQL injection. Core members are knowledgeable and genuinely concerned with security.

However, there are places where the default behavior could be more secure. This post explores potential security issues in Rails 3 that are fixed in Rails 4, as well as some that are still risky. I hope this post will help you secure your own apps, as w…

Repeats

ActiveRecord: validate_uniqueness_of is case sensitive by default

By default, validates_uniqueness_of does not consider "username" and "USERNAME" to be a collision. If you use MySQL this is probably not what you want, since string comparisons are case-insensitive in MySQL.

(If you use PostgreSQL, read this instead.)

Say you have a user model

class User < ActiveRecord::Base
  validates_uniqueness_of :name
end

with a unique index in the database.

If you try to create the users "user" and "USER", this will not trigger a validation error, but may fail with a SQL error due to d…

Beware: Don't name a controller action "cookies"

The method cookies is defined in the ActionController and should never be overwritten.

Bad example

class StaticPagesController < ApplicationController

  def imprint
  end

  def cookies
    redirect_to '/'
  end

end

If you create an action called cookies, any call to the cookie storage will be broken and call the method. What's more, in this example calls to static_pages_controller#imprint might as well end up redirecting to the homepage.

Solution

Just define the action as cookies_action or similar and adjust…

Repeats

Heads up: RSpec's diffs may not tell the truth

RSpec provides a nice diff when certain matchers fail.

Here is an example where this diff is helpful while comparing two hashes:

{a:1}.should match(a:1, b:2)

Failure/Error: {a:1}.should match(a:1, b:2)
  expected {:a=>1} to match {:a=>1, :b=>2}
  Diff:
  @@ -1,3 +1,2 @@
   :a => 1,
  -:b => 2,

Unfortunately, this diff is not as clever as it would need to. RSpec's instance_of matchers will look like errors in the diff (even if they are not), and time objects that differ only in milliseconds won't appear in the …

Using the Ruby block shortcut with arguments

Ruby has this handy block shortcut map(&:to_i) for map { |x| x.to_i }. However, it is limited to argument-less method invocations.

To call a method with an argument, you usually need to use the full block form. A common case is retrieving values from a list of hashes (imagine using a JSON API):

users = [ { name: 'Dominik', color: 'blue' }, { name: 'Stefan', color: 'red'} ]
names = users.collect do |user|
  user['name']
end

But with a little extension, you can do better.

Block shortcut with arguments

By defining `Symbol#c…

Cucumber steps to travel through time with Timecop

These steps are now part of Spreewald.


Here are some useful examples how to use the attached Cucumber Timecop steps:

When the date is 2011-05-06
When the time is 2011-05-06 17:30

There is also one really awesome step that lets you travel to the past or to the future:

When /^it is (\d+|a|some) (seconds?|minutes?|hours?|days?|months?|years?) (later|earlier)$/

As you can see, you describe the *time unit amo…

CSS: Using interaction media detection to disable hover styles for devices that have no hover

Since late 2015, all major browsers (still excluding Firefox) support pointing device media queries. These can be used to distinguish e.g. coarse from fine pointers (e.g. finger vs mouse), or a device with hover support from one without (e.g. desktop with mouse vs tablet).

Motivation

When hover styles modify the DOM, most mobile devices activate the hover styles on first tap. A second tap is required to trigger a click. While this can be handy, at times it makes the UX worse.

Another issue with hover styles is that they tend to st…

Repeats

Find an ActiveRecord by any column (useful for Cucumber steps)

The attached patch lets you find a record by a string or number in any column:

User.find_by_anything('carla')
User.find_by_anything('email@domain.de')
User.find_by_anything(10023)

There's also a bang variant that raises ActiveRecord::NotFound if no record matches the given value:

User.find_by_anything!('carla')

Boolean and binary columns are excluded from the search because that would be crazy.

I recommend copying the attachment to features/support/find_by_anything.rb, since it is most useful in Cucumber step …

Auto-destruct in 46 days

Updated: Unpoly: Loading large libraries on-demand

Rails asset pipeline

As in newer Rails all assets are precompiled and the path above /huge-lib.js will at least not exist in production. Here is our preferred way to solve this:

1. Copy the large library to your vendor folder e.g. vendor/asset-libs/huge-lib/huge-lib.js

2. Create a new file e.g. app/assets/javascripts/huge-lib.js with the following content:

//= require huge-lib.js

3. Allow this file to be precompi…

View
3481 cards
This website uses cookies to improve usability and analyze traffic.
Accept or learn more