Escaping strings in PostgreSQL queries

Tagged sql, postgres, escape  Languages sql

PostgreSQL queries containing, for example, single quotes or semicolons need to be escaped.

This won’t work:

UPDATE jobs SET work=':'';

To make it work, escape the strings using C-style escapes (E’’) and replacing single-quotes with ‘’:

UPDATE jobs SET work=E':''';

See section “4.1.2.2. String Constants with C-style Escapes” in the PostgreSQL documentation https://www.postgresql.org/docs/10/sql-syntax-lexical.html for details.

How to use private Github repositories with Bundler

Tagged private, repository, bundler, git, github  Languages bash

Does your Gemfile reference private Github repositories?

Option 1: ENV variable

export BUNDLE_GITHUB__COM=username:password
export BUNDLE_GITHUB__COM=<personal-oauth-token>:x-oauth-basic

Option 2: bundle config

bundle config https://github.com/bundler/bundler.git username:password

Option 3: ~/.git-credentials

echo 'https://user:pass@example.com' >> ~/.git-credentials

References https://github.com/rubygems/bundler/pull/3898 https://git-scm.com/docs/git-credential-store

RabbitMQ Publisher Confirms with Bunny

Tagged publisher, bunny, confirms, rabbitmq  Languages ruby

Source https://github.com/ruby-amqp/bunny/blob/master/examples/guides/extensions/publisher_confirms.rb:

require "bunny"

puts "=> Demonstrating publisher confirms"
puts

conn = Bunny.new
conn.start

ch   = conn.create_channel
x    = ch.fanout("amq.fanout")
q    = ch.queue("", :exclusive => true).bind(x)

ch.confirm_select
1000.times do
  x.publish("")
end
ch.wait_for_confirms # blocks calling thread until all acks are received

sleep 0.2
puts "Received acks for all published messages. #{q.name} now has #{q.message_count} messages."

sleep 0.7
puts "Disconnecting..."
conn.close

How to call JavaScript code from Ruby

Tagged ruby, javascript, execjs, therubyracer  Languages js, ruby, javascript

Gemfile:

source 'https://rubygems.org'
gem "therubyracer"
# commonjs.rb provides 'require' needed to import modules
gem 'commonjs'

add_with_javascript.rb:

require 'v8'
require 'commonjs'
env = CommonJS::Environment.new(V8::Context.new, path: './node_modules')
env.require('add.js').add(2, 2)

node_modules/add.js:

function add(a, b) {
  // Require works, if the package is available in node_modules
  // require('xyz');
  // Console is not available by default
  // console.log(msg);
  return a + b;
}

exports.add = add;
$ bundle exec ruby add_with_javascript.rb

Tested with:

  • commonjs (0.2.7)
  • libv8 (3.16.14.19)
  • therubyracer (0.12.3)

Set the accept-language header in Capybara / Selenium WebDriver tests

Tagged selenium, ruby, capybara  Languages ruby

To change the accept-language header that Chrome sends with each request in Capybara tests, add the ‘selenium-webdriver (3.142.6)’ preference to the driver’s options:

  opts.add_preference('intl.accept_languages', 'sv')

Example:

Capybara.register_driver :chrome do |app|
  caps = Selenium::WebDriver::Remote::Capabilities.chrome(loggingPrefs: { browser: 'ALL' })
  opts = Selenium::WebDriver::Chrome::Options.new
  opts.add_preference('intl.accept_languages', 'sv')
  chrome_args = %w[--no-sandbox --disable-popup-blocking --enable-features=NetworkService,NetworkServiceInProcess --window-size=1920,1080]
  chrome_args.each { |arg| opts.add_argument(arg) }
  Capybara::Selenium::Driver.new(app, browser: :chrome, options: opts, desired_capabilities: caps)
end

Tested with:

  • capybara (3.30.3)
  • selenium-webdriver (3.142.6)

Which version of Ruby to use with Rails?

Tagged ruby, rails, version  Languages 

To know which version of Ruby is safe to use with Rails one option is to look at the Travis configuration, for example, Rails 5.2: https://github.com/rails/rails/blob/ee1278cc841a883835918fbedb70add96c84543b/.travis.yml#L64-L69

Note the allowed_failures configuration: https://github.com/rails/rails/blob/172bb64ca8d3f1cf88ca7daa4f6a58436e577c69/.travis.yml#L145-L148

This works for Rubygems too: https://github.com/bblimke/webmock/blob/a3c8de2550122d0998cb47292be0bbd2e3f203bf/.travis.yml#L4-L14

Optimistic locking in Rails

Tagged optimistic lock, edit conflict, rails, optimistic locking  Languages ruby, slim

This code is an adaptation of the code found in this Railscasts article: http://railscasts.com/episodes/59-optimistic-locking-revised?view=asciicast

app/models/concerns/optimistic_locking.rb

module OptimisticLocking
  extend ActiveSupport::Concern

  included do
    validate :handle_conflict, on: :update
  end

  def original_updated_at
    @original_updated_at || updated_at.to_f
  end
  attr_writer :original_updated_at

  def handle_conflict
    return unless @conflict || updated_at.to_f > original_updated_at.to_f

    @conflict = true
    # By setting original_updated_at to nil the edit conflict is ignored on a second form submit
    @original_updated_at = nil

    errors.add :base, I18n.t('activerecord.edit_conflict.message')
    changes.each do |attribute, values|
      errors.add(
        attribute,
        I18n.t('activerecord.edit_conflict.was', value: values.first)
      )
    end
  end
end

app/models/project.rb

class Project < ApplicationRecord
  include OptimisticLocking

app/views/_edit_conflict.slim

- object.errors[:base]&.each do |error|
  p.text-danger
    = icon('fas', 'exclamation-circle')
    =< error

app/views/projects/_form.slim

= form @project do |f|
  = render '/edit_conflict', object: @project
  = f.hidden_field :original_updated_at
  = f.text_field :name, class: 'form-control'
  = form_buttons(f, @project)

How to use Rails' form_for with a PostgreSQL array

Tagged array, form_for, string, postgresql  Languages ruby

Using Rails’ form_for with a PostgreSQL array, for example, user roles can be done as follows:

Migration:

class CreateProjectMembers < ActiveRecord::Migration[6.0]
  def change
    create_table :project_members, id: :uuid do |t|
      ...
      t.string :roles, array: true, default: []

Model:

class ProjectMember < ApplicationRecord
  validate :validate_roles
  ROLES = %w[administrator collaborator].freeze

  def validate_roles
    return if roles.is_a?(Array) && roles.all? { |d| ROLES.include?(d) }

    errors.add(:roles, :invalid)
  end
end

View (slim-lang):

= form @project_member do |f|
  .form-group
    = f.label :roles, 'Roles'
    = f.check_box :roles, { label: 'Administrator', multiple: true }, 'administrator', nil
    = f.check_box :roles, { label: 'Collaborator', multiple: true }, 'collaborator', nil

Controller:

class ProjectMembersController < ApplicationController
  ...
  def update
    if @project_member.update(project_member_params)
    ...
  end

  def project_member_params
    params.require(:project_member).permit(roles: [])
  end

Note that if you have a “has_many through” relationship, instead of a PostgreSQL array, you can simply use:

= f.collection_check_boxes(:locale_ids, Locale.all, :id, :language) do |b|
  b.check_box

Reference: https://apidock.com/rails/v6.0.0/ActionView/Helpers/FormOptionsHelper/collection_check_boxes

How to dump Postgres extensions and all schemas with Rails

Tagged migration, wtf, rails, lol  Languages bash, ruby

When running rake db:reset you might get one of the following informative errors:

  function public.gen_random_uuid() does not exist
  db/structure.sql:22: ERROR:  schema "public" already exists

To fix them tell Ruby to dump everything by editing config/application.rb:

module XXX
  class Application < Rails::Application
    config.active_record.dump_schemas = :all

Tested with Rails 6.

References: https://github.com/rails/rails/issues/17157#issuecomment-77400517

How to fix "if the extend should be able to fail" and "undefined variable" SCSS errors

Tagged extend, sprockets, scss, rails  Languages bash

If you get an error similar to one of these:

Use “@extend .navbar-inverse !optional” if the extend should be able to fail”:

Undefined variable: $some_variable

The solution might be the one explained here.

My issue was that I had the SCSS partials in the root folder like this:

❯ tree app/assets/stylesheets
app/assets/stylesheets
├── application.scss
├── _bootstrap-custom.scss
├── _bootstrap-overrides.scss
└── _theme.scss

The problem with this flat folder structure is that all the SCSS files, including partials, are compiled separately. This is the case with Rails’ Sprockets at least.

The solution for me was to move all partials into a subdirectory, like this:

❯ tree app/assets/stylesheets
app/assets/stylesheets
├── application.scss
├── partials
│   ├── _bootstrap-custom.scss
│   ├── _bootstrap-overrides.scss
│   ├── _theme.scss
└── vendor
    └── introjs.min.css

This way Sprockets, which compiles SCSS in Rails applications, will only compile the application.scss, which includes the partials.

References

https://github.com/rails/sprockets/blob/master/UPGRADING.md#manifestjs

You will have to edit the manifest.js to specify those files.

You may also find that some files that were not previously compiled as top-level targets now are. For instance, if your existing app has any js files directly at ./app/assets/javascripts or css/scss files ./app/assets/stylesheets, Rails with Sprockets 4 will now compile them as top-level targets. Since they were not previously treated as such, you probably don’t mean them to be; if they are .scss partials referencing variables meant to be defined in other files, it may even result in an error message that looks like Undefined variable: $some_variable.

To correct this, you can move these files to some subdirectory of ./app/assets/stylesheets or javascripts; or you can change the manifest.js to be more like how Rails with Sprockets 3 works, linking only the specific application files as top-level targets:

Rails log:

2019-12-19 15:52:49 +0200: Rack app error handling request { GET / }
#<ActionView::Template::Error: Error: "#nav" failed to @extend ".navbar-inverse".
       The selector ".navbar-inverse" was not found.
       Use "@extend .navbar-inverse !optional" if the extend should be able to fail.
        on line 64:11 of app/assets/stylesheets/_theme.scss
>>   @extend .navbar-inverse;

   ----------^
>
/Users/christian/projects/rails-app/app/assets/stylesheets/_theme.scss:64
/Users/christian/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/sassc-2.2.1/lib/sassc/engine.rb:49:in `render'
/Users/christian/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/sprockets-4.0.0/lib/sprockets/sassc_processor.rb:59:in `block in call'
/Users/christian/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/sprockets-4.0.0/lib/sprockets/utils.rb:138:in `module_include'
/Users/christian/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/sprockets-4.0.0/lib/sprockets/sassc_processor.rb:58:in `call'
/Users/christian/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/sprockets-4.0.0/lib/sprockets/sassc_processor.rb:31:in `call'
/Users/christian/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/sprockets-4.0.0/lib/sprockets/processor_utils.rb:84:in `call_processor'
/Users/christian/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/sprockets-4.0.0/lib/sprockets/processor_utils.rb:66:in `block in call_processors'
/Users/christian/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/sprockets-4.0.0/lib/sprockets/processor_utils.rb:65:in `reverse_each'
/Users/christian/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/sprockets-4.0.0/lib/sprockets/processor_utils.rb:65:in `call_processors'
/Users/christian/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/sprockets-4.0.0/lib/sprockets/processor_utils.rb:22:in `block in <class:CompositeProcessor>'
/Users/christian/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/sprockets-4.0.0/lib/sprockets/processor_utils.rb:33:in `call'
/Users/christian/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/sprockets-4.0.0/lib/sprockets/processor_utils.rb:84:in `call_processor'
/Users/christian/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/sprockets-4.0.0/lib/sprockets/processor_utils.rb:66:in `block in call_processors'
/Users/christian/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/sprockets-4.0.0/lib/sprockets/processor_utils.rb:65:in `reverse_each'
/Users/christian/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/sprockets-4.0.0/lib/sprockets/processor_utils.rb:65:in `call_processors'