Today I Learned

16 posts by ireneuszskrobiś

rack difference after upgrade from 2.0.7 to 2.2.2

I had to update rack because of the security issues with the previously used version. Unfortunately specs for this controller:

module Internal
  module Dashboard
    class StatisticsController < Sinatra::Base
      include Serial::RailsHelpers

      use Rack::Auth::Basic, 'Protected Area' do |username, password|
        username == App.config.internal_basic_auth_name &&
          password == App.config.internal_basic_auth_password
      end

      get '/', provides: :json do
        statistics = InternalStatistics.new(year: params[:year], month: params[:month])

        serialize(statistics).to_json
      end

      private

      def view_context
        Rails.application.routes.url_helpers
      end
    end
  end
end

started to fail in one specific case:

require 'rails_helper'

RSpec.describe Internal::Dashboard::StatisticsController do
  def post(params: {}, token:)
    env = {
      'REQUEST_METHOD' => 'GET',
      'CONTENT_TYPE' => 'application/json',
      'QUERY_STRING' => "&#{params.to_param}",
      'HTTP_AUTHORIZATION' => "Basic #{token}",
      'rack.input' => StringIO.new('')
    }

    status, _headers, response_body = Internal::Dashboard::StatisticsController.new.call(env)
    { status: status, body: response_body.first }
  end

  it 'rejects unauthorized requests' do
    response = post(token: nil)
    
    expect(response[:status]).to eq 401
  end
end

I started to get 400 instead of 401. Because I was not familiar with the code I started to digging in if this is a real problem and what causing it.

After comparing the code in rack gem I have noticed that basic? method has changed.

From (rack-2.0.7/lib/rack/auth/basic.rb)

def basic?
  "basic" == scheme
end

to (rack-2.2.2/lib/rack/auth/basic.rb)

def basic?
  "basic" == scheme && credentials.length == 2
end

So it is a correct behaviour now but if we want to get 401 status in specs we have to pass wrong (not empty) HTTP_AUTHORIZATION which we can do like this:

it 'rejects unauthorized requests' do
  response = post(token: Base64.strict_encode64(":"))
  
  expect(response[:status]).to eq 401
end

Pre-filtering table to speed up query in MS SQL

In one of my tasks I had to edit enormous query that is responsible for displaying complicated table on one of our views. As a part of this task I had to join table and take the first element of it to perform further calculation. The solution looks somehting like this:

# lots of sql here...

LEFT JOIN (
  SELECT
    id,
    post_id,
    ROW_NUMBER() OVER (
      PARTITION BY post_id
      ORDER BY id DESC
    ) row_num
  FROM
    comments
  WHERE
    deleted_at IS NULL
) c ON c.post_id = author_stats.post_id AND row_num = 1
LEFT JOIN ...

# lots of sql here...

Unfortunately, this happen to be quite slow because of the enormous number of data we were working on.

So by changing the offending query to specifically pre-filtering the posts table I was able to get the query from 15 seconds to 3 (in the biggest account -> which was acceptable):

DECLARE @comments_sub TABLE (id INT, post_id INT )
 
INSERT INTO @comments_sub (id, post_id)
  SELECT c.id, c.bond_id
  FROM (
    SELECT
      id,
      lcc.post_id,
      ROW_NUMBER() OVER (
        PARTITION BY c.bond_id
        ORDER BY id DESC
    ) row_num  
  FROM comments c
  INNER JOIN listed_comments lc ON c.post_id = lc.post_id
  WHERE deleted_at IS NULL) c
  WHERE c.row_num = 1

and then in query instead of the first snipped we can just use:

# lots of sql here...

LEFT JOIN @comments_sub c on c.post_id = author_stats.post_id
LEFT JOIN ...

# lots of sql here...

Accessing helpers and paths in Ruby On Rails console

You can access path/url in two ways:

1) using app, for instance: app.game_path(1)

2) including Rails.application.routes.url_helpers, for instance:

include Rails.application.routes.url_helpers
game_path(1)

both will result with a string => "/games/1"

The above knowledge will be handy if you are going to use helpers with paths.

Accessing helpers in rails console

The easiest way to do this is through helper object, for instance

helper.number_to_currency(100) => "$100.00"

helper.link_to("test", app.game_path(1), method: :get) =>

"<a data-method=\"get\" href=\"/games/1\">test</a>"

The nice thing about is that you can use your helpers in the same fashion:

Some real life usage:

module EventsHelper
  def event_form_header(event)
    content_tag :h2, class: 'text-center pull-left' do
      if event.game
        "#{event.category.capitalize} for #{event.game.title}"
      else
        "Global #{event.category.capitalize}"
      end
    end
  end
end

=>

"<h2 class=\"text-center pull-left\">Cash_ladder for Gentleman Test Game</h2>"

Unfortunately, if you want to use your helper in console and this helper uses path inside you will have to first include Rails.application.routes.url_helpers in this helper (which is of course not needed if you are using it in views)

module PlayerHelper
  include Rails.application.routes.url_helpers

  def user_player_info(player)
    if player.user.removed_by_user?
      'User removed his account'
    else
      content_tag(:div, player.user.username) +
        link_to(player.reference,
                game_player_path(player.game, player),
                class: 'highlight')
    end
  end
end

helper.user_player_info(Player.last) =>

"<div>Varian</div><a class=\"highlight\" href=\"/games/2-hots/players/3\">Thrall</a>"

Ruby On Rails console sandbox mode

If you want to perform some data migration/modification on production and you want to be sure that it will not break anything you can always perform a test run using rails console with --sandbox flag.

After entering console with

rails console production --sandbox

you will get following information

Loading production environment in sandbox (Rails X.X.X)
Any modifications you make will be rolled back on exit

You can now safely perform data migration and check if everything is OK.

Truncate database in Ruby on Rails 6

Insteaed of chaining rails db:drop, rails db:create and rails db:migrate we are finally able to use one commend that will clean databse in case you have to seed it one more time:

rails db:truncate_all

NOTE: In older version of rails you can always use database_cleaner gem. Thanks to this gem you can run DatabaseCleaner.clean_with :truncation in rails console or create a rake task on your own.

Ruby on Rails with RSpec - yielding multiple arguments

If you are yielding many things in one yield statement, for instance:

def each
  author.books.each do |book|
    yield BookDecorator.new(book), EpicBookDecorator.new(book)
  end
end

you are able to test it in RSpec using yield_successive_args with arguments packed within an array:

expect { |b| book_collection.each(&b) }.to yield_successive_args(
  [book_decorator1, epic_book_decorator1],
  [book_decorator2, epic_book_decorator2]
)

Login to Ruby on Rails console with email and password

If you ever need to protect your rails console (or need to authenticate for any other reason) you can create a new file in config/initializers with code like this:

module Rails
  module ConsoleMethods
    def self.included(_base)
      puts 'Enter your email:'
      email = gets
      user = User.find_by(email: email.strip)
      unless user
        puts 'Email not found in database! Exiting...'
        exit
      end

      puts 'Enter your password:'
      pass = $stdin.noecho(&:gets)
      if user.valid_password?(pass.strip)
        puts "Welcome #{user.email}!"
      else
        puts 'Provided password is not correct! Exiting...'
        exit
      end
    end
  end
end

You can of course add condition so this code will be executed only on production and/or staging.

Note 1: If you are using devise for authentication as it is is done in my example above please make sure that your file is loaded after config/initializers/devise.rb which means that your file’s name should be “bigger” than devise.rb, for example: perform_authentication_in_console.rb

Note 2: with ruby version “2.3” or higher you are able to use IO::console.getpass instead of $stdin.noecho(&:gets)

Extend Ruby On Rails console with custom methods

If you find yourself executing complicated code to get some information in rails console and you know that you might do this many times in the futere you can consider extending Rails::ConsoleMethods.

Create a new file in config/initializers directory, for example:

config/initializers/custom_console_methods.rb

in which you can define your module and prepend it to Rails::ConsoleMethods. The most trival example would be:

module CustomConsoleMethods
  def user(id)
    User.find(id)
  end
  alias_method :u, :user
end

require 'rails/console/helpers'
Rails::ConsoleMethods.prepend(CustomConsoleMethods)

rails, postgres & jsonb; find if key exists using where

Let’s assume that we have an ActiveRecord model called Day with jsonb field called references.

In the references field we stores property ids with some references, for example:

references: {'1' => 'ref123', '2' => 'ref456'}

If we want to find all days that does not have reference for a specific property id we can do this with following command:

Day.where("(references ->> '?')::text IS NULL", property.id)