Optimistic locking in Rails

Tagged rails, edit conflict, optimistic lock, 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 postgresql, array, form_for, string  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 scss, extend, rails, sprockets  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'

How to encrypt/decrypt a file with GPG

Tagged gpg, password, decrypt, encrypt  Languages bash

First, install GPG:

brew install gpg

To encrypt a file with GPG, run:

gpg -c secrets.txt

To decrypt the encrypted and password-protected GPG file, run:

gpg secrets.txt.gpg

failed to push some refs to

Tagged git, pull, push, rebase  Languages bash

git push fails:

❯ git push
To github.com:xxx/yyy.git
 ! [rejected]        master -> master (fetch first)
error: failed to push some refs to 'git@github.com:xxx/yyy.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Use pull and rebase to rewind and replay your commit, otherwise you will end up with a “merge commit” in your git history:

❯ git pull --rebase
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 3 (delta 2), reused 3 (delta 2), pack-reused 0
Unpacking objects: 100% (3/3), done.
From github.com:xxx/yyy
   aaa..bbb  master     -> origin/master
First, rewinding head to replay your work on top of it...
Applying: <Your commit message>.

How to Undo Almost Anything With Git: https://github.blog/2015-06-08-how-to-undo-almost-anything-with-git/

Git Pretty - Git Flowchart: http://justinhileman.info/article/git-pretty/

Oh Shit, Git!?!: https://ohshitgit.com/

Running Rubocop on commit, changes, or branch

Tagged git, branch, rubocop  Languages 
  • To RuboCop the current commit
git diff-tree --no-commit-id --name-only -r HEAD --diff-filter AMT | xargs bundle exec rubocop
  • To RuboCop the working tree changes
git diff --name-only --diff-filter AMT | xargs bundle exec rubocop
  • To RuboCop all of the changes from the branch
git diff --name-only master --diff-filter AMT | xargs bundle exec rubocop

From https://about.gitlab.com/handbook/tools-and-tips/rubocop/

Also see:

Procfile dependencies

Tagged procfile, sleep, dependencies, foreman, overmind  Languages bash

You can use the sleep command as a way of specifying dependencies in a Procfile even if dependencies are not explicitly supported.

For example:

redis:        redis-server
sidekiq:      sleep 5; bundle exec sidekiq
rails:        sleep 5; bundle exec rails
proxy:        sleep 10; haproxy -c haproxy.cfg

SSH jump host

Tagged host, jump, ssh  Languages bash
# Connect to server-b by going through server-a (you => server-a => server-b):
$ ssh -t <user>@<server-a> ssh <server-b>

How to solve "CreateContainerConfigError" in Kubernetes

Tagged kubectl, k8s, createcontainerconfigerror  Languages bash

To solve CreateContainerConfigError you can try this command:

$ kubectl describe pod xxx-86cbd7555b-2zrs2
...
...
  Warning  Failed     75s (x8 over 2m50s)  kubelet, k8s-node1  Error: couldn't find key XXX in ConfigMap default/xxx
...