Efficiently add an event listener to many elements
When you need to add a event listener to hundreds of elements, this might slow down the browser.
An alternative is to register an event listener at the root of the DOM tree (document
). Then wait for events to bubble up and check whether the triggering element (event.target
) matches the selector before you run your callback.
This technique is called event delegation.
Performance considerations
Because you only register a single listener, registering is ...
How to grep through the DOM using the Capybara API
When your Cucumber feature needs to browse the page HTML, and you are not sure how to express your query as a clever CSS or XPath expression, there is another way: You can use all
and find
to grep through the DOM and then perform your search in plain Ruby.
Here is an example for this technique:
Then /^I should see an image with the file...
Capybara: Easiest way to get the parent of an element
If you already selected an element and want to get its parent, you can call find(:xpath, '..')
on it.
To get the parents parent, call find(:xpath, '../..')
.
Example
Find a link which contains a twitter icon and check that it links to the correct page:
<a href="http://twitter.com/">
<i class="icon is-twitter"></i>
</a>
link = page.find("a .icon.is-twitter").find(:xpath, '..')
link[:href].should == "http://twitter.com/"
There is a good overview on xpath syntax on [w3schools](https://www.w3schools.com...
How to set Chrome's time zone in Selenium tests
For Selenium tests, your browser starts in your local timezone, or whatever your system's environment specifies.
This is usually good enough. To test any timezone-dependent behavior in Chrome, you can change the time zone using the Chrome DevTools Protocol.
Example
Chrome accepts any IANA time zone name, like "Europe/Berlin" or "Asia/Tokyo".
Here is the relevant command for Capybara:
page.driver.browser.execute_cdp('Emulation.setTimezoneOverride', timezoneId: 'Asia/Tokyo')
...
Rails: Keeping structure.sql stable between developers
Why Rails has multiple schema formats
When you run migrations, Rails will write your current database schema into db/schema.rb
. This file allows to reset the database schema without running migrations, by running rails db:schema:load
.
The schema.rb
DSL can serialize most common schema properties like tables, columns or indexes. It cannot serialize more advanced database features, like views, procedures, triggers or custom ditionaries. In these cases you must switch to a SQL based schema format:
# in application.rb
config.a...
Implementing upload progress and remove button with ActiveStorage DirectUpload
DirectUpload allows you to upload files to your file storage without having to wait for the form to submit. It creates AJAX requests to persist the file within your form and wraps them in a little API. This card will show you how to use it in order to create in-place file uploads with progress and a remove button.
This is basic functionality, you may add additional elements, styles and logic to make this look fancy, but the core functionality is the same. I created a file upload that looks like this:
.
Step 1: Put some <br>
s into your HTML
First, place explicit line breaks that should work for most cases.
This depends on your use case and is affected by e.g. container widths or user content.
<div class="my-element">
<p>
Average score
<br>
last month
</p>
</div>
Step 2: Hide those <br>
s when you can balan...
Implementing authentication and authorization for ActiveStorage blobs/files
ActiveStorage does not provide any built-in way of implementing authentication for the available DirectUpload endpoint in Rails. When using DirectUpload
as JS wrapper in the frontend, be aware that its Rails endpoint is public by default, effectively allowing anyone to upload an unlimited amount of files to your storage.
The DirectUploadController
from @rails/activestorage
bypasses your form controller because it uploads the file using an AJAX request that runs directly, before any form roundtrip happens. This is a comfortable solutio...
Rails: Preloading associations in loaded records
Sometimes you want to fetch associations for an ActiveRecord that you already loaded, e.g. when it has deeply nested associations.
Edge Rider gives your models a static method preload_associations
. The method can be used to preload associations for loaded objects like this:
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
@user.preload_associations(threads: { posts: :author }, messages: :sender)
end
end
The attached initializers re...
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...
Updated: Automated "git bisect" will make your day
Added methods to keep the line number steady while you time-travel between commits:
- Copy the file to a new location that is not tracked by Git
- Run RSpec example by description
- Run RSpec example by nesting index
How to create a terminal progress indicators in Ruby
For long running scripts it is useful to show a indicator for the progress in the terminal. Alternatively you can use a gem like paul/progress_bar.
count = User.count
index = 0
User.find_each do |user|
printf("Progress: %.2f%%\r", index.to_f / count * 100)
user.update!(role: 'member')
index += 1
end
Preview video
Updated: Jasmine: Creating DOM elements efficiently
I have moved away from creating fixture elements using CSS selectors. While CSS can be very concise, it can be hard to recognize the created elements, and how they are nested within each other.
A more visual alternative is to embed a string of HTML into your test. My specs often have a function htmlFixtures()
that parses the HTML and returns a reference to all created elements:
let [list, item1, item2] = htmlFixtures(`
<ul type="square">
<li>item 1</li>
<li>item 2</li>
</ul>
`)
Because the effective HTML is visible...
How to automatically optimize SVG files with esbuild
SVG files often contain redundant information, like editor metadata or hidden elements. When esbuild handles your static assets, you can easily optimize SVG files using svgo as a plugin. Here is how to do it.
Adding svgo as an esbuild plugin
- Add the
svgo
package to your project'spackage.json
- Adjust your
esbuild.config.js
like so:
const esbuild = require('esbuild')
const svgo = require('svgo')
const options = {
// ...
loader: {
// ...
'.svg': 'copy', // this may be different...
Rails: Using PostgreSQL full-text search without a gem
PostgreSQL can cosplay as a full-text search engine. It doesn't have the features or fidelity of ElasticSearch or Algolia, but it's good enough if you just need to search and rank large volumes of text.
This card will teach you how to index, search and rank your Rails models in a PostgreSQL full-text index. We will do this without using any gems aside from ActiveRecord. While there are gems like pg_search or pg_fulltext, manual integration requires very...
Overview of method delegation in Rails
Method delegation in Rails can help you to keep your code organized and avoid deep call chains (law of demeter) by forwarding calls from one object to another. Rails provides several ways to accomplish this. Below is a concise overview of the most common approaches:
Single-Method delegation with delegate
Use the built-in delegate
method from ActiveSupport
to forward specific methods:
class User < ApplicationRecord
has_one :profile
delegate :full_name, :age, to: :profile, prefix: true
end
- `delegate: full_name, :age...
RSpec: Executing specs by example id (or "nesting index")
There are several ways to run a single spec. I usually copy the spec file path with the line number of the example and pass it to the RSpec binary: bin/rspec spec/models/user_spec.rb:30
(multiple line numbers work as well: :30:36:68
). Another is to tag the example with focus: true
or to run the example by matching its name.
In this card I'd like to ...
Rails: Overwriting default accessors
All columns of a model's database table are automagically available through accessors on the Active Record object.
When you need to specialize this behavior, you may override the default accessors (using the same name as the attribute) and simply call the original implementation with a modified value. Example:
class Poet < ApplicationRecord
def name=(value)
super(value.strip)
end
end
Note that you can also avoid the original setter and directly read/write from/to the instance's attribute storage. However this is dis...
How to not die with ActionView::MissingTemplate when clients request weird formats
When HTTP clients make an request they can define which response formats they can process. They do it by adding a header to the HTTP request like this:
Accept: application/json
This means the client will only understand JSON responses.
When a Rails action is done, it will try to render a template for a format that the client understand. This means when all you are HTML templates, a request that only accepts application/json
will raise an error:
An ActionView::MissingTemplate occurred in pages#foo:
Missing templa...
Flexbox: flex-basis vs. width vs. min-width vs. max-width
Within a Flexbox layout, there are multiple CSS attributes that may affect a child's basis (the initial width before flexing). You might be confused how flex-basis
, width
, min-width
and the intrinsic width of your content play together.
The attached article explains the differences. In summary:
- If a
flex-basis
is set, that is used as the basis - If no
flex-basis
is set, thewidth
is used as the basis - If neither
flex-basis
norwidth
is set, the content...
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 ...
Rails routing: Using constraints to avoid "Missing template" errors
You can use constraints in your routes.rb
to avoid getting ActionView::MissingTemplate
errors when wrong routes are called. Instead, the user will see a 404.
If you want multiple routes to use the same constraint you can use the block syntax:
constraints(format: 'html') do
resources :pages
resources :images
end
If you want constraints only on certain routes, you can do:
get '/users/account' => 'users#account', constraints: { format: 'html' }
Tip
You can also avoid this error type through [format con...
Haml Whitespace Preservation (or: Fixing Textarea Indentation in Haml)
Haml renders HTML with indentation reflecting the nesting level of elements. When it comes to white-space preserving content, this may become a problem: the content will be rendered including the HTML indentation.
Problematic: Preserved Indentation
.nest
%span Reference
%pre
= content
<div class="nest">
<span>Reference</span>
<pre>
Hello
World
</pre>
</div>
Better: Without Extra Indentation
Render with tilde ~
instead of equal...
Why your Cucumber feature loses cookies when run under Selenium
When your Cucumber feature seems to forget cookies / sessions when you run it with Selenium check if the test travels in time like here:
Given the date is 2017-10-20
When I sign in
Then I should see "Welcome!"
What happens here is that the Rails application serving pages runs in 2017, but the process running your browser still lives today. This huge gap in time will expire most cookies immediately.
If all you need is to freeze the time to a date, a workaround is to travel to the future instead.