Using multiple MySQL versions on the same linux machine using docker
We had a card that described how to install multiple mysql versions using mysql-sandbox. Nowadays with the wide adoption of docker it might be easier to use a MySQL docker image for this purpose.
Create a new mysql instance
docker run --name projectname_db -e MYSQL_ROOT_PASSWORD=secret -p "33008:3306" -d --restart unless-stopped mysql:5.7
The port 33008 is a freely chosen free port on the host machine that will be used to establish a...
Returning an empty ActiveRecord scope
Returning an empty scope can come in handy, e.g. as a default object. In Rails 4 you can achieve this by calling none on your ActiveRecord model.
MyModel.none # returns an empty ActiveRecord::Relation object
For older Rails versions you can use the attached initializer to get a none scope.
Creating a self-signed certificate for local HTTPS development
Your development server is usually running on an insecure HTTP connection which is perfectly fine for development.
If you need your local dev server to be accessible via HTTPS for some reason, you need both a certificate and its key. For a local hostname, you need to create those yourself.
This card explains how to do that and how to make your browser trust the certificate so it does not show warnings for your own certificate.
Easy: self-signed certificate
To just create a certificate for localhost, you can use the following command....
Too many parallel test processes may amplify flaky tests
By default parallel_tests will spawn as many test processes as you have CPUs. If you have issues with flaky tests, reducing the number of parallel processes may help.
Important
Flaky test suites can and should be fixed. This card is only relevant if you need to run a flaky test suite that you cannot fix for some reason. If you have no issues...
How to not repeat yourself in Cucumber scenarios
It is good programming practice to Don't Repeat Yourself (or DRY). In Ruby on Rails we keep our code DRY by sharing behavior by using inheritance, modules, traits or partials.
When you reuse behavior you want to reuse tests as well. You are probably already reusing examples in unit tests. Unfortunately it is much harder to reuse code when writing integration tests with Cucumber, where you need to...
Faking and testing the network with WebMock
An alternative to this technique is using VCR. VCR allows you to record and replay real HTTP responses, saving you the effort to stub out request/response cycles in close details. If your tests do require close inspection of requests and responses, Webmock is still the way.
WebMock is an alternative to FakeWeb when testing code that uses the network. You sh...
Managing chrome versions for selenium
Currently we often use geordi to run cucumber and rspec tests. Geordi takes care of installing a matching chromedriver for the installed google-chrome binary. The google-chrome binary is managed by apt.
Another approach is to use the Selenium Manager for installing and using the correct browser versions for you. Here is the setup you need for your integration tests:
Capybara.register_driver :chrome do |app|
options = Sele...
ActiveRecord: Creating many records works faster in a transaction
When you need to insert many records into the same table, performance may become an issue.
What you can do to save time is to open a transaction and save multiple records within that transaction:
transaction do
500.times { Model.create! }
end
Although you will still trigger 500 INSERT statements, they will complete considerably faster.
When I tried it out with a simple model and 500 iterations, the loop completed in 1.5 seconds vs. 6 seconds without a transaction.
Alternative
Another fast way to insert many ...
How to discard a surrounding Bundler environment
tl;dr: Ruby's Bundler environment is passed on to system calls, which may not be what you may want as it changes gem and binary lookup. Use Bundler.with_original_env to restore the environment's state before Bundler was launched. Do this whenever you want to execute shell commands inside other bundles.
Example outline
Consider this setup:
my_project/Gemfile # says: gem 'rails', '~> 3.0.0'
my_project/foo/Gemfile # says: gem 'rails', '~> 3.2.0'
And, just to confirm this, these are the installed Rails versions for each ...
Databases don't order rows unless you tell them so
There is no such thing as a "default order" of rows in database tables.
For instance, when you paginate a result set: When using LIMIT, it is important to use an ORDER BY clause that constrains the result rows into a unique order. Otherwise you will get an unpredictable subset of the query's rows. You might be asking for the tenth through twentieth rows, but tenth through twentieth in what ordering? The ordering is unknown, unless you specified ORDER BY.
In Rails, if you use Record.first or Record.last, it will default to orderin...
A different testing approach with Minitest and Fixtures
Slow test suites are a major pain point in projects, often due to RSpec and FactoryBot. Although minitest and fixtures are sometimes viewed as outdated, they can greatly improve test speed.
We adopted a project using minitest and fixtures, and while it required some initial refactoring and establishing good practices, the faster test suite was well worth it! Stick with me to explore how these tools might actually be a good practice.
So, why is this setup faster? Partially, it's because minitest is more lightweight than RSpec, which...
Configuring Git with .gitconfig
Basic configuration
Please keep this config simple. It should be a starting point for new developers learning Git.
[user]
name = Your Name
email = your.name@domain.com
[branch]
sort = -committerdate
[color]
ui = auto
[color "branch"]
current = yellow reverse
local = yellow
remote = green
[color "diff"]
whitespace = white reverse
meta = blue reverse
frag = blue reverse
old = red
new = green
[color "status"]
added = green
changed = yellow
untracked = cyan
[interactive]
singlekey = true # Do not requir...
In MySQL, a zero number equals any string
In MySQL comparing zero to a string 0 = "any string" is always true!
So when you want to compare a string with a value of an integer column, you have to cast your integer value into a string like follows:
SELECT * from posts WHERE CAST(posts.comments_count AS CHAR) = '200'
Of course this is usually not what you want to use for selecting your data as this might cause some expensive database operations. No indexes can be used and a full table scan will always be triggered.
If possible, cast the compared value in your application to...
Using a virtual column for trigram indexes in PostgreSQL
Full-text search can reach its limits in terms of flexibility and performance. In such cases, trigram indexes (pg_trgm) offer a lightweight alternative.
You can base the index on a virtual column that combines multiple text attributes. A stored virtual column stores the result of an expression as if it were a real column. It is automatically updated when the source columns change and can be indexed like normal data. This keeps your query logic consistent and avoids repeating string concatenation in every search.
def searc...
RSpec: Leverage the power of Capybara Finders and Matchers for view specs
View specs are a powerful tool to test several rendering paths by their cases instead of using a more costing feature spec. This is especially useful because they become quite convenient when used with Capybara::Node::Finders and Capybara::RSpecMatchers. This allows to wirte view unit specs as you can isolate specific part...
Case sensitivity in PostgreSQL
PostgreSQL, unlike MySQL, treats strings as case sensitive in all circumstances.
This includes
- comparison with
=andLIKE - collision detection in unique indexes
Usually this is fine, but some strings (like emails and usernames) should typically be treated as case insensitive.
There are a few workarounds available:
- use the citext extension (not recommended)
- use
ILIKEinstead ofLIKE - use Postgres'
lower()function - add an index on
lower(email)
Probably th...
Use CSS "text-overflow" to truncate long texts
When using Rails to truncate strings, you may end up with strings that are still too long for their container or are not as long as they could be. You can get a prettier result using stylesheets.
The CSS property text-overflow: ellipsis has been around for quite a long time now but since Firefox did not support it for ages, you did not use it. Since Firefox 7 you can!
Note that this only works for single-line texts. If you want to truncate tests across multiple lines, use a JavaScript solution like...
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)
...
RSpec: automatic creation of VCR cassettes
This RailsCast demonstrated a very convenient method to activate VCR for a spec by simply tagging it with :vcr.
For RSpec3 the code looks almost the same with a few minor changes. If you have the vcr and webmock gems installed, simply include:
# spec/support/vcr.rb
VCR.configure do |c|
c.cassette_library_dir = Rails.root.join("spec", "vcr")
c.hook_into :webmock
end
RSpec.configure do |c|
c.around(:each, :vcr) do |example|
name = example.metadata[:full_descripti...
Using the ActiveSupport::BroadcastLogger
The ActiveSupport::BroadcastLogger allows you to log to multiple sinks. You know this behavior from from the rails server command, that both logs to standard out and the log/development.log file.
Here is an example from the ActiveSupport::BroadcastLogger API:
stdout_logger = ActiveSupport::Logger.new(STDOUT)
file_logger = ActiveSupport::Logger.new("development.log")
broadcast = ActiveSupport::BroadcastLogger.new(stdout_logger, file_logger)
broadcast.i...
Using Passenger Standalone for development
For our production servers we use Passenger as a Ruby application server. While it is possible to use Passenger for development as an Apache module, the installation process is not for the faint of heart.
Luckily Passenger also comes as a standalone binary which requires zero configuration.
You can Passenger Standalone as a replacement for Webrick or Thin if you'd like to:
- Use SSL certificates locally
- Get performance behavior that is closer to ...
Local development with SSL and Puma
Sometimes the need arises for SSL in local development. We have guides for different webservers, this one is for puma.
-
make sure mkcert is installed
-
create an SSL certificate for localhost with mkcert:
$ mkcert-v1.4.4-linux-amd64 localhost
Created a new local CA 💥
...
- use the certificate in the Puma config
config/puma.rb:
localhost_key = "#{File.join('localhos...
How to: Use git bisect to find bugs and regressions
Git allows you to do a binary search across commits to hunt down the commit that introduced a bug.
Given you are currently on your branch's HEAD that is not working as expected, an example workflow could be:
git bisect start # Start bisecting
git bisect bad # Tag the revision you are currently on (HEAD) as bad. You could also pass a commit's SHA1 like below:
git bisect good abcdef12345678 # Give the SHA1 of any commit that was working as it should
# shorthand:
git bisect start <bad ref> <good ref>
Git will fetch a comm...
New gem: Rack::SteadyETag
Rack::SteadyETag is a Rack middleware that generates the same default ETag for responses that only differ in CSRF tokens or CSP nonces.
By default Rails uses Rack::ETag to generate ETag headers by hashing the response body. In theory this would enable caching for multiple requests to the same resourc...