コード日進月歩

しんくうの技術的な小話、メモ、つれづれ、など

ActiveRecordの内容に変化はないが、updated_atだけ更新したい場合はtouchを使用する

簡易な記録テーブルなどで、時刻などを更新をしたいときの豆知識

環境

$ rails -v
Rails 7.1.2

やり方

該当レコードで .touch をすると自動的に保存がされる

book.updated_at
# Thu, 01 Feb 2024 14:37:18.696490000 UTC +00:00

このようなレコードに .touch をすると現在時刻が入る。

book.touch
# true
book.updated_at
# Thu, 01 Feb 2024 14:37:37.490409000 UTC +00:00

応用編

任意の時間を設定することもできる。

book.touch(time: Time.new(2015, 2, 16, 0, 0, 0))
book.updated_at
# Sun, 01 Feb 2015 00:00:00.000000000 UTC +00:00

注意点

GitHubの該当コード部分 にも書いてあるが、特定のバリデーションは省略され、特定のコールバックしか動かないので注意

Please note that no validation is performed and only the +after_touch, +after_commit and +after_rollback callbacks are executed.

関連リンク

最新のトップレベルドメインのリストを見たいときはICANNのサイトにリストがある

最新リストを探したいときにどこを見ると良さそうなのかざっと調べる

前提としてTop Level Domain(TLD)とは

過去のまとめたものがあるので下記参照のこと

TLD と eTLD と eTLD+1に関してざっくりまとめる - コード日進月歩

TLDのリスト

TLDのリストは管理をしているICANNのサイトから見ることができる

List of Top-Level Domains - ICANN

なお、TLDの利用状況に関しては下記にドメインごとのレポートが用意されている

Monthly Registry Reports - ICANN

なお、現在Generic Top Level Domain(gTLD)は申請制のため、ICANNが敷く審査を通過すれば誰でも増やすことが可能。

関連リンク

Factory Botで数百を超えるレコードを作る場合はbuild_listとinsert_allと組み合わせると軽快に生成できる

すごく助かったのでメモがてら

環境

$ rails -v
Rails 7.1.2
factory_bot (6.4.4)
  activesupport (>= 5.0.0)
factory_bot_rails (6.4.2)
  factory_bot (~> 6.4)
  railties (>= 5.0.0)

やり方について

今回の事例

下記のように1000レコードを作成するようなテストを書く場合を想定する。

RSpec.describe SoccerMember, type: :model do
  context "1000人のメンバーが作成されたとき" do
    let!(:team) { create(:soccer_team) }
    before do
      ## この部分で SoccerMember を1000レコード分作る ##
    end
    it "正しい順番でならんでいること" do
      ## 略 ##
    end
  end
end

素直にcreate_listを使う場合と課題点

factoryBotにはcreate_listという機能があるので、素直に使うと以下のようになる。

RSpec.describe SoccerMember, type: :model do
  context "1000人のメンバーが作成されたとき" do
    let!(:team) { create(:soccer_team) }
    before do
      create_list(:soccer_member, 1000, soccer_team: team)
    end
    it "正しい順番でならんでいること" do
      ## 略 ##
    end
  end
end

ただし、この場合内部的には SoccerMember.create が1000回繰り返されてしまうため、SQLのINSERTが1000回実行することになるのでパフォーマンスとしてはよろしくはない。

大体案としての build_list + insert_all

やりたいことの内訳としては

  1. FactoryBotに定義したカラムの内容を生成してもらう
  2. それをRDB上にレコードとしてつくる

というような内訳になるので、1と2の作業を分離する。

1. カラムの内容生成

1の作業としてbuild_listでオブジェクトをつくる。

active_record_list = build_list(:soccer_member, 1000,
                                soccer_team: team,
                                created_at: Time.zone.now,
                                updated_at: Time.zone.now)

2. RDB上にレコードを作成する

2の作業として、insert_all で実行を行う。insert_allはActiveRecordのオブジェクトではなく、attributesのhashの配列がほしいので変換してあげる。

attributes_array = active_record_list.each_with_object([]) do |soccer_member , result_array|
  attribute_hash = soccer_member.attributes
  result_array.push(attribute_hash)
end

SoccerMember.insert_all(attributes_array)

もっと簡潔に書くと以下のようにも書ける

SoccerMember.insert_all(active_record_list.map(&:attributes))

注意点として、ActiveRecord独自のattributesを増やしているとそれも .attributes で取り出すときに含まれてしまうので注意。その場合はhashから取り除く必要がある。

実行時間の比較

比較として下記のように実行してみる。

RSpec.describe SoccerMember, type: :model do
  context "1000人のメンバーが作成されたとき" do
    let!(:team) { create(:soccer_team) }
    before do
      start_time = Time.zone.now.to_f

      create_list(:soccer_member, 1000, soccer_team: team)

      end_time = Time.zone.now.to_f
      elapsed_time = (end_time - start_time) * 1000
      puts "create_listのみ          生成時間: #{elapsed_time}ミリ秒"
    end
    it "正しい順番でならんでいること" do
      ## 略 ##
    end
  end

  context "1000人のメンバーが作成されたとき" do
    let!(:team) { create(:soccer_team) }
    before do
      start_time = Time.zone.now.to_f

      now_time = Time.zone.now
      # NOTE: 明示的にcreated_atとupdated_atを入れてあげないとnilになってしまってエラーになるので入れる
      active_record_list = build_list(:soccer_member, 1000,
                                      soccer_team: team,
                                      created_at: now_time,
                                      updated_at: now_time
      )
      SoccerMember.insert_all(active_record_list.map(&:attributes))

      end_time = Time.zone.now.to_f
      elapsed_time = (end_time - start_time) * 1000
      puts "build_list + insert_all 生成時間: #{elapsed_time}ミリ秒"
    end
    it "正しい順番でならんでいること" do
      ## 略 ##
    end
  end
end

そうなると結果は以下のようになり、だいぶ短縮された。

create_listのみ         生成時間: 2795.0141429901123ミリ秒
build_list + insert_all 生成時間: 140.3510570526123ミリ秒

参考リンク

CSSの詳細度(Specificity)に関してざっくりまとめる

いつも仕様を忘れてしまうので自身のメモとしてざっくりまとめる

仕様について

Selectors Level 4 - 17. Calculating a selector’s specificity

詳細度と他の要素の関係

詳細度以前に順番を決めるルールが存在するが、それは別の記事で記載したのでそちらを参考のこと。

2024年1月時点のCSS記述の採用順番についてざっくり理解する - コード日進月歩

ルールについて

以下の内容で決まる

詳細度のA,B,Cのカウントで勝る内容(A,B,Cに関しては後述) 1. Aの数を比べて数値が高い方 2. Aが同じ場合、Bの数値が高い方 3. AもBも同じ場合、Cの数値が高い方 4. AもBもCも同じ場合、記述がファイルのあとにある方

詳細度には3つのカウント数値をもとに決める。

カウンタ名称 何をカウントするか
A (ID列) 「IDセレクタ(ex.#example)」のカウント
B (CLASS列) 「クラスセレクタ(ex..example)」「属性セレクタ(ex.[name="example"])」「擬似クラス(ex.:hover)(一部除く※1)」のカウント
C (TYPE列) 「要素型セレクタ(ex.div)」「疑似要素(ex. ::before)」

※1 :where() は記載してもカウントは増えず,:not().:has(),:is() は引数の要素でもっとも高い詳細度のカウントを+1する。

このABCは (A, B, C)A-B-Cという表現をする。

<div>
  <p name="example" id="first">
    sample text
  </p>
</div>

というHTMLがあった場合に、以下のCSSはすべて上記のDOMには適用されるが、color に関しては詳細度が一番高いものが適用されるので、文字色は緑になる。

div p  {  /* 詳細度は(0,0,2) */
  color: red;
}

div p#first {  /* 詳細度は(1,0,2) そのため他3つよりもこれが優先 */
  color: green;
}

p#first{ /* 詳細度は(1,0,1) */
  color: orange;
}

div p[name="example"] { /* 詳細度は(0,1,2) */
  color: yellow;
}

参考リンク

2024年1月時点のCSS記述の採用順番についてざっくり理解する

import! がどうやって適応されていくのかの原理を理解する。

出典元

2024年時点だとLevel4の資料がベースになるのでそれを今回をベースに記載する。

CSS Cascading and Inheritance Level 4

どこの記述が利用されるかを決める "Cascade Sorting Order"

CSSのどの記述が採用されるかはLevel4では大きく4つのパートに分けており、1のルールから順番に適用されていく。

  1. Origin and Importance
  2. Context
  3. Specificity
  4. Order of Appearance

Origin and Importance(Originに関してとimportかどうか)

最初に適用順番が決まるのがこの Origin and Importance のルール。このルールを把握するためにまず、 OriginImportance という概念に関して理解する必要がある。

Origin

Originはおおまかに言うとCSSが記述されている箇所の話であり、3つに区分けされている。

  • Author
    • HTMLやCSSファイルに書かれた領域のStyleの記述。普段から読み書きしているのはこの領域。
  • UserAgent
    • ブラウザが元から備える領域のStyle記述。リセットCSSなどで無効化される記述の部分などがそれにあたる。
  • User
    • ブラウザを利用するユーザが設定する領域のStyle記述。ブラウザ側によって様々だが、例としてブラウザが提供する拡張機能などに記載のあるStyleなどが挙げられる。

Importance

もう一つの概念として Importance がある。これは !important がついている記述かどうかの観点。ついていないものを normal と呼称し、ついているものを important という。

決定順番

優先順 優先されるもの
1 Transactionに関する記述
2 UserAgent の領域で important な記述
3 User の領域で important な記述
4 Author の領域で important な記述
5 Animationに関する記述
6 Author の領域で normal な記述
7 User の領域で normal な記述
8 UserAgent の領域で normal な記述

この優先順の上で、同列の存在に関しては次以降の優先順位決定ロジックが適用されていく。

Context

こちらはカプセル化された内側要素(たとえばShadow DOM)と比較した場合にどちらの記述を優先するかという話を定義している。

前項であげた importantnormal かで決まるルールとなっており…

  • important の場合は内側の記述が優先される
  • normal の場合は外側の記述が優先される。

分かりづらい話ではあるが、Shadow DOM採用時の例で整理すると、通常のDOM(Light DOM)が外側、Shadow DOMが内側となり、Shadow DOM側に !important があった場合は、外側であるLight DOMも影響を受ける、以下のサイトでは実例のコードも乗っているので参考のこと。

Cascading and Inheritance Level 4ではContextによるルールの優先付けが定義されており、normalな(!importantではない)ルールではShadow DOMの中よりも外が優先される一方で、!importantはルールではShadow DOMの外よりも中が優先されると定義されている。 - Shadow DOMによりスタイルが閉じ込められる仕組み

Specificity(詳細度)

この詳細度は普段から利用されるものなので、本説明では割愛する。

Order of Appearance(styleの登場順番)

styleの記述が重複している場合は、後に登場する記述が優先(後勝ち)される

挙動のまとめ

下記の順番で決まる。

  1. Origin and Importance
  2. Context
  3. Specificity
  4. Order of Appearance

1の中で優先度が同じルールのものは2のルールへ 2の比較をしてもでも同列のものは3の詳細度の高い順で 3の詳細度で最優先値のものが2つ並んだ場合は4のルールである登場順の後ろ

上記のような考え方で決まる。

補足 Level5とLevel6について

2024年2月時点ではLevel5とLevel6のルールも存在する

Level5の順番について

執筆時点でのpusblishedな情報だと以下の通り

  1. Origin and Importance
  2. Context
  3. [Level5での追加要素] Element-Attached Styles
  4. [Level5での追加要素] Layers
  5. Specificity
  6. Order of Appearance

2つ要素が追加されている。

  • Element-Attached Styles は インラインstyleであればそちらを優先するというもの。header要素に <style> を宣言したり、<p style="margin: 10px"> のような記述はCSSの詳細度判定以前に優先されるルール。この実装に関しては現時点でも多く行われている。
  • Layers@layerという新しい記述方法で優先されるものを切り分ける方法。詳しいことはここでは割愛する。Layerは2023年4月ごろには主要なブラウザには対応している。またトランスパイルも提供されている。

Level6の順番について

こちらに関しては2024年時点では絶賛議論中だが、2024年2月時点最新のEditors Draftによるとだと以下のようになりそう。

  1. Origin and Importance
  2. Context
  3. The Style Attribute(Element-Attached Stylesと内容は同じ)
  4. Layers
  5. Specificity
  6. [Level6での追加要素] Scoping Proximity
  7. Order of Appearance

  8. Scoping Proximity@scope を使うことで影響の及ぼす範囲を限定できるようにするもの。こちらも詳しいことは割愛する。当初Specificityより先に判断するか後に判断するかが議論されていたが、最終的にはSpecificityより後の順番になりそうな状況。

参考リンク

オブジェクト指向CSS(OOCSS)とは何なのかをざっくりまとめる

提唱元のスライド

OOCSSが提唱された2009年頃のCSS事情

OOCSSが提唱された2009年頃は、再利用性を鑑みたCSSは少なく、ページ単位でCSSを置くことが多い時代でした。またSassなども本格的に流行する前だったため、動的に何かを生成するというようなアプローチも確立されていない頃でした。

当時を知る手がかりとして2009年当時の書籍WebDesigningでのCSS特集のサンプルコードサイトがあります。

Web Designing: 2009年11月号

構造として、登場するDOM要素ごとにCSSが記述されており、 「header.sitewide .util li」というような作りになっていたので、DOM要素とclassの組み合わせで見栄えを決めるということが主流だったことが読み取れます。

OOCSSが解決したかった課題

2009年頃のCSSはパーツごとにCSSを書かなければならない状況であり、資料などを読み解くと以下の問題があったようでした。

  • CSSが表示するDOM要素それぞれにフィットするように個別に作られているので再利用性がない
  • CSSが肥大化しているためWebサイトの表示スピードが遅くなる

そのため、この課題に対して解決する考え方としてOOCSSが提唱したと考えられます。ではOOCSSはどのようなアプローチでこの問題に取り掛かったのでしょうか?

OOCSSの考え方

OOCSSはCSSとそこに関連するHTMLやJSを「CSS Object」と捉えて、Webページ毎につくるものではなく、抽象化して再利用可能なパーツとして考える考え方です。そのため具体的な記述ルールなどがあるわけではなく、CSSやHTMLを設計するための考え方となっています。

再利用性を念頭に置くことで、重複する記述の削除と無駄なコードの削減を狙ったのではないか、と考えられます。そしてこの再利用性を実現するために大きく2つの原則を提唱しています。

OOCSSを構成する2つの原則

原則1. ストラクチャ(構造)とスキンの分離

Webページについて、構成物パーツを形成する「ストラクチャ」と、ストラクチャに対しての見栄えを制御する「スキン」に分けてつくるという考え方です。

例えば、Webページ全体で使う "送信ボタン" , "キャンセルボタン" , "いいねボタン" があったときに、「ボタンストラクチャ」と「送信スキン」「キャンセルスキン」「いいねスキン」というような形式で分離をするというようなアプローチです。

原則2. コンテナとコンテンツを分離する

ここで言う「コンテンツ」は文章や画像などのWebページの構成パーツのことで、「コンテナ」はそれを束ねる要素のことを指しています。その2つを分離するのは、依存関係をなくすということを指しています。

例としてよくあるのはページの先頭の"ページヘッダ"がコンテナとして存在し、フッタ内に配置される"ボタンアイコン" がコンテンツとして存在するときの例です。この"ボタンアイコン"が"ページヘッダ"の特定の場所に配置することを前提としたCSS記述にすると、もし”ページフッタ"で使いたくなったときに見た目の部分は同じ"ボタンアイコン"に関する記述をページフッタ専用で書き直す必要が出てきてしまいます。そのようなことを回避するためにコンテナとコンテンツを分離するべき、というアプローチです。

どう「オブジェクト志向」を取り込んだのか

2009年当時の話でもふれたとおり、CSSの作りが作るページ単位で書き下ろしをし、またサイト内で共通化するということもしづらかった状態が当時あったことかと推察されます。

ではオブジェクト指向のどのあたりを取り込んだのがOOCSSなのでしょうか。提唱者のGitHubを見ると以下のような記述があります。

Basically, a CSS “object” is a repeating visual pattern, that can be abstracted into an independent snippet of HTML, CSS, and possibly JavaScript. That object can then be reused throughout a site. - Home · stubbornella/oocss Wiki

和訳すると以下のようになります。

基本的に、CSSの "Object"とは、繰り返される視覚的なパターンのことで、HTML・CSS・場合によってはJavaScriptスニペットとして独立した抽象化をすることができる。このObjectは、サイト全体で再利用することができます。

また、上記の CSS Objectの構成要素として以下のような内容も上げています。

A CSS object consists of four things:

  1. HTML, which can be one or more nodes of the DOM,
  2. CSS declarations about the style of those nodes all of which begin with the class name of the wrapper node
  3. Components like background images and sprites required for display, and
  4. JavaScript behaviors, listeners, or methods associated with the object.

This can be confusing because each CSS class is not necessarily an object in its own right, but can be a property of a wrapper class. - FAQ · stubbornella/oocss Wiki

和訳すると以下

CSS Objectは以下の4つから構成される:

  1. HTML、DOMの1つまたは複数のノード。
  2. CSS宣言、これらのノードのスタイルに関するものでラッパー・ノードのクラス名で始まるもの。
  3. コンポーネント、例えば表示に必要な背景画像やスプライトなど。
  4. オブジェクトに関連するJavaScript、ビヘイビア・リスナー・メソッドなど。

CSSクラスは、それ自体がオブジェクトであるとは限らず、ラッパークラスのプロパティであることもあるので、これは混乱するかもしれません。

このような例示をしているので、OOCSSで提唱したかったものはDOMそのものをふくめたコンポーネントそのもののことを指しているように読み取れます。

スライドでもWebページの構成をレゴで組み立てる話になぞらえて説明をしているので、オブジェクト指向の考え方の構造的な面でのメリットである「部品化」や「再利用性」というところをCSSをライティングする人々の世界に取り込みたかったのかなと考えられます。

参考リンク

RFC8058ワンクリック購読解除の仕様に関してざっくりまとめる

headerを設定すると何が起きるのか整理する

出典となるRFC

先にまとめ

RFC2369で、メーラーに関してそのメールの購読解除の案内をするヘッダとして List-Unsubscribe ヘッダの仕様がある。このヘッダはメールアドレスを指定しておくとその宛先にメールを送る、URLを指定するとそのページに遷移するというような挙動をサポートするヘッダ。 RFC8058でList-Unsubscribeと合わせて、List-Unsubscribe-Post を利用すると、指定したURIにPostリクエストを送信する仕様が定義されている。

ヘッダについて

List-Unsubscribeヘッダ

RFC2369にて定義されている設定。ユーザが対象のメールの購読を解除するためのアクションを設定する。値として例示されているのは大きくmailtoの形式と、URLの形式。

▼mailtoの形式

List-Unsubscribe: <mailto:list@example.com?subject=unsubscribe>

▼URLの形式

List-Unsubscribe: <http://example.com/list.cgi?cmd=unsub&lst=list>

なお複数の形式も設定することもできる

List-Unsubscribe: <http://example.com/list.cgi?cmd=unsub&lst=list>,<mailto:list@example.com?subject=unsubscribe>

List-Unsubscribe-Postヘッダ

RFC8058に定義されている設定。List-Unsubscribe とセットで利用するヘッダで、セットで送ることでList-UnsubscribeにPostリクエストでワンクリック登録解除が実現できることを示す。なお、フォーマットは以下の通り。

List-Unsubscribe-Post: List-Unsubscribe=One-Click

なお、List-Unsubscribe=One-Click 以外の書き方は現時点では記述が存在しないので書き方はこの一択。

挙動について

RFC8058の挙動に関して意図通りにサポートしていることが明確なのはGmailだが、他のメーラーに関しては対応状況はまちまちの様子である。なかでもOutlookに関しては List-Unsubscribe にURLしかない場合には意味がないものとみなすことがあるとのこと(詳しくは参考リンク参照のこと)

参考リンク