rails でRDBにツリー構造を持たせるには
参考
概要
railsでデータにツリー構造を持たせたい必要がでたので調べてみた。RDBでツリー構造を表現する方法は
など、いくつかメジャーな方法があるようです。
今回は入れ子集合モデルを持つawesome_nested_setというgemを使いました。
入れ子集合モデルとは、あえてすごい簡略化して説明すると、ツリー構造の図を一筆書きで反時計回りに囲うようになぞった時に各要素の左端と右端に出会った順番に番号を振っていくことでツリー構造をテーブル上に表現する方法のことです。この説明は適当すぎですが、SQLで木と階層構造のデータを扱う(1)―― 入れ子集合モデルでわかりやすく説明されています。
model
class PostIt < ApplicationRecord acts_as_nested_set counter_cache: :children_count end
DB
class ChangePostIts < ActiveRecord::Migration[5.2] def change change_table :post_its do |t| t.integer :parent_id, index: true t.integer :lft, null: false, index: true t.integer :rgt, null: false, index: true t.integer :depth, null: false, default: 0 t.integer :children_count, null: false, default: 0 end end end
It is highly recommended that you add an index to the rgt column on your models. Every insertion requires finding the next rgt value to use and this can be slow for large tables without an index. It is probably best to index the other fields as well (parent_id, lft, depth). awesome_nested_setより
なぜindexをつけると速くなるのかはよくわからない。また今度調べてみたいと思う。
method
@root = PostIt.new(memo: '親') @root.save @child = @root.children.new(memo: '子') @child.save
こんな感じで children メソッドというのが使えるようになる。saveの時に上記で説明した一筆書きを計算するためのたくさんのSQLが生成されるのがわかります。要素が増えるほど大量のSQLが必要になるのでこの計算の際にindexをつけておくことが推奨されているのだと思う。
CLIでgitレポジトリ初期化からpushまで
git では GUI のクライアントソフトを使っていたが、なぜか最近うまくうごかいないので、いっそのこと CLI での操作を覚えたいと思います。
参考
macでgit使いになるために抑えておきたいコマンド(基礎編)
リポジトリを作るには対象のディレクトリまで行ってから下記のコマンドを叩く。
git init
そうすると .git というディレクトリが生成される。
git add ファイル名
ステージするには add コマンドを使う。オプションとして対象のファイルを指定するか、--all でgitignoreされていないファイルで変更のあるものを全てステージできる。
git commit -m "コミットメッセージ"
これでコミットできる。
git remote add origin(リモート名) リモートレポジトリのurl
これでoriginという名前でリモートレポジトリに紐づけることができた。
git config --list
上記コマンドで config を確認すると
remote.origin.url=リモートレポジトリのurl
となっており登録できていることがわかる。 最後に
git push origin master
でプッシュができる。
GitHub の草
ふと、コミットしてるのに草生えないのはなんでろう?と寂しい気持ちになったので調べてみた。
コミットに使用されるメールアドレスがGitHubアカウントに紐づいている必要があるということがわかりました。
$ git config --global user.name "My Name" $ git config --global user.email myname@example.com
これで無事草が生えました。 そもそも、ユーザネームとアドレスは最初に設定すべきものだったらしい。理由はまだわからないが、、
参考
【草はやしてる?】意外と知らないGitHubで草を生やす条件とは
gitconfig の基本を理解する
使い始める - 最初のGitの構成
accepts_nested_attributes_forの使い方
- 参考
- 何がしたいか
- モデルで accepts_nested_attributes_for を宣言
- form を作る
- view でネストするパラメータを作るための form を作成
- ストロングパラメータ
- save
- ポイント
- まとめ
参考
http://kzy52.com/entry/2013/07/10/200144
何がしたいか
form でネストする params を作って異なる model の属性を格納し、一回のアクションで同時にsaveしたい。
モデルで accepts_nested_attributes_for を宣言
ネストさせるパラメータの親のモデルクラスで accepts_nested_attributes_for を記述する。引数にはネストの子のモデルをシンボルで指定。
class Teacher < ApplicationRecord belongs_to :school accepts_nested_attributes_for :school end
accepts_nested_attributes_for メソッドを使う前にモデル同士のリレーションが宣言されている必要がある。
こうすることで school_attributes メソッドが teacher モデルに追加される。
form を作る
新しいインスタンスを同時に作る場合。
def new @teacher = Teacher.new @teacher.build_school end
これで @teacher と @teacher.school が同時に作られる。中身は空。
<%= render 'form', teacher: @teacher, school: @school %>
view でネストするパラメータを作るための form を作成
<%= form_with(model: teacher, local: true) do |form| %> <div class="field"> <%= form.label :name, '名前' %> <%= form.text_field :name %> </div> <%= form.fields_for :school do |school_form| %> <div class="field"> <%= school_form.label :name, '学校名' %> <%= school_form.text_field :name %> </div> <% end %> <div class="actions"> <%= form.submit %> </div> <% end %>
form_with 内で 子params のための fields_for の領域を作る。
上の例でいうと form.fields_for とすることで params がネストされる。
ストロングパラメータ
# teachers_controller.rb def teacher_params params.require(:teacher).permit(:name, school_attributes: [:name]) end
ここで school_attributes をpermit に加えると、コントローラで入れ子のschoolの属性を @teacher.school として扱えるようになる。
save
def create @teacher = Teacher.new(teacher_params) respond_to do |format| if @teacher.save reset_session session[:teacher] = @teacher.id format.html { redirect_to @teacher, notice: '登録が完了しました.' } format.json { render :show, status: :created, location: @teacher } else format.html { render :new } format.json { render json: @teacher.errors, status: :unprocessable_entity } end end end
あとは @teacher のみを普通に save するときと同じ。
ポイント
accepts_nested_attributes_for はモデル同士のリレーションを前提としている。ただし、 accepts_nested_attributes_for はネストさせる params の親のモデルクラスで宣言する。今回のようにモデル自体のリレーションの親子関係 (belongs_to, has_many)と params の親子関係が逆だとややこしいので注意する必要がある。
まとめ
上の二つでやろうとしていた処理が accepts_nested_attributes_for を使ったことで簡単に実装することができた!
エラーメッセージ
<% if teacher.errors.any? %> <div id="error_explanation"> <h2><%= pluralize(teacher.errors.count, "error") %> prohibited this teacher from being saved:</h2> <ul> <% teacher.errors.full_messages.each do |message| %> <li><%= message %></li> <% end %> </ul> </div> <% end %>
上記はエラーメッセージを表示するための記述。scaffold では_formファイルの form_with下に生成される。モデルの処理でエラーがあるとerrors オブジェクトが作られる。エラーがあった場合はif teacher.errors.any?以下が実行される。
pluralize(teacher.errors.count, "error")
pluralizeは単数形を複数形に変換するためのメソッド。第一引数の数値を第二引数を単位として表現する。エラーの数が1なら1 error で2なら 2 errors となる
<% teacher.errors.full_messages.each do |message| %> <li><%= message %></li> <% end %>
validationにかかった場合、インスタンス.errors.full_messages のなかに配列でエラーメッセージが入る。順に取り出して表示しなんで保存に失敗したかをユーザが確認できるようにしている。
『AWSをはじめよう』で学習①
参考
物理サーバと仮想サーバ
物理サーバは普通にそこにあるサーバ。
仮想サーバは物理サーバ上のリソースの領域を仮想的に分割してそこを独立したサーバと見なすこと。
オンプレミスとクラウド
住居に例えるとオンプレミスは持ち家。クラウドは賃貸。
VPS とクラウド
どちらも仮想サーバ。VPSは定額制が多い。限定されたリソースが前提となっていることが多い。クラウドの場合は従量課金、柔軟なリソースの変更。ただ、厳密な違いはないらしい。VPSもクラウドの一種と言えるのかも。
AWSをはじめる
AWSが有利な点は現在シェアの30割を占めており他者に比べて圧倒的にシェアが高いこと。
これにより、対応可能なエンジニアの数も多く、資料の量も最も多いことから有利になっている。AWSの管理画面はマネジメントコンソールと呼ばれている。
IAMユーザ
デフォルトではログインした状態でのユーザはルートになっている。
普段利用する時にルートだと間違って大きな変更を加えてしまう可能性があるのでIAMユーザを作っておいてそれを利用する。IAMではグループを作ってそれぞれ任意の権限を設定した上で運用することができる。グループを作ってユーザを登録するという流れ。IAM は Identity and Access Management の略。リージョンの選択によって立てるサーバの地域も変わってしまうので設定を忘れないようにする。
CloudTrail
サーバやアカウントの管理についていつ誰が何をしたかの記録が確認できるサービス。
EC2
AMI
Amazon Machine Image.
インスタンス(サーバのこと)の作成に必要なOSなどのソフトウェア構成のテンプレートのこと。
インスタンスのタイプ
T や M などスペックやバージョンによって種類がたくさんある。
セキュリティグループ
セキュリティグループとはファイアウォールのこと。セキュリティグループに指定した場所から以外はサーバに入れないようにする。また、セキュリティにはキーペアもあり、これは一度しかダウンロードができない。これがないとサーバに入ることができない。
SSH
SSHとは遠隔地のサーバと自分の目の前のパソコンを安全に繋ぐためのサービスのこと。SSHできるサーバのことをSSHサーバと呼ぶ。SSHではデータを暗号化した上で送受信している。SFTP(SSH File Transfer Protocol) や SCP(Secure Copy) と呼ばれる仕組みもこの機能を使っている。
トランザクション処理
実装
同時に保存すべき項目があったのでトランザクション処理にしてみた。
def create Teacher.transaction do @teacher = Teacher.new(teacher_params) @school = @teacher.build_school(school_params) @teacher.save! @school.save! end reset_session session[:teacher] = @teacher.id redirect_to @teacher rescue => e render plain: e.message end
構文
transactionメソッドはモデルクラス経由で呼び出す。
transactionのブロック内にDBにアクセスする処理(例外が起こりうる処理)を書く。
ブロック内の処理が成功したら実行したい処理をブロックの後に続けて書く。
最後に rescue => e に続けてブロック内の例外が発生した場合の処理を書く。
ポイント
ブロック内のDBへの保存の処理はsaveでなく、save! にする。
save の返り値は true/false だけど、save! の場合は失敗すると例外が返る。トランザクション処理のロールバックは例外がトリガーになるので成功以外の場合は例外になるようにする必要がある。
例外処理を用意する
ruby では bigin ~ rescue ~ end で例外を捕捉する構文があるけどここでも同じことをしている。
rescue => e で 変数e に例外オブジェクトを格納している。
例外が起きた時は e.message が rendering される。
追記
今回のパターンだと accepts_nested_attributes_for を使った方が良さそうです。下記のページでまとめました。