Category TILs

Thread.new.join

command = Thread.new do
  # do something
end
command.join

The join method is used to wait for the thread to complete before continuing with the rest of the program. This ensures that any work performed by the thread has finished before moving on to the next task.

Devise paranoid

Problem

When trying to reset password with not exists email => “Email not found”

Cause

Normal action of devise but not good if someone want to know your user email

Solution

Use devise paranoid mode

# It will change confirmation, password recovery and other workflows
# to behave the same regardless if the e-mail provided was right or wrong.
# Does not affect registerable.
config.paranoid = true

Confident Rails ENV

Problem

Sometimes after deployment my app did not work because of missing ENV

Cause

Currently my code look like

vault_token = ENV['VAULT_TOKEN']

and when I forgot to add VAULT_TOKEN to .env file it simple return nil so my app failed

Solution

Thanks to Confident Ruby and rubocop, I totally move to fetch

vault_token = ENV.fetch('VAULT_TOKEN')
# => in `fetch': key not found: "VAULT_TOKEN" (KeyError)

fetch also have options for default value, for block…
FYI: https://apidock.com/ruby/Hash/fetch

Rails 6.1 form_with update

Problem

My form submit process as WidgetsController#create as HTML
My expectation: WidgetsController#create as JS

Cause

Due to this PR

Rails 6.0: form_with would generate a remote form by default.
Rails 6.1: form_with would generate a non-remote form by default.

Solution

Update config config/environments/*.rb

config.action_view.form_with_generates_remote_forms = true

Spread object support for safari 10

Problem

I got error message “Unexpected token ‘…’. Expected a property name.” when using Safari 10.

Cause

Due to Document Spread in object literals only support Safari version 11.1 and later.

Solution

Using this plugin to parse spread to Object assign

{
  "plugins": ["@babel/plugin-proposal-object-rest-spread"]
}

In

z = { x, ...y };

Out

z = Object.assign({ x }, y);

GraphQL Invalid IAP credentials: empty token

Tech stacks:
  • Google app engine with Identity-Aware Proxy turned on
  • GraphQL with Apollo Server Express
Problem
const { ApolloServer } = require('apollo-server');
const { typeDefs, resolvers } = require('./schema');

const server = new ApolloServer({
  typeDefs,
  resolvers,
  introspection: true,
  playground: true,
});

server.listen().then(({ url }) => {
  console.log(`🚀 Server ready at ${url}`);
});
Cause
  • Request POST /graphql return 401 errors
    Digging in that request we got "credentials": "omit"
Solution
...

const server = new ApolloServer({
  typeDefs,
  resolvers,
  introspection: true,
  playground: {
    settings: {
      'request.credentials': 'include'
    }
  },
});

...

You dont need Momentjs

Problem

The format I need: 2020-04-20T16:20:00+09:00

Some code smell I wrote :-ss

const standardizeDate = (date) => {
    date.setHours(date.getHours() + 9);
    return date.toISOString().slice(0, -5) + "+09:00";
}

This one looks much better:

<script src="https://unpkg.com/dayjs@1.8.21/dayjs.min.js"></script>
const standardizeDate = (date) => dayjs(date).format('YYYY-MM-DDTHH:mm:ssZ');
Performance concerns

Large bundle size

Shout out to You-Dont-Need-Momentjs and also dayjs

Using Puppeteer in Google Cloud Functions

Setup

package.js

{
  "name": "sample-http",
  "version": "0.0.1",
  "dependencies": {
    "puppeteer": "^1.9.0"
  }
}
Problem
const browser = await puppeteer.launch();
// Error: Failed to launch chrome!
Solution
const browser = await puppeteer.launch({
            args: [
                '--no-sandbox',
                '--disable-setuid-sandbox',
            ]
        });

Reference to puppeteerl

Better Rails where like query

Problem

user_name = "I'm Tony"
User.where("name like '%#{user_name}%'")
# => SELECT  `users`.* FROM `users` WHERE (name like '%I'm Tony%')
=> ActiveRecord::StatementInvalid: Mysql2::Error: You have an error in your SQL syntax;

Better solution

User.where("name like ?", "%#{user_name}%")
# => SELECT  `users`.* FROM `users` WHERE (name like '%I\'m Tony%')

Set basic auth for specific environment

For all environment

Set authentication in ApplicationController

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  http_basic_authenticate_with name: 'username', password: 'password'
end
For specific environment

Set authentication in environment.rb, using rack middleware

# config/environments/staging.rb
# also can set for other environment (development, clone, preview...)
Rails.application.configure do
  config.middleware.use Rack::Auth::Basic, "Protected Environment" do |username, password|
    username == "username" && password == "password"
  end
end

Rails cache fetch

Problem
def generate_job_id
  cached_value = Rails.cache.fetch("cache_key", expires_in: 24.hours) do

    job_ids = Job.all.map(&:id) # Trillion jobs

    return [] if job_ids.blank?
    job_ids
  end
  cached_value
end

=> Missing cache when job_ids blank, cached_value is nil

def generate_job_id
  cached_value = Rails.cache.fetch("cache_key", expires_in: 24.hours) do

    job_ids = Job.all.map(&:id) # Trillion jobs

    break [] if job_ids.blank?
    job_ids
  end
  cached_value
end

=> Missing cache when job_ids blank, cached_value is []

def generate_job_id
  cached_value = Rails.cache.fetch("cache_key", expires_in: 24.hours) do

    job_ids = Job.all.map(&:id) # Trillion jobs

    job_ids.blank? ? [] : job_ids
  end
  cached_value
end

=> Problem solved, “cache_key”

Reference to https://github.com/rails/rails/blob/a08a069acdbea8a74282f753a2498d0d7b0c3137/activesupport/lib/active_support/cache.rb#L31 Exactly function

#https://github.com/rails/rails/blob/master/activesupport/lib/active_support/cache.rb#L737

def save_block_result_to_cache(name, **options)
  result = instrument(:generate, name, options) do
    yield(name)
  end

  write(name, result, options) unless result.nil? && options[:skip_nil]
  result
end

The line write is not executed =>

Ruby Array uniq

List post
post_a (topic_name: "Ruby", priority: 10)
post_b (topic_name: "PHP", priority: 9)
post_c (topic_name: "Ruby", priority: 8)

Expect each topic has only 1 post, order by priority

current_posts
# => [["Ruby", post_a], ["PHP", post_b], ["Ruby", post_c]]
to_h
current_posts.to_h
# => {"Ruby" => post_c, "PHP" => post_b}

=> post_c with lower priority is above post_b => ERROR

uniq
current_posts.uniq(&:first).to_h
# => {"Ruby" => post_a, "PHP" => post_b}

=> as expected

Rails Size & Count

Mỗi khi exec query, Active Record sẽ tạo ra biến @loaded = true cho Relation đó.

@posts = Post.all

@posts.loaded? #=>true

Hàm sizecount

def size
  loaded? ? @records.length : count(:all)
end
def count(column_name = nil, options = {})
  # [...]
  calculate(:count, column_name, options)
end

count luôn luôn sinh ra query, còn size sẽ check relation và query nếu cần.

Rõ ràng là cần cân nhắc dùng count để tránh dư thừa query.

Ruby Set

Ruby Set - a list of unique items.
Two special attributes:

  • Fast lookup times (with include?)
  • Unique values

Benchmark #include?

# Ruby 2.5.0
set   include: 8381985.2 i/s
array include: 703305.5  i/s - 11.92x  slower

Credit to rubyguides

HTML target _blank and blank

<a> tag document

<a target="_blank|_self|_parent|_top|framename">
  • _blank always opens url in new window
  • blank means framename, opens url in a window named blank if exists, otherwise open new window and name it blank
  • you possible to set any name for target
    eg: <a target="new_window">

Happy 420

#happy420 #happybirthday

Happy 420

Bootstrap 4 grid tier & reordering

Grid option

From v4.0.0-alpha.6, the xs tier no longer requires a breakpoint abbreviation

bootstrap-v4-grid

Reordering

From v4.0.0-beta, use .order- classes for controlling the visual order of your content

PC view

| Column A | Column B |

Mobile view

| Column B |
| Column A |

Boolean type in Rails & Mysql

MySQL BOOLEAN data type, which is the synonym of TINYINT(1), value range [-128..127]

Confused situation
id (integer) name (string) status (boolean)
1 Aoi 1
2 Maria 10


# Rails 4
Idol.find(2).status
=> false

# Rails 5
Idol.find(2).status
=> true
Explanation

Rails 4 boolean.rb

def cast_value(value)
  if value == ''
    nil
  elsif TRUE_VALUES.include?(value)
    true
  else
    # [...]
    false
  end
end

Rails 5 boolean.rb

def cast_value(value)
  if value == ""
    nil
  else
    !FALSE_VALUES.include?(value)
  end
end


Be careful when use find_by

City.find_by(name: "Maria") || City.find_by(name: "Aoi")

Liệu có giống với:

City.find_by(name: ["Maria", "Aoi"])

Trông thì có vẻ giống đó, thử check SQL câu dưới xem

Idol.find_by(name: ["Maria", "Aoi"])
# => SELECT  `idols`.* FROM `idols` WHERE `idols`.`name` IN ('Maria', 'Aoi') LIMIT 1

Trông vẫn có vẻ ổn, nhưng câu này lại trả ra Aoi thay vì Maria như mình mong đợi.

Vấn đề là hàm find_by(arg, *args) được định nghĩa như sau:

Finds the first record matching the specified conditions. There is no implied ordering so if order matters, you should specify it yourself. If no record is found, returns nil.

# File activerecord/lib/active_record/relation/finder_methods.rb, line 80
def find_by(arg, *args)
  where(arg, *args).take
rescue ::RangeError
  nil
end

no implied ordering, nên khi gặp Aoi với id nhỏ hơn sẽ trả ra kết quả luôn thay vì tìm Maria trước theo như argument mà ta truyền vào.

Để có được kết quả như mong đợi mình phải dùng where và thêm order:

Idol.where(name: ["Maria", "Aoi"]).order("FIELD(name, ('Maria, Aoi'))").take
# => SELECT  `idols`.* FROM `idols` WHERE `idols`.`name` IN ('Maria', 'Aoi') ORDER BY FIELD(name, ('Maria, Aoi')) LIMIT 1

Dùng 2 account github trên cùng 1 thiết bị

Config lại file ~/.ssh/config

# Account của công ty
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_rsa

# Account cá nhân
Host github-me
HostName github.com
User git
IdentityFile ~/.ssh/id_rsa_me

Khi Clone thì chú ý url, sửa git@github.com thành git@github-me

git clone git@github-me:account_name/repo_name.git

Check lại remote:

git remote -v
origin	git@github-me:account_name/repo_name.git (fetch)
origin	git@github-me:account_name/repo_name.git (push)

Config author
  • Add config system
    # /etc/gitconfig
    [user]
     email = account@company.com
     name = company
    
  • Add config local
    # your_personal_repo/.git/config
    [user]
     email = account@me.com
     name = me