パーフェクトPHPで学習中(chapter3)

参考

パーフェクトPHP

chapter3

論理型

PHP 型の比較表 phpでは文字列"0"などでもfalseになる。

演算子

可算子・減算子(インクリメント・デクリメント)

インクリメント(++)とデクリメント(--)は対象の変数に1を足す/引くという演算を行い結果を変数に代入する。前置か後置かで返り値が変わる。値を返す処理と演算の処理の順番が変わり、前置の場合は先に演算を行い、後置の場合はあとで演算を行う。

<?php
$hoge = 0;
var_dump ($hoge == ++$hoge); //bool(true)
var_dump ($hoge <= $hoge++); //bool(false)
$hoge = 0;
var_dump ($hoge++);          //int(0)

演算子

演算子(instanceof)は左辺のインスタンスが右辺の型である場合にtrueを返す演算子。 当てはまらない場合はfalseを返す。

<?php
if($a instanceof Some){
  ehco '$a is instance of Some class.'
}

パーフェクトPHPで学習中(chapter2)

参考

パーフェクトPHP

chapter2

関数とメソッド

PHPでは関数とメソッドには必ず()をつける。 ()がないとプロパティ扱いになる。

PHPではそれぞれの文の終わりに;をつける。 一文しかないPHPブロック内では;は不要。

PHP_EOL

PHP_EOLは改行を意味する定数。

変数

概要

PHPは変数を$と識別子で区別する。変数の頭には必ず$をつけなければならない。 識別子は大文字と小文字で区別される。 isset()で引数の名前の変数がセットされているかどうかを調べることができる。

可変変数

$$で宣言する。 評価する変数名を変数にすることができる。 内側の$から順に評価していくイメージ。 参照する変数を動的に変更するときや文字列から変数名を組み立てたいときに役立つ。

変数のスコープ

グローバルスコープ

PHPブロックの中はグローバルスコープになる。 他のPHPブロックやファイルの中でも使うことができる。 またPHPにはブロックスコープがない。 制御構文中のブロックを抜けた後でもブロック内の変数が残る。 要注意。

ローカルスコープ

関数やクラスのメソッド内がローカルスコープになる。 ローカルスコープとグローバルスコープの変数は互いに行き来することができない。

定義済み変数

phpには実行時に自動的に初期化されている変数がある。 全ての変数が書き換え可能であるため誤って上書きしないように注意する必要がある。

スーパーグローバル変数

前述の定義済み変数の他に全てのスコープから参照可能な変数がある。これも実行時に自動的に定義されている。実行環境情報やHTTPのparams、セッション変数やクッキーなどが入っている。

定数

一度定義したらスクリプト終了まで変更できない。定数には$はいらない。

定数定義

<?php
define('NAME', 'yshr');
const NAME = 'yshr';

上記の二つはどちらも定数定義の方法だが、constで定義したものだけ名前空間の影響を受ける。

rails でRDBにツリー構造を持たせるには

参考

概要

railsでデータにツリー構造を持たせたい必要がでたので調べてみた。RDBでツリー構造を表現する方法は

  1. 隣接リストモデル
  2. 経路列挙モデル
  3. 入れ子集合モデル
  4. 閉包テーブルモデル

など、いくつかメジャーな方法があるようです。

今回は入れ子集合モデルを持つ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の使い方

参考

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 のなかに配列でエラーメッセージが入る。順に取り出して表示しなんで保存に失敗したかをユーザが確認できるようにしている。