Rails
CSS3
ActiveRecord
rake_task

accepts_nested_attributes_forのupdate_onlyについて学ぶ他_100DaysOfCodeチャレンジ8日目(Day_8:#100DaysOfCode)

はじめに

この記事はTwitterで人気のハッシュタグ#100DaysOfCodeをつけて、
100日間プログラミング学習を続けるチャレンジに挑戦した8日目の記録です。

動作環境

  • ruby 2.4.1
  • Rails 5.0.1

現在学習している内容のリポジトリ

https://github.com/yuta-ushijima/notebook-api-on-rails
https://github.com/yuta-ushijima/css-biginner

本日学んだこと

  • CSSの詳細度について
  • accepts_nested_attributes_forのupdate_onlyについて
  • %w記法でRailsタスクをもっと便利に使う

ここからはCSS編です

CSSの詳細度について

CSSにはセレクタに対して詳細度という概念があります。

id、クラス(擬似クラスも含む)、要素(擬似要素も含む)のそれぞれをスコアとして捉え、
同じ要素のスタイルを適用させる際に各スコアが高いものが優先されることになります。

なお、優先度としてはidが最も高く、要素が最も低いということを覚えて置いてください。

h1 { color:  red; }
h1#main.box { color: yellow; }
#about > #main { color: gray; }
section > #main.title { color: blue; }
section#about > h1 { color: pink; }

例えば上記のようなスタイルが書かれているとしましょう。

いずれもh1要素に対してスタイルを記述するという意味です。

この5つの中におけるスタイルの詳細度を考えた時、もっとも優先されて適用されるのは、#about > #main{ color: gray; }になります。

これはidセレクタが2つあるのに対して、他のスタイルはidセレクタが1つもしくは0だから。

続いて、優先されるのは、section > #main.title { color: blue; }です。

このスタイルを分解すると、次のようになります。

  • idセレクタ...1つ(#main)*}
  • クラスセレクタ...1つ(.title)
  • 要素セレクタ...1つ(section)

したがって、id/クラス/要素が各一つずつあることになりますね。

ちなみに、h1#main.box { color: yellow !important; }も同じ構造になっていますが、CSSでは後から記述されたスタイルの方が優先されるといった特徴があります。

そのため、h1#main.box { color: yellow !important; }よりもsection > #main.title { color: blue; }の方が後にスタイルが記述されているので、優先的に適用されることになります。

このように見ていくと、要素セレクタのみを指定しているh1 { color: red; }は5つの中で最も低いことになることがわかるかと思います。

インライン形式のスタイルは外部ファイルのスタイルよりも詳細度は高い

ここまで見てきたのはstyle.cssのような外部ファイルを用いた詳細度についてです。

CSSにはHTML要素に直接スタイルを適用させるインライン形式での書き方がありますよね。

実はこのインライン形式、外部ファイルで記述したスタイルよりも詳細度が高いので、優先されてスタイルが適用されることになります。

/* css/styles.css */
h1 { color:  red; }
h1#main.box { color: yellow; }
#about > #main { color: gray; }
section > #main.title { color: blue; }
section#about > h1 { color: pink; }

上記のようなファイルで記述されていたとしても、次のようにHTMLが記述されていれば、styles.cssの中で詳細度が高いgrayのスタイルは無視され、文字色はskyblueになる。

<!-- index.html -->
<body>
  <section id="about">
    <h1 id="main" class="title box" style="color:skyblue;">Main Title</h1>
  </section>
</body>

諸刃の剣である!important

インライン形式で記述すると、外部ファイルで書かれたスタイルよりも優先された適用されるとお話しましたが、一つだけ例外があります。

それは、プロパティに対する値の後に!importantが指定された場合です。

この!importantが指定された場合、他の何よりも優先して適用されることになります。

なお、!importantが複数指定された場合は、SSの原則により後から!importantを書いた方が優先されことになるので注意。

Bootstrapなどのフレームワークや各ブラウザのuser agentなどで適用されているスタイルよりも!importantで指定されたスタイルが優先されるのがポイントです。

ただ、たくさん!importantを指定してしまうと、意図通りにスタイルが適用されないことがあるので、その点については頭にいれて置いたほうがいいですね。


ここからはRails編です

%w記法でRailsタスクをもっと便利に使う

%w記法を使うと、コマンドを出力することができます。

これをRailsタスクを記述しているファイルにあらかじて書いて置くことで、タスクコマンドを実行するとrails db:drop db:create db:migrateを同時に行ってくれます。

namespace :dev do
  desc "開発環境設定"
  task setup: :environment do
    puts "データベースをリセット中..."
    %x(rails db:drop db:create db:migrate)
  end
end

つまり、rails dev:taskを実行すれば、rails db:drop db:create db:migrate dev:setupと同じことができるようになるということです。

単純にターミナルで余計なコマンドを打つ手間がなくなるので、オリジナルのRailsタスクと組み合わせればテストデータ作成がもっと便利になりますね。

モデル同士が1対1の関係にある時のaccepts_nested_attributes_forについて

1対1、つまりモデル同士がhas_oneとbelongs_toの関係にある時、accepts_nested_attributes_forを使う時には注意が必要です。

belongs_to側のモデルに対してネストによるレコード更新を行う際、idを指定しないとデフォルトの状態では新規にidが作成されてしまいます。

例えば、ContactモデルとAddressモデルが1対1の関係にあったとしましょう。

contactモデル

class Contact < ApplicationRecord
  has_one :address
  accepts_nested_attributes_for :address, update_only: true
end

Addressモデル

class Address < ApplicationRecord
  belongs_to :contact, optional: true
end

この時、以下のような情報をもつレコードがあるとします。

{
    "id": 103,
    "email": "akanetan@wakatsuki.com",
    "kind_id": 1,
    "name": "若月 茜",
    "birthdate": "2004-07-12",
    "created_at": "2018-06-24T08:46:34.095Z",
    "updated_at": "2018-06-24T08:50:35.333Z",
    "address": {
        "contact_id": 103,
        "street": "3-1-5",
        "city": "新宿区",
        "id": 106,
        "created_at": "2018-06-24T08:52:56.633Z",
        "updated_at": "2018-06-24T08:59:53.533Z"
    }
}

デフォルトの状態では、例えばcityの値を"新宿区"から"渋谷区"に更新した場合、idを指定しないと(この場合は106)新規にidが作成されてしまいます。

# 実行結果

{
    "id": 103,
    "kind_id": 1,
    "name": "若月 茜",
    "email": "akanetan@wakatsuki.com",
    "birthdate": "2004-07-12",
    "created_at": "2018-06-24T08:46:34.095Z",
    "updated_at": "2018-06-24T08:50:35.333Z",
    "address": {
        "id": 107, # idが106ではなく、107になっている
        "street": null,
        "city": "渋谷区",
        "contact_id": 103,
        "created_at": "2018-06-24T09:16:52.183Z",
        "updated_at": "2018-06-24T09:16:52.183Z"
    }
}

そこで、accepts_nested_attributes_forのオプションとしてupdate_only: trueを設定すれば、idが指定されていようがいまいが、同じidで値が更新されるようになります。

# 実行結果

{
    "id": 103,
    "kind_id": 1,
    "name": "若月 茜",
    "email": "akanetan@wakatsuki.com",
    "birthdate": "2004-07-12",
    "created_at": "2018-06-24T08:46:34.095Z",
    "updated_at": "2018-06-24T08:50:35.333Z",
    "address": {
        "id": 106, # idが106のまま
        "street": null,
        "city": "渋谷区",
        "contact_id": 103,
        "created_at": "2018-06-24T09:16:52.183Z",
        "updated_at": "2018-06-24T09:16:52.183Z"
    }
}

参考リンク

https://qiita.com/mogulla3/items/46bb876391be07921743

https://developer.mozilla.org/ja/docs/Learn/CSS/Introduction_to_CSS/Syntax