TECH EXPERT 15日目

TECH::EXPERT

ついに丸2週間が終了。
本日から応用コースへ入ります。
少し気持ちがだれてきたので9時に教室に行かなくなってきた。気を引き締めないと。

応用コースからはメンターさんを呼ぶのではなく、チャットかテレビ電話的なので質問してねってルールなのでチャットで質問してみた。チャット形式だとニュアンスが伝わりにくいのでやりとりが長くなって嫌だなぁ。かといって私の質問はめんどくさい内容が多いので、答える側としても時間が欲しいだろうから通話形式もお互い時間の無駄な気がする。

明日からはちょっとルールを変えて、11ー18時はカリキュラムを進める。
それ以外の時間は自分のweb制作に当てることにします。
だらだらカリキュラムをやると時間が間延びしてしまうのでハード寄りなスケジュールで行きたいと思います。9ー11時、18ー21時に自分の時間でどれだけ頑張れるかが実力の差になってくるはず。

before actionで行われる処理とインスタンス変数の参照?

before_action :set_tweet

def show
      @comments = @tweet.comments.includes(:user)
end

def set_tweet
      @tweet = Tweet.find(params[:id])
end

これでshowメソッド内で@tweetが動くのはなぜ?
before actionの内部的な処理はどうなってるの?

showが呼ばれた時の動きとして、
set_tweet
show
の順でメソッドを呼んでいるのならset_tweet内で定義した@tweetは次のメソッドで利用できない。

def show
      set_tweet
      @comments = @tweet.comments.includes(:user)
end

みたいに書き換えているとしても結局変数を呼べないよね?
少なくともrubyの記法ならこんな書き方しても機能しないはず。

理解

そもそも”@”tweetと書くということは、これはただの変数ではなくインスタンス変数。
では@tweetを属性に持つインスタンスとは何か?

おそらくコントローラークラスのインスタンスである。
つまりこの例で言うと、Tweetsクラスのインスタンス(hogehoge)が属性として@tweetを持っている。
なので他のメソッド(インスタンスメソッド)を利用しても共通してインスタンス(hogehoge)の属性を扱うことができる。

おそらくrailsでは、コントローラが呼び出される際に自動的にコントローラクラスのインスタンスが生成される。
このインスタンスに対して、コントローラ内のインスタンスメソッドで処理を行い、インスタンス変数を格納していく。
ビューで使う場合にもこのインスタンスが引き渡されているはずなので、html.erb内で@付きの変数(インスタンス変数)を利用できる。

Rails: 提案「コントローラから`@`ハックを消し去ろう」(翻訳)|TechRacho by BPS株式会社
概要 原著者の許諾を得て翻訳・公開いたします。 英語記事: Removing the @ Hack in Rails C...
何故 Rails では View から Controller のインスタンス変数にアクセスできるのか
この記事は Okinawa.rb Advent Calendar 2016  6日目の記事です。 はじまり むかしむかし...

いくつかのサイトをみてみたが、大筋の理解は間違ってなさそう。
ただし、@付きの変数に関してはインスタンス変数だから、ではなくこれを目印として別途viewへ引き渡すための監視を特別に行なっている模様。

begin ~ rescueとraiseとcatch ~ throw

begin ~ rescue

処理内で例外が発生した場合、rescueが実行される。
またensureを用いると、例外発生の有無にかかわらずensure句は実行される。

raise/fail 強制的にエラーを起こす。
どちらも同じだが、raiseは意図的にエラーを起こしてrescueで再キャッチする際に用いる。

catch ~ throw

catchで指定したオブジェクトをthrowで受けた場合に処理を抜けることができる。
この処理に例外の有無は関係がないため、本質的には上記とは使い方が異なる。
一気に飛ぶのでパフォーマンスもcatchの方が良い。

複数のコントローラに同じ処理が記述されている場合(concerns)

開発の規模が大きくなるにつれ、複数のコントローラに同じような処理が繰り返し記述されることがある。このような場合に

  • app/controllers/concernsにファイルを追加し、必要箇所で読み込ませる
  • 親コントローラにメソッドを定義する

などの方法でコントローラの記述を共通化できる。
concerns ディレクトリに共通するコードをモジュールとして定義し、ソースコードの可読性をあげる。

app/controllers/concerns/current_cart.rb

module CurrentCart
  extend ActiveSupport::Concern

  private

  def set_cart
    @cart = Cart.find_by(id: session[:cart_id])
    if @cart.nil?
      @cart = Cart.create
      session[:cart_id] = @cart.id
    end
  end
end
class ProductsController < ApplicationController
  include CurrentCart
  before_action :set_cart
end

同じ親コントローラを継承していれば、親コントローラに記述するだけでOK。

def ApplicationController < ActionController::Base

  private

  def authorize_owner
    redirect_to root_path unless current_user.owner?
  end
end

複数のアクションに同じ処理が記述されている場合

同じコントローラ内で同じような処理が繰り返し記述されている場合はcallbackを用いて共通化する。before_actionなどのcallbackを使うと良い。

注)諸説あり。before_action等が増えるとメソッド内の処理が見通せなくなる。
callbackの順番によって意図しないエラーが起こる可能性がある。

コントローラに複雑な処理を記述している場合

コントローラのコード量が多くなっている場合、本来モデルで行うべき処理がコントローラに書かれている可能性が高い。

モデル編

ビューやコントローラから呼び出される様々な処理はモデルに集約されていくために、特にモデルは肥大化し易い。幾つかの観点で切り分ける。

Decorator

Decorator(デコレーター)とはビューとモデルの中間に位置し、モデルやビューなどに実装されやすい表示ロジックやフォーマットなどの責務を引き受けるクラス。

モデルにビューでしか使用しないメソッドが増えていくことがある。
full_nameやfull_name_kanaと言ったメソッドがその例。
こうしたメソッドをデコレーターに移動することでコードの見通しが改善される。

app/models/user.rb

class User < ActiveRecord::Base
  def full_name
    "#{family_name} #{first_name}"
  end

  def full_name_kana
    "#{family_name_kana} #{first_name_kana}"
  end
end

Railsでデコレーターを使用する場合にはdraperやactive_decoratorと言ったgemを使う方法が一般的。draperを使った例を以下に記述します。

app/decorators/user_decorator.rb

class UserDecorator < Draper::Decorator
  delegate_all

  def full_name
    "#{family_name} #{first_name}"
  end

  def full_name_kana
    "#{family_name_kana} #{first_name_kana}"
  end

end

app/controllers/users_controller.rb

class UsersController < ApplicationController
  def show
    @user = User.find(params[:id]).decorate
  end
end

app/views/users/show.html.erb

<%= @user.full_name %>
<%= @user.full_name_kana %>

Validator

モデルの役割の一つにバリデーションがある。
バリデーションとはデータの整合性を保つために、データを検証する機能のこと。

あるモデルのバリデーションに複雑な処理があったり、複数のモデルに共通のバリデーションが存在する場合にはそれらをモデルから切り離すことでリファクタリングが可能。

app/models/articles.rb

class Article < ActiveRecord::Base
  validates :name, url_format: true
end

app/validators/url_format_validator.rb

class UrlFormatValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    if value.present? && value !~ /\A#{URI::regexp(%w(http https))}\z/
      record.errors[attribute] << "のフォーマットが不正です"
    end
  end
end

上の例ではActiveModel::EachValidatorを継承したクラスの中にvalidate_eachというメソッドを定義しています。このクラスのファイル名から_validatorを取り除いたものを各クラスのvalidatesメソッドに引数として渡すと、そのカラムを検証する際にvalidate_eachメソッドが実行されます。

Callback

コントローラ同様にモデルにもvalidationの直前に実行されるbefore_validationであったり、saveの直後に実行されるafter_saveなど様々なタイミングで実行されるコールバックが存在する。

開発が大規模になるとコールバックのメソッドが増え、メソッド同士の関係性がわかりづらくなる。callbackの引数に、そのコールバックと同名のメソッドを持つインスタンスを渡すことで、コールバックの処理を別のクラスに移動することができる。

class BankAccount < ActiveRecord::Base
  before_save      EncryptionWrapper.new
  after_save       EncryptionWrapper.new
  after_initialize EncryptionWrapper.new
end

class EncryptionWrapper

  def before_save(record)
    record.credit_card_number =  encrypt(record.credit_card_number)
  end

  def after_save(record)
    record.credit_card_number = decypt(record.credit_card_number)
  end

  def after_initialize(record)
    record.credit_card_number = decypt(record.credit_card_number)
  end

  private

    def encrypt(value)
      # 暗号化の処理
    end

    def decrypt(value)
      # 解読の処理
    end
end

正規表現

subメソッド gsubメソッド

subメソッドでは一致する文字列の最初の1つ目しか置換されない。
gsubメソッドではマッチする文字列の全てを置換する。
このgが意味するのは、グローバルマッチと呼ばれ、文字列内で指定した文字が複数含まれている場合、その全てを置換する。

irb(main):001:0> str = "りんごを食べる"
=> "りんごを食べる"

irb(main):002:0> str.sub(/りんご/,"みかん")
=> "みかんを食べる"

subメソッドは、第一引数に置き換えたい文字列を指定し、第2引数に変換後の文字列を指定する。また、操作したい文字列は/で囲む。

JavaScriptでは、subメソッドと同じ機能を持つメソッドとして、replaceメソッドがある。

matchメソッド

irb(main):001:0> str = "Hello, World"
=> "Hello, World"

irb(main):002:0> str.match(/Hello/)
=> #<MatchData "Hello">

irb(main):003:0> str.match(/Good/)
=> nil

matchは引数に指定した文字列がレシーバの文字列に含まれているか否かをチェックする。
含まれている場合は、指定した文字列がMatchDataオブジェクトの返り値となる。
含まれていない場合は、返り値がnilとなる。

MatchDataオブジェクト

マッチした文字列等はMatchDataオブジェクトで返る。
MatchDataオブジェクトから文字列等を取り出す際は、以下の様に配列と同様にして取り出す。

irb(main):001:0> str = "Hello, World"
=> "Hello, World"

irb(main):002:0> md = str.match(/Hello/)
=> #<MatchData "Hello">

irb(main):003:0> md[0]
=> "Hello"

正規表現

pass.match(/[a-z\d]{8,}/i)
  • [a-z]: 角括弧で囲まれた文字のいずれか 1 個にマッチ
  • \d: 数字にマッチ
  • {n, m}: 直前の文字が少なくとも n 回、多くても m 回出現するものにマッチ
  • i: 大文字・小文字を区別しない検索
irb(main):001:0> mail = 'hoge@tech-camp.com'
=> "hoge@tech-camp.com"

irb(main):002:0> mail.match(/@.+/)
=> #<MatchData "@tech-camp.com">
  • . : どの1 文字にもマッチ
  • + : 直前の文字の 1 回以上の繰り返しにマッチ

基本的に \A と \z を使うことが必須。
文章の頭から終わりまで。\Zは文末の改行ならその手前まで。なので改行を許容することになるので通常は\zを使う。

JavaScript

JavaScriptは、HTMLとCSSを操作するための言語。

scriptタグでJavaScriptを埋め込む方法は2つ。
一つがscriptタグの中に直接JavaScriptを記述する方法、
もう一つが外部ファイルを読み込む方法です。
外部ファイルを読み込むにはsrc属性で読み込むJavaScriptファイルのパスを指定する。

<body>
  <script src="script.js"></script>
</body>
<body>
  <script>
    window.alert('こんにちは');
  </script>
</body>

JavaScriptでは行の最後にセミコロン(;)をつける。
文字列はシングルクォート(’)かダブルクオート(”)で囲む。
文字列同士は+で連結できる。

'Hello' + 'World';

変数の定義 var

var name = 'TECH::CAMP';

条件式

if (条件式1) {
  // 条件式1がtrueのときの処理
} else if (条件式2) {
  // 条件式1がfalseで条件式2がtrueのときの処理
} else {
  // 条件式1も条件式2もfalseのときの処理
}

配列

var list = ['Ruby', 'Ruby on Rails'];

配列要素の取得

var item = list[0];

配列に要素を追加する push()

配列の一番後ろに要素を追加する。引数には追加したい要素を渡す。

var list = ['Ruby', 'Ruby on Rails', 'JavaScript', 'HTML', 'CSS'];
list.push('jQuery');
list[list.length - 1]; // jQueryが取得できる

配列の要素を削除する pop(), shift()

popは配列の一番後ろの要素を削除する。
shiftは一番最初の要素を削除する。

var list = ['Ruby', 'Ruby on Rails', 'JavaScript', 'HTML', 'CSS'];
list.pop();
list[list.length - 1]; // HTMLが取得できる

オブジェクト

オブジェクトはデータのまとまりのこと。
オブジェクトは名前と値をセットにしてデータを管理する。(rubyでいうハッシュ)
オブジェクトを作成するには波カッコ{}を使う。

var obj = { name: 'TECH::CAMP' };

プロパティ

プロパティとはオブジェクトの属性値。オブジェクトは複数のプロパティを持てる。
プロパティは必ず属性名である”プロパティ名”とデータである”値”をセットで持つ。

var obj = { プロパティ名1: 値1, プロパティ名2: 値2 };

プロパティの値を取得する

取得する方法は以下の2つです。

  1. オブジェクト名とプロパティ名をドットでつなぐ
  2. オブジェクト名のあとに角カッコ[]を記述し、その中にプロパティ名を文字列で指定
obj.プロパティ名
obj['プロパティ名']

繰り返し for文

for (var i = 0; i < 10; i = i + 1) {
  console.log(i);
}

関数

JavaScriptで関数を定義するにはfunction文を使う。

function hello(name) {
  window.alert(name + 'さん、こんにちわ!');
}

返り値を持たすにはreturnする。

function square(num1) {
  return num1 * num1;
}
console.log(square(3));

jQueryとは

jQueryはJavaScriptのライブラリ。
JavaScriptで行えるほぼ全てのことを、JavaScriptよりも簡単に行える。

jQueryへのリンク

コピーした記述をindex.htmlのhead内に以下のように貼り付けることでjQueryを読み込める。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>jQuery Practice</title>
    <link rel="stylesheet" href="style.css" charset="utf-8">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <script src="./main.js"></script>
  </head>

HTML要素を取得してみる

jQueryでのHTML/CSSの操作は非常にシンプルで、以下の2ステップ。

  1. HTML要素を取得する
  2. 取得したHTMLを操作する
$(function() {

  console.log($("#title"));

});

IDセレクタ

IDセレクタとは、HTML要素のid属性で指定するセレクタのことです。IDセレクタは、取得したいHTML要素のid属性の値に#(ハッシュ)を付けたものをセレクタとして利用します。

$("#idSelector") // idがidSelectorの要素を取得

クラスセレクタ

クラスセレクタとは、HTML要素のclass属性で指定するセレクタのこと。
クラスセレクタは、取得したいHTML要素のclass属性の値に.(ドット)を付けたものをセレクタとして利用する。

$(".classSelector") // classがclassSelectorの要素をすべて取得

要素セレクタ

要素セレクタとは、h1やpのようなHTML要素を対象としたセレクタのこと。
要素セレクタは取得したいHTML要素の要素名をそのままセレクタとして利用する。

$("h1") // h1要素をすべて取得

セレクタを指定した場合、該当するすべてのHTML要素が取得されます。
そのためidセレクタは1つしか要素が取得されませんが(id属性の値は被ることがないため)、他のセレクタでは複数の要素が取得される。

$("セレクタ").jQueryの命令(引数);

text()

取得したHTML要素のテキストを書き換えることができます。引数には書き換えたいテキストを文字列で指定する。
また、text()のように引数を与えずに使うとテキストの取得ができる。

html()

html()を使うと、取得したHTML要素のテキストをHTMLのタグを含むテキストに書き換えることが出来る。引数には書き換えたいHTML要素を文字列で指定する。
text()メソッド同様html()と引数を省略するとhtmlの内容を取得できる。

$("p").html("<strong>変更されたコンテンツ</strong>");

$(function() { … }

記述することでHTML要素が全て読み込み終わったタイミングで処理を実行してくれるようになります。

イベントを設定する

on()メソッド

on()は、jQueryオブジェクトに対してイベントを設定するメソッド。
あるオブジェクトに対して、「どんなイベントが起きたら」「何を実行するか」を設定できる。

$(function() {
  $("セレクタ").on("イベント", function() {
    イベントが起きた時に行う処理
  });
});

onメソッドの第2引数は、(無名)関数を渡します。
無名関数とは文字通り名前のついていない関数のこと。1回しか使わないのであれば名前をつける必要ない。

関数の定義をする場合、 function methodName(){}のような記述をしますが、このmethodName部分を省略したのが無名関数です。

イベント名該当するタイミング
clickクリックされた時
dblclickダブルクリックされた時
mouseoverマウスが要素の上に乗った時
mouseoutマウスが要素から離れた時
keydownキーが押された時
keyupキーが離されたとき
change入力内容が変更された時
$(function() {
  $("#title").on("click", function() {
    console.log("タイトルがクリックされました");
  });
});

textbox

中身はtext()で取り出すのではなく、val()

コメント