Speed up JSON generation with oj

Using this gem I could get JSON generation from a large, nested Ruby hash down from 200ms to 2ms.

Its behavior differs from the default JSON.dump or to_json behavior in that it serializes Ruby symbols as ":symbol", and that it doesn't like an ActiveSupport::HasWithIndifferentAccess.

There are also some issues if you are on Rails < 4.1 and want it to replace #to_json (but you can always just call Oj.dump explicitely).

Security warning: Oj does not escape HTML entities in JSON
---------...

Make Less interpret the escape codes in a logfile

The unix command line tool less is a good choice for browsing logfiles. In the standard configuration, though, it does not interpret the escape sequences used in the rails logfiles. To enable this type:

less -R my_logfile.log

You can also have an alias to save yourself the typing

alias less='less -R'

Dynamic conditions for belongs_to, has_many and has_one associations

Note: Consider not doing this. Use form models or vanilla methods instead.


The :conditions option for Rails associations cannot take a lambda. This makes it hard to define conditions that must be evaluated at runtime, e.g. if the condition refers to the current date or other attributes.

A hack to fix this is to use faux string interpolation in a single-quoted :conditions string:

class User < ActiveRecord::Base
  has_many :contracts
  has_one :current_contract, :class_name => 'Contract', :conditions => '...

How to recognize CVE-2019-5418

If you get requests with values for formats like this:

{:locale=>[:de], :formats=>["../../../../../../../../../../etc/services{{"], :variants=>[], :handlers=>[:erb, :builder, :raw, :ruby, :coffee, :haml]}

or fails like this:

Invalid query parameters: invalid %-encoding (../../../../../../../../../etc/passwd%%0000.html)

Someone tries to exploit CVE-2019-5418.
If you use the latest Rails (or latest Rails LTS) you're...

RSpec: Where to put shared example groups

Shared example groups are a useful RSpec feature. Unfortunately the default directory structure generated by rspec-rails has no obvious place to put them.

I recommend storing them like this:

spec/models/shared_examples/foo.rb
spec/models/shared_examples/bar.rb
spec/models/shared_examples/baz.rb
spec/controllers/shared_examples/foo.rb
spec/controllers/shared_examples/bar.rb
spec/controllers/shared_examples/baz.rb

To ma...

PSA: Chrome and Firefox do not always clear session cookies on exit

Cookies without an expiration timestamp are called "session cookies". [1] They should only be kept until the end of the browsing session.

However, when Chrome or Firefox are configured to reopen tabs from last time upon start, they will keep session cookies when closing the browser. This even applies to tabs that were closed before shutting down the browser.

This is by design in Chrome and [Firefox](https://bugzilla.mozilla.org/buglist.cgi?bug_id=337551,345830,358042,362212,36...

8 steps for fixing other people's code

Guide how to make fixes in other people's GitHub repositories. It's basically "Open Source Development 101".

Way back in mid-2007, when Rails 1.2 was the new hotness and GitHub was still a year away from crawling out of the primordial internet soup, prolific open source contributor Dr Nic wrote an article titled “8 steps for fixing other people’s code”. (...)

Here in the fantastical future world of 2012, while we still don’t have hoverboards or household nuclear fusion, we do have some great tools that make fixing other people’s code...

Migrating to RSpec 2 from RSpec 1

You will need to upgrade to RSpec >= 2 and rspec-rails >= 2 for Rails 3. Here are some hints to get started:

  • In RSpec 2 the executable is rspec, not spec.
  • RSpec and rspec-rails have been completely refactored internally. All RSpec classes have been renamed from Spec::Something to RSpec::Something. This also means that every require 'spec/something' must now be require 'rspec/something'.
  • In spec_helper.rb, Spec::Runner.configure becomes RSpec.configure
  • It has become really hard to extend specific example groups ...

Upgrading Ruby from 1.8.7 to 2.3.5

Suggested Workflow

Set the ruby version in .ruby-version to 2.3.5, then perform these steps one by one, fixing errors as they occur:

  1. Update gems as listed below, and bundle
  2. Boot a Rails console - see below for a list of changes you will probably need
  3. Run Specs with --backtrace option
  4. Run Cucumber features (with Geordi's --debug option)
  5. When all tests are green, look through your Gemfile and remove as many version constraints as possible.
  6. Boot the application in different environements to spot further issues, e...

RSpec: How to test the content of a flash message in a request spec

The ActionDispatch module of Rails gives you the helper method flash to access the flash messages in a response.

describe PostsController, type: :request do

  describe 'update' do

    it 'shows a success message on update' do
      post_record = create(:post)

      put "/posts/#{post_record.id}"

      # Same as @request.flash[:alert]
      expect(flash[:alert]).to eq('Post updated successfully.')
    end

  end


end

Disabling Spring when debugging

Spring is a Rails application preloader. When debugging e.g. the rails gem, you'll be wondering why your raise, puts or debugger debugging statements have no effect. That's because Spring preloads and caches your application once and all consecutive calls to it will not see any changes in your debugged gem.

Howto

Disable spring with export DISABLE_SPRING=1 in your terminal. That will keep Spring at bay in that terminal session.

In Ruby, [you can only write environment variables that subproc...

How to: Solve gem loaded specs mutex

Use bundler > 1.15 to fix Gem::LOADED_SPECS_MUTEX (NameError).


Given the following project:

ruby -v
ruby 1.8.7

bundler -v
Bundler version 1.13.7

gem -v
1.8.30

rails -v
Rails 3.2.22.1

Running specs or features resulted in:

uninitialized constant Gem::LOADED_SPECS_MUTEX (NameError)

The previous settings described in Maximum version of Rubygems and Bundler for Ruby 1.8.7 and Rails 2.3 (even the rails version was rails 3.2 and not 2.3) seems not to work here, so I used (also described in the ca...

Headless Chrome: Changing the Accept-Language header is not possible

It seems like changing the HTTP_ACCEPT_LANGUAGE is not possible for a headless chrome.

  • On Ubuntu the headless Chrome derives the Accept-Language from the operation system
  • Adding the option options.add_argument('--lang=de') to the Capybara::Selenium::Driver has no effect
  • Adding the preference options.add_preference('intl.accept_languages', 'de') to the Capybara::Selenium::Driver has only effects if the --headless option is skipped (see bug ticket #775911)
  • Cha...

When sessions, cookies and Clearance tokens expire and how to change it

Expiration of Rails sessions

By default Rails sessions expire when the user closes her browser window.

To change this edit your config/initializers/session_store.rb like this:

ActionController::Base.session = {
  :key          => '...',
  :secret       => '...'
  :expire_after => 10.years
}

In older Railses the initializer is not available. Set the option in the environment.rb instead:

config.action_controller.session = {
  :key          => '...',
  :secret       => '...'

...

We have deprecated Rack::SteadyETag

Rack::SteadyETag was a Rack middleware that generates the same default ETag for responses that only differ in XOR-masked CSRF tokens or CSP nonces.

We have deprecated Rack::SteadyETag. We instead recommend reconfiguring your Rails app so two requests to the same resource produce the same HTML for a given user.

Spec "content_for" calls in helpers

This only applies to RSpec below version 1.3.2. The issue has been fixed in RSpec 1.3.2, and most likely RSpec 2 and later versions.


When you have a helper that calls content_for and want to check its behavior you should probably write a feature instead. If you still want to do it, mind the following.

Consider this helper:

module LayoutHelper
  def title(string)
    content_for :title, string
    string
  end
end

Somewhere in the layout we'd then say something like this: `<%= yield :title %...</p>

RSpec: How to compare ISO 8601 time strings with milliseconds

Rails includes milliseconds in Time / DateTime objects when rendering them as JSON:

JSON.parse(User.last.to_json)['created_at']
#=> "2001-01-01T00:00:00.000+00:00"

In RSpec you might want to use .to_json instead of .iso8601 to use the build-in eq matcher:

it 'returns the created at attribute of a user' do
  get '/users/1'
  
  expect(JSON.parse(response.body)['created_at']).to eq(Time.parse('2001-01-01').to_json)
end

Otherwise the strings do not match:

DateTime.parse('2001-01-01').to_s (will defa...

Heads up: Byebug has problems with zeitwerk

I encountered a unlucky behavior of byebug 11.1.3 (the most recent version at time of writing) when using it with Rails 6 and it's new autoloading component, zeitwerk. There already is a issue for that, so I hope it will be fixed with a future release.

The following test succeeds:

  context 'factories' do
    let(:test_case) { FactoryBot.create(:test_case) }
    it 'are valid' do
      expect(test_case).to be_valid
    end
  end

But when I did the same in byebug the foll...

Force absolute URLs for parts of a view or controller

You know that you can force absolute URLs throughout a response. Now you want to modify URLs similarly, but only in parts of a view (or controller) logic. Here is how.


Note: this has only been tested on a Rails 2 application. It should work similarly for Rails 3.


Put this into your ApplicationController:

def rewrite_options(*args)
  options = super
  options.merge!(:only_path => false) if @with_full_urls
  options
end...

How to disable cookies in cucumber tests

Unfortunately, Capybara does not offer a switch to disable cookies in your test browser. However, you can work around that by using a tiny Rack middleware -- it works for both Selenium and non-Selenium tests.


Wouldn't it be nice to say something like this?

Given cookies are disabled
When I try to sign in
Then I should see "Can't sign you in. Please enable cookies."

You can! Put the code below into some place like lib/rack/cookie_stripper.rb.

module Rack
  class CookieStripper
    
    ENABLED = false

...

Perform Sidekiq jobs immediately in development

# config/initializers/sidekiq.rb

# Perform Sidekiq jobs immediately in development,
# so you don't have to run a separate process.
# You'll also benefit from code reloading.
if Rails.env.development?
  require 'sidekiq/testing'
  Sidekiq::Testing.inline!
end

Postgresql: Paginate and count in one query using window functions

When paginating records, we usually need to know the number of total records in order to render pagination links. Popular pagination libraries like will_paginate or Kaminari do this for us by simply issuing an extra query, like this:

SELECT post.* FROM posts LIMIT 20 OFFSET 100;

SELECT COUNT(*) FROM posts;   

This is fine most of the time. But rarely, you might have very complicated WHERE conditions or a subquery that takes time to run. In thes...

Fix warning: No secret option provided to Rack::Session::Cookie

You will get this when you are using the latest version of Rails with a recent version of Rack:

SECURITY WARNING: No secret option provided to Rack::Session::Cookie.
This poses a security threat. It is strongly recommended that you
provide a secret to prevent exploits that may be possible from crafted
cookies. This will not be supported in future versions of Rack, and
future versions will even invalidate your existing user cookies.

The warning is caused by Rails calling Rack incorrectly. [It is unclear](https://github.c...

ActionMailer: How to send a test mail directly from the console

If your rails application is unable to send mails, it might be useful to debug your settings using the rails console. Here is a snippet that shows the current settings and lets you send a test mail directly from the console:

mailer = ActionMailer::Base.new

# check settings:
mailer.delivery_method # -> :smtp
mailer.smtp_settings # -> { address: "localhost", port: 25, domain: "localhost.localdomain", user_name: nil, password: nil, authentication: nil, enable_starttls_auto: true }

# send mail:
mailer.mail(from: 'sender@example.com', ...