Rails 5.1 do not compile asset in test vs asset is not present in the asset pipeline

We don’t want to compile assets during test runs, since that is slow, but we also don’t want the asset pipeline to fail because assets are missing.

This will not work if you plan on doing javascript integration tests, but everything else should work fine.

Rails 5.1 added a flag for this which prints deprecations and will be removed in rails 5.2 so that is not a elegant solution either.

config.assets.unknown_asset_fallback = true

So we are now using this fix to fake assets being available!:

# config/environments/test.rb
# make our tests fast by avoiding asset compilation
# but do not raise when assets are not compiled either
Rails.application.config.assets.compile = false
Sprockets::Rails::Helper.prepend(Module.new do
  def resolve_asset_path(path, *)
    super || path
  end
end)

WebBrick startup is slow if your machine name looks like a domain

Socket.gethostbyname is usually fast if your local machine has a normal name, because it crashes early, but if you have a name that looks like a real domain things take 5s.

Internally webbrick/config.rb does:

ruby -r socket -e 'Socket.gethostbyname(Socket.gethostname)'

Which is slow … wait for https://bugs.ruby-lang.org/issues/13007 to resolve … or rename your localhost to something that does not look like a domain to ruby.

Ruby Code Duplication Detection with Flay

Flay is terribly useful, but has terribly usability …

If the repo was not such a mess I’d make PRs to fix it, but tests are not even runnable and PRs to make the Readme readabe got rejected … so I’m not going to bother … a small excerpt from Samson
More config options can be found on it’s homepage
Just wanted to share this useful rake task which found a bunch of duplication and already prevented me from adding new duplication twice 🙂

desc "Analyze for code duplication (large, identical syntax trees) with fuzzy matching."
task :flay do
  require 'flay' # do not require in production
  files = Dir["{config,lib,app/**/*.{rb,erb}"]
  files -= [
    # Things you want to ignore
  ]
  flay = Flay.run([*files, '--mass', '25']) # mass threshold is shown mass / occurrences
  abort "Code duplication found" if flay.report.any?
end

Making Rails 4 and 3 share signed cookies

Rails 4 by default wants to upgrade all cookies, which makes rails 3 unable to read them. But we want that to work since we let rails 3 and 4 run in parallel to test performance (which is terrible on rails 4 … )

# While we run servers with rails 3 and rails 4 we don't want to encrypt our cookie
# once everything is on rails 4 we can by using the upgrade signed to encrypted strategy
# tested via test/integration/rails_compatibility_test.rb
if RAILS4
  ActionDispatch::Cookies::ChainedCookieJars.class_eval do
    def signed_or_encrypted
      signed
    end
  end

  # do not update ... compare to action_dispatch/middleware/cookies.rb:184
  ActionDispatch::Cookies::UpgradeLegacySignedCookieJar.class_eval do
    def initialize(*args)
      super
      @verifier = @legacy_verifier
    end

    def verify_and_upgrade_legacy_signed_message(name, signed_message)
      deserialize(name, @legacy_verifier.verify(signed_message))
    rescue ActiveSupport::MessageVerifier::InvalidSignature
      nil
    end
  end
end

Render nested Rails object errors

  • No looping
  • Infinitely deep
  • Html safe

nested-errors-wordpress

  # application_helper.rb
  def render_nested_errors(object, seen=Set.new)
    return "" if seen.include?(object)
    seen << object
    return "" if object.errors.empty?

    content_tag :ul do
      lis = object.errors.map do |attribute, message|
        content_tag(:li) do
          content = "".html_safe
          content << object.errors.full_message(attribute, message)
          values = (object.respond_to?(attribute) ? Array.wrap(object.send(attribute)) : [])
          if values.first.is_a?(ActiveRecord::Base)
            values.each do |value|
              content << render_nested_errors(value, seen)
            end
          end
          content
        end
      end
      safe_join lis
    end
  end

  # application_helper_test.rb
  
  describe "#render_nested_errors" do
    # simulate what erb will do so we can see html_safe issues
    def render
      ERB::Util.html_escape(render_nested_errors(stage))
    end

    let(:stage) { stages(:test_staging) }

    it "renders nothing for valid" do
      render.must_equal ""
    end

    it "renders simple errors" do
      stage.errors.add(:base, "Kaboom")
      render.must_equal "<ul><li>Kaboom</li></ul>"
    end

    it "renders nested errors" do
      stage.errors.add(:deploy_groups, "Invalid") # happens on save normally .. not a helpful message for our users
      stage.errors.add(:base, "BASE") # other error to make sure nesting is correct
      stage.deploy_groups.to_a.first.errors.add(:base, "Kaboom")
      render.must_equal "<ul><li>Deploy groups Invalid<ul><li>Kaboom</li></ul></li><li>BASE</li></ul>"
    end

    it "does not loop" do
      stage.errors.add(:project, "Invalid")
      stage.project.stubs(stages: [stage])
      stage.project.errors.add(:stages, "Invalid")
      render.must_equal "<ul><li>Project Invalid<ul><li>Stages Invalid</li></ul></li></ul>"
    end

    it "cannot inject html" do
      stage.errors.add(:deploy_groups, "<foo>")
      stage.errors.add(:base, "<bar>")
      stage.deploy_groups.to_a.first.errors.add(:base, "<baz>")
      render.must_equal "<ul><li>Deploy groups &lt;foo&gt;<ul><li>&lt;baz&gt;</li></ul></li><li>&lt;bar&gt;</li></ul>"
    end
  end