Open94

Ruby on Railsに入門してみる。

fukumanfukuman

rubyの初歩文法をprogateにて終了。

phpとの違い、気づいたことなど。

不明点

  • ⚠️インスタンスメソッドと、クラスメソッドの違いは?
    • インスタンスメソッドはインスタンス化して呼び出す。クラスメソッドはインスタンスしなくても呼び出すことができる?

基本

  • echo等出力は、puts
  • if文などは、{中括弧}いらない。閉じるときは、end必須。(pythonと同じ?)
  • 配列はphpとほぼ同じ。
  • phpのforeachは、
arrays.each do | array |
    # 処理
end
  • phpの連想配列は、rubyだとハッシュという
exam = {"subject" => "Math", "score" => 80}

# キー「"subject"」の値を「"Science"」に更新してください
exam['subject'] = 'Science'

# キーが「"grade"」、値が「"good"」の要素を追加してください
exam['grade'] = 'good'

# キー「"grade"」の値を出力してください
puts exam['grade']
  • 文字列とは別にシンボルがある。 puts "ruby"puts :rubyとかける。
    • ⚠️明確な違い等は、要確認
    • シンボルは、ハッシュのキーとしてよく使われるらしい。
    • ハッシュのキーの部分を「:name」という様に、シンボルを用いた場合には、 その値を用いる時も「user[:name]」とシンボルで指定する必要がある。
# キーをシンボルで書き換えてください
exam = {:subject => "Math", :score => 80}

# キー「:score」の値を出力してください
puts exam[:score]

# 要素がハッシュの配列を作成してください
exams =[
  {subject: "Math", score: 80},
  {subject: "Science", score: 55}
]

# インデックス番号が1の要素の値を出力してください
puts exams[1]
puts exams[1][:score]

関数

  • 関数の基本形は以下の通り。
    • やはり endを忘れないようにする。
def introduce
  puts "こんにちは"
  puts "私はにんじゃわんこです"
  # 出力を追加してください
  
end

puts "-----自己紹介-----"
# introduceメソッドを呼び出してください
introduce

#引数あり
def print_info(item)
  puts "わんこでんきへようこそ!"
  puts "今日は#{item}がセール中です!"
end

print_info("ヘッドホン")


# reutrn
def discount(price)
# 「price / 2」を戻り値として返してください
 return price / 2
end
puts "テレビがセール中です!"

half_price = discount(15000)
puts "特別価格で#{half_price}円です"
  • 関数に?つけるとbooleanを返す関数!!!
# shipping_free?メソッドを定義してください
def shipping_free?(price)
  return price >= 5000
end

# if文の条件式でshipping_free?メソッドを呼び出してください
if shipping_free?(3000)
 puts "5000円以上のお買い上げなので送料はいただきません"
else
 puts "追加で送料をいただきます"
end
  • 引数は、キーワード引数も可能。
# キーワード引数を使うように書き換えてください
def buy(item:, price:, count:)
 puts "#{item}#{count}台のお買い上げです"
 puts "合計金額は#{price * count}円です"
end

# キーワード引数を使うように書き換えてください
buy(item: "テレビ", price: 15000, count: 2)

クラス

  • クラスは、 クラスの作成→インスタンスの利用、という流れになる。
    • class名の頭文字は大文字
    • 最後はendでしめる。
    • インスタンス変数は、attr_accessor :nameのように書く。
class Menu
  # インスタンス変数
  attr_accessor :name
  attr_accessor :price

    def info
    # インスタンス変数は、self.var
    return "#{self.name} #{self.price}円"
  end
end

# Menuクラスのインスタンスを生成
menu1 = Menu.new

# menu1のprice, nameに代入
menu1.name = "ピザ"
menu1.price = 800

# menu1のpriceを出力
puts menu1.price

# info関数を呼び出す
puts menu1.info
  • コンストラクタはinitializeを利用する。
class Menu
  attr_accessor :name
  attr_accessor :price
  
  def initialize(name:, price:)
    self.name = name
    self.price = price
  end
  
  def info
    return "#{self.name} #{self.price}円"
  end
  
  def get_total_price(count)
    total_price = self.price * count
    if count >= 3
      total_price -= 100
    end
    return total_price
  end
end

# 引数を渡してインスタンスを生成してください
menu1 = Menu.new(name: 'すし', price: 1000)
puts menu1.info


# ----------------------------------------

menu1 = Menu.new(name: "ピザ", price: 800)
menu2 = Menu.new(name: "すし", price: 1000)
menu3 = Menu.new(name: "コーラ", price: 300)
menu4 = Menu.new(name: "お茶", price: 200)

# 変数menusを定義して配列を代入してください
menus = [menu1, menu2, menu3, menu4]

# 変数indexを定義して「0」を代入してください

# phpのforeachみたいにkeyはないから、外から呼ぶ。
index = 0
menus.each do |menu|
  # 番号をつけてメニューの内容が出力されるように書き換えてください
  puts "#{index}. " + menu.info
  
  # 変数indexに1を加えて値を更新してください
  index += 1
end
  • 継承は、class 小クラス < 親クラス
class Drink < Menu
    # 継承先のインスタンス変数
    attr_accessor :amount
end
  • オーバーライド(メソッド)は小クラスに、親クラスと同じメソッドを書いてあげる。

class Menu
  attr_accessor :name
  attr_accessor :price

  def initialize(name:, price:)
    self.name = name
    self.price = price
  end
  
  def info
    return "#{self.name} #{self.price}円"
  end
  
  def get_total_price(count)
    total_price = self.price * count
    if count >= 3
      total_price -= 100
    end
    return total_price
  end
end

class Food < Menu
  attr_accessor :calorie
  
  # infoメソッドのオーバーライド。親要素と同じメソッド名なので、こちらが優先される。
  def info
    return "#{self.name} #{self.price}円 (#{self.calorie}kcal)"
    # return "#{self.name} #{self.price}円"
  end
  
  def calorie_info
    return "#{self.name}は#{self.calorie}kcalです"
  end
end
  • コンストラクタのメソッドは、super
class Menu
  attr_accessor :name
  attr_accessor :price

  def initialize(name:, price:)
    self.name = name
    self.price = price
  end
  
  def info
    return "#{self.name} #{self.price}円"
  end
  
  def get_total_price(count)
    total_price = self.price * count
    if count >= 3
      total_price -= 100
    end
    return total_price
  end
end

class Food < Menu
  attr_accessor :calorie
  
  def initialize(name:, price:, calorie:)
    # superを使って書き換えてください
    super(name: name, price:price)
  end
  
  def info
    return "#{self.name} #{self.price}円 (#{self.calorie}kcal)"
  end
  
  def calorie_info
    return "#{self.name}#{self.calorie}kcalです"
  end
end
  • 既存のクラス(埋め込みクラス?)Dateを利用する。
# 呼び出すときは、./ いらない。
require "date"

# 変数todayに、Date.todayの戻り値代入。
# Dateクラスのメソッドは他にもたくさんあり。
today = Date.today

# 変数todayをputsしてください
puts today

# 変数todayに対してsunday?
# これは、日曜日ならTrue, 違ったらFalseを返す。
puts today.sunday?
fukumanfukuman

シンボルについてはこちらを参考にさせていただく。
https://zenn.dev/kanoe/articles/352d78902c83e168db66

  • シンボルはオブジェクト
  • シンボルはImmutable(文字列はmutable)
  • シンボルは同値ならば必ず同一
  • 比較の処理はシンボルの方が高速

どっちでも問題ない場合は、シンボル利用したほうが良さそうだ。

fukumanfukuman

https://blog.jnito.com/entry/2022/07/20/080512

  • 初心者向けのざっくりとした指針を示すなら、「クラスメソッドとインスタンスメソッドの使い分けに迷ったら、とりあえずインスタンスメソッドにしておけ」と答えます。

  • インスタンス変数を全く使ってないので、この場合は次のようにクラスメソッドにしても問題ないですね。

fukumanfukuman

railsは、appディレクトリの中に、「models」「views」「controllers」が含まれている形

fukumanfukuman

routingの基本は、
config/routes.rbに、

Rails.application.routes.draw do
  root "controller_name#action_name"
end

の形。

fukumanfukuman

まだ、routingの関数の中身がわかってないな、、、。
下記の場合は、hello関数で、中身がよくわからない。
雰囲気で、hello, world!をフロントにレンダリングするんだろうなとはわかるんだが、一語一句わかったわけではない。

#app/controllers/application_controller.rb

  def hello
    render html: "hello, world!"
  end

ルーティングファイルもわかっていない。

Rails.application.routes.draw do
  root 'application#hello'
end
fukumanfukuman

チュートリアルのマイクロポストでは、user 1 対 多 micropostsで作成する

fukumanfukuman

Laravelので言う、
php artisan make:model User -c
が、
rails generate scaffold User name:string email:string
ぽい。

更に、php artisanrails generateにあたりそう。
さらにさらに、laravelだと --resourceCRUDのルート等を作成してくれるが、
generate scaffoldはそれをそのまま同じように実行してくれる。

generate scaffold で、ルーティングにはresources :usersが追加される
また、User controllerも追加されて、中にCRUDの処理が勝手に書かれる。

fukumanfukuman

2.1 演習

  1. (CSSの知識がある人向け)新しいユーザーを作成し、ブラウザのHTML検証機能を使って「User was successfully created.」の箇所を調べてみてください。ブラウザをリロードすると、その箇所はどうなるでしょうか?
  • <p style="color: green">User was successfully destroyed.</p>とあった記述が消える。Flushメッセージになっている。
  1. emailを入力せず、名前だけを入力しようとした場合、どうなるでしょうか?
  • 空白でも登録できた。
  1. 「@example.com」のような間違ったメールアドレスを入力して更新しようとした場合、どうなるでしょうか?
  • 登録できた。本当はバリデーションがあるのか?
  1. 上記の演習で作成したユーザーを削除してみてください。ユーザーを削除したとき、Railsはどんなメッセージを表示するでしょうか?
  • <p style="color: green">User was successfully destroyed.</p>
fukumanfukuman

routingの
root 'users#index'は、
/にアクセスしたときに表示するやつか。

fukumanfukuman

@users = User.allに関して。

User.allは、User Modelから全部抜き出すということっぽい?
@usersはインスタンス変数の記述があるので、User model objectをインスタンスにしたものだろうか。

class UsersController < ApplicationController
  def index
    @users = User.all
  end
# リスト 2.10の下
@記号で始まる変数をRubyではインスタンス変数と呼び、Railsのコントローラ内で宣言したインスタンス変数はビューでも使えるようになります。この場合、リスト 2.11のindex.html.erbビューは@usersの一覧を並べ、1行ごとにHTMLの行として出力します(今はこのコードの意味がわからなくても問題ありません。これはあくまで説明のためのものです)。
fukumanfukuman

疑問

laravelでは、controllerないのメソッドで、return viewすることで、どのviewファイルを利用するか選択した。

今回、rails generate scaffold Userで作ったコントローラーには、どのコントローラーがどのviewに返すのかを示していない。

今回は、generate scaffoldだから勝手にやってくれているのか?
本来は、どのviewに返すのかをどこに明示する必要があるのか?

fukumanfukuman

2.1 演習

  1. 図 2.12を参考にしながら、/users/1/editというURLにアクセスしたときの振る舞いについて図を書いてみてください。

  2. 図示した振る舞いを見ながら、Scaffoldで生成されたコードの中でデータベースからユーザー情報を取得しているコードを探してみてください。(ヒント: set_userという特殊な場所の中にあります。)

  • user controllerの一番下のprivate mthodにある。
    # Use callbacks to share common setup or constraints between actions.
    def set_user
      @user = User.find(params[:id])
    end
  1. ユーザーの情報を編集するページのファイル名は何でしょうか?
  • edit.html.erb
    • ※編集するページが何をさすのかイマイチわからない。
fukumanfukuman

2.15 演習

  1. (CSSの知識がある人向け)新しいマイクロポストを作成し、ブラウザのHTML検証機能を使って「Micropost was successfully created.」の箇所を調べてみてください。ブラウザをリロードすると、その箇所はどうなるでしょうか?
  • 省略
  1. (CSSの知識がある人向け)新しいマイクロポストを作成し、ブラウザのHTML検証機能を使って「Micropost was successfully created.」の箇所を調べてみてください。ブラウザをリロードすると、その箇所はどうなるでしょうか?
  • 作成できちゃう。
  1. 141文字以上の文字列をContentに入力した状態で、マイクロポストを作成しようとするとどうなるでしょうか?
  • 作成できる。
  1. 上記の演習で作成したマイクロポストを削除してみましょう。
  • 省略
fukumanfukuman

リレーションは、Laravelのそれとそっくり。
以下、ユーザー1に対してpostsが複数ある場合

class Micropost < ApplicationRecord
  belongs_to :user
  validates :content, length: { maximum: 140 }
end
class User < ApplicationRecord
  has_many :microposts
end
fukumanfukuman

2.17 演習

  1. ユーザーのshowページを編集し、ユーザーの最初のマイクロポストを表示してみましょう。同ファイル内の他のコードから文法を推測してみてください(コラム 1.2で紹介した技術の出番です)。うまく表示できたかどうか、/users/1にアクセスして確認してみましょう。
  • こう?
#user controller 
  private
    # Use callbacks to share common setup or constraints between actions.
    def set_user
      @user = User.find(params[:id])
      @users_microposts_first = @user.microposts.first
    end
# show.html.erb
<p style="color: green"><%= notice %></p>

<%= render @user %>
<%= render @users_microposts_first %>

  1. リスト 2.18は、マイクロポストのContentが存在しているかどうかを検証するバリデーションです。マイクロポストが空でないことを検証できているかどうか、実際に試してみましょう(図 2.18のようになっていると成功です)。
  • 省略
  1. リスト 2.19の(コードを書き込む)となっている箇所を書き換えて、Userモデルのnameとemailが存在していることを検証してみてください(図 2.19)。
  • こう?
#user model 
class User < ApplicationRecord
  has_many :microposts
  # バリデーション
  validates :name, presence: true
  validates :email, presence: true
end


追記

1番はこれでいけるわ。

# show.html.erb
<p style="color: green"><%= notice %></p>

<%= render @user %>

<%= @user.microposts.first.content %>

fukumanfukuman

2.3.4 演習

Applicationコントローラのファイルを開き、ApplicationControllerがActionController::Baseを継承している部分のコードを探してみてください。

ここ↓

class ApplicationController < ActionController::Base
  def hello
    render html: "hello, world!"
  end
end
ApplicationRecordがActiveRecord::Baseを継承しているコードはどこにあるでしょうか? 先ほどの演習を参考に、探してみてください。(ヒント: コントローラと本質的には同じ仕組みなので、app/modelsディレクトリ内にあるファイルを調べてみましょう。)

ここ↓

class ApplicationRecord < ActiveRecord::Base
  primary_abstract_class
end
fukumanfukuman

Rubyがクラス名にキャメルケースを使う慣習があり

おk

fukumanfukuman

3.2.1 演習

Fooというコントローラを生成し、その中にbarとbazアクションを追加してみてください。

rails generate controller Foo bar baz
コラム 3.1で紹介したテクニックを駆使して、Fooコントローラとそれに関連するアクションを削除してみてください

rails destroy  controller Foo bar baz
fukumanfukuman

テスト

rails generate controllerすると、testディレクトリにテストコードが入っている。

実行は、rails test

fukumanfukuman

例えば、config/routes.rbに、

Rails.application.routes.draw do
  get  "static_pages/about"
end

というルートを加える。
この結果、自動的にstatic_pages_about_urlというヘルパーが使えるようになります。

testコードでは、

  test "should get about" do
    get static_pages_about_url # ←←←←
    assert_response :success
  end

を確認することができる。

fukumanfukuman

コラム 3.3に記載されている、なぜテストをするのか?

  1. テストが揃っていれば、機能停止に陥るような回帰バグ(Regression Bug: 以前のバグが再発したり機能の追加/変更に副作用が生じたりすること)を防止できる。
  2. テストが揃っていれば、コードを安全にリファクタリング(機能を変更せずにコードを改善)できる。
  3. テストコードは、アプリケーションコードから見ればクライアントとして動作するので、アプリケーションの設計やシステムの他の部分とのインターフェイスを決めるときにも役に立つ。
fukumanfukuman

「テスト駆動」にするか「一括テスト」にするか

「テスト駆動」にするか「一括テスト」にするかを決める目安となるガイドラインがあると便利です。著者の経験を元に、次のようにまとめてみました。

  • アプリケーションのコードよりも明らかにテストコードの方が短くシンプルになる(=簡単に書ける)のであれば、「先に」書く
  • 動作の仕様がまだ固まりきっていない場合、アプリケーションのコードを先に書き、期待する動作を「後で」書く
  • セキュリティが重要な課題またはセキュリティ周りのエラーが発生した場合、テストを「先に」書く
    バグを見つけたら、そのバグを再現するテストを「先に」書き、回帰バグを防ぐ体制を整えてから修正に取りかかる
  • すぐにまた変更しそうなコード(HTML構造の細部など)に対するテストは「後で」書く
    リファクタリングするときは「先に」テストを書く。特に、エラーを起こしそうなコードや止まってしまいそうなコードを集中的にテストする
fukumanfukuman

HTMLビューのファイルの拡張子が.html.erbやつ。

これは、ERB(またはERb)と呼ばれている。

中で、rubyを書きたい場合は、
<% provide(:title, "Home") %>のように、<% %>で囲む。
出力したい場合は、=を加えて、 <%= ... %>と記載する。

fukumanfukuman

view

  1. application.html.erbで骨子を用意。中に挿入するための<%= yield %>を用意してあげる。
# /sample_app/app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <meta charset="utf-8">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
  </head>

  <body>
    # ↓これ
    <%= yield %>
  </body>
</html>
  1. yieldに入れるための各ページ特有のレイアウトを作成する。
# /sample_app/app/views/static_pages/home.html.erb
<% provide(:title, "Home") %>
<h1>Sample App</h1>
<p>
  This is the home page for the
  <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
  sample application.
</p>
fukumanfukuman

リスト 3.41 演習

サンプルアプリケーションにContact(問い合わせ先)ページを作成してください17 。(ヒント: まずはリスト 3.17を参考にして、/static_pages/contactというURLのページに「Contact | Ruby on Rails Tutorial Sample App」というタイトルが存在するかどうかを確認するテストを最初に作成しましょう。次に、3.3.3でAboutページを作ったときのと同じように、Contactページにもリスト 3.42のコンテンツを表示してみましょう。)
# Contactページで使うコード
# app/views/static_pages/contact.html.erb
<% provide(:title, "Contact") %>
<h1>Contact</h1>
<p>
  Contact the Ruby on Rails Tutorial about the sample app at the
  <a href="https://railstutorial.jp/contact">contact page</a>.
</p>
  1. test コードに以下追記
  test "should get contact" do
    get static_pages_contact_url
    assert_response :success
    assert_select "title", "Contact | #{@base_title}"
  end
  1. ルーティングに以下追加
Rails.application.routes.draw do
# 略
  get 'static_pages/contact'
# 略
end
  1. controllerに以下追記
  def contact
  end
  1. $ touch /sample_app/app/views/static_pages/contact.html.erbしたあと、以下追記
<% provide(:title, "Contact") %>
<h1>Contact</h1>
<p>
  Contact the Ruby on Rails Tutorial about the sample app at the
  <a href="https://railstutorial.jp/contact">contact page</a>.
</p>
fukumanfukuman

第4章。
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>

stylesheet_link_tagについては、公式

使い方は。
stylesheet_link_tag(スタイルシートへのパス..)

なるほど、assets配下にあるファイルへのリンクを自動生成してくれる、、、のかな?

以下公式から。

外部スタイルシートを指定するリンクタグを生成

stylesheet_link_tag "style"
#=> <link href="/assets/style.css" media="screen" rel="stylesheet" />
# 拡張子まで指定
stylesheet_link_tag "style.css"
#=> <link href="/assets/style.css" media="screen" rel="stylesheet" />
# URLで指定
stylesheet_link_tag "http://www.example.com/style.css"
#=> <link href="http://www.example.com/style.css" media="screen" rel="stylesheet" />
# 複数指定
stylesheet_link_tag "random.styles", "/css/stylish"
#=> <link href="/assets/random.styles" media="screen" rel="stylesheet" /><link href="/css/stylish.css" media="screen" rel="stylesheet" />
fukumanfukuman

view で利用できるhelperは、app/helpers/application_helper.rbに記載する。

fukumanfukuman

なるほど、
puts は勝手に改行してくれる。戻りはnil
print は改行しない。戻りはnil

>> puts "foo"
foo
=> nil
>> print "foo"    # 文字列の画面出力 (putsと同じだが改行がない)
foo=> nil
fukumanfukuman

4.2.1 演習

city変数に適当な市区町村名を、prefecture変数に適当な都道府県名を代入してください

>> city = '港区'
=> "港区"
>> prefecture = '東京都'
=> "東京都"

先ほど作った変数と式展開を使って、「東京都 新宿区」のような住所の文字列を作ってみましょう。出力にはputsを使ってください。

>> puts city + ' ' +  prefecture
港区 東京都
=> nil     

上記の文字列の間にある半角スペースをタブに置き換えてみてください。(ヒント: 改行文字と同じで、タブも特殊文字です。)

>> puts city + "\t" + prefecture
港区    東京都
=> nil 

タブに置き換えた文字列を、ダブルクォートからシングルクォートに置き換えてみるとどうなるでしょうか?

>> puts city + '/t' + prefecture
港区/t東京都
=> nil                                               
>> 
fukumanfukuman

boolの評価の仕方が?なの、わかりやすいけど慣れない。。。

>> "foobar".empty?
=> false
>> "".empty?
=> true

empty?メソッドの末尾にある疑問符にご注目ください。Rubyでは、メソッドがtrueまたはfalseという論理値(boolean)を返すことを、末尾の疑問符で示す慣習があります。 論理値は、特に処理の流れを変更するときに有用です。

fukumanfukuman

ifの使い方。
この場合は、xがemptyじゃなければx is not emptyが表示される。
unlessに置き換えることも可能。

>>x = 'test'
test

>> puts "x is not empty" if !x.empty?
x is not empty

>> puts "x is not empty" unless x.empty?
x is not empty
fukumanfukuman

4.2.2 演習

"racecar" の文字列の長さはいくつですか? lengthメソッドを使って調べてみてください。

>> 'racecar'.length
=> 7

reverseメソッドを使って、"racecar"の文字列を逆から読むとどうなるか調べてみてください。

>> 'racecar'.reverse
=> "racecar"

変数sに "racecar" を代入してください。その後、比較演算子(==)を使って変数sとs.reverseの値が同じであるかどうか、調べてみてください。

>> s = 'racecar'
=> "racecar"
>> s == s.reverse
=> true

変数sに "racecar" を代入してください。その後、比較演算子(==)を使って変数sとs.reverseの値が同じであるかどうか、調べてみてください。

>> puts "It's a palindrome!" if s == s.reverse
It's a palindrome!
=> nil 
>> s = "onomatopoeia"
=> "onomatopoeia"
>> puts "It's a palindrome!" if s == s.reverse
=> nil
fukumanfukuman

rubyでもreturn使えるんだ。
けど、みんな省略してるんだ。

def string_message(str = '')
   return "It's an empty string!" if str.empty?
   return "The string is nonempty."
 end
fukumanfukuman

4.2.3 演習 メソッドの定義

回答まとめ↓

def palindrome_tester(s)
  if s == s.reverse
    puts "It's a palindromeです!!"
  else
    puts "It's not a palindromeだ!"
  end
end

palindrome_tester('racecar')
palindrome_tester('onomatopoeia')
palindrome_tester('racecar').nil?
palindrome_tester('onomatopoeia').nil?
fukumanfukuman

Rubyのモジュールについて。

例えば、app/helpers/application_helper.rbの文頭にある
module ApplicationHelper

includeメソッドを使ってモジュールを読み込むことができます。
これをミックスイン(mixed in)とも呼びます。

単なるRubyのコードを書くのであれば、モジュールを作成するたびに明示的に読み込んで使うのが普通です。
つまり、モジュールAを作成したら、それを使いたいファイルでincludeしてあげる。

一方Railsでは自動的に(すべてのファイルで)ヘルパーモジュールを読み込んでくれるので、include行をわざわざ書く必要がありません。

つまり、このfull_titleメソッドは自動的にすべてのビューで利用できるようになっているということです。

→ここに書けば、わざわざincludeする必要ない。
→複数ファイルで読み込みたい場合は個々に書いてあげる。

fukumanfukuman

配列

配列の添字には、マイナス も利用可能。


>> a = [42, 8, 17]
=> [42, 8, 17]
>> a[0]               # Rubyでは角カッコで配列にアクセスする
=> 42
>> a[1]
=> 8
>> a[2]
=> 17
>> a[-1]              # 配列の添字はマイナスにもなれる!
=> 17
fukumanfukuman

配列に対してのメソッドは通常非破壊的

>> a = [42, 8, 17]
=> [42, 8, 17]
>> a
=> [42, 8, 17]
>> a.sort
=> [8, 17, 42]
>> a
=> [42, 8, 17]

破壊的メソッドを使うには、元のメソッドの末尾に「!」を追加したものを使う。

>> a.sort!
=> [8, 17, 42]
>> a
=> [8, 17, 42]
fukumanfukuman

範囲(range)は以下のように記す。
0..9

配列化するには、to_aを利用。
※丸括弧で配列化する文字列を囲ってあげる必要あり。

>> (0..9).to_a
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

a-zは、

('a'..'z').to_a

範囲は、配列の要素を取り出すことが可能。

>> a = %w[foo bar baz quux]         # %wを使って文字列の配列に変換
=> ["foo", "bar", "baz", "quux"]
>> a[0..2]
=> ["foo", "bar", "baz"]

fukumanfukuman

4.3.1 演習 配列と範囲演算子

文字列「A man, a plan, a canal, Panama」を ", " で分割して配列にし、変数aに代入してみてください。

>> a = 'A man, a plan, a canal, Panama'.split(',')
=> ["A man", " a plan", " a canal", " Panama"]

今度は、変数aの要素を連結した結果(文字列)を、変数sに代入してみてください。

>> s = a.join
=> "A man a plan a canal Panama"

変数sを半角スペースで分割した後、もう一度連結して文字列にしてください。(ヒント: メソッドチェーンを使うと1行でもできます。)

リスト 4.10で使った回文をチェックするメソッドを使って、(現状ではまだ)変数sが回文ではないことを確認してください。downcaseメソッドを使って、s.downcaseは回文であることを確認してください。

palindrome_tester(s.split(' ').join)
palindrome_tester(s.split(' ').join.downcase)

aからzまでの範囲オブジェクトを作成し、7番目の要素を取り出してみてください。

##同様にして、後ろから7番目の要素を取り出してみてください。(ヒント: 範囲オブジェクトを配列に変換するのを忘れないでください。)

alpabet = ('a'..'z').to_a
alpabet[7]
alpabet[-7]
fukumanfukuman

範囲オブジェクトに対してeachメソッドがある。

(1 .. 5).each { |i| puts i ** 2 }
(1 .. 5).each do | i |
    puts i ** 2
end

eachのあとの、

{ |i| puts i ** 2 }

もしくは、

do | i |
    puts i ** 2
end

の部分をブロックという。
iはそれぞれブロック変数という。

phpでいうforeach()valueの部分的なやつ。

fukumanfukuman

単体テストにもブロックがある。

test "should get home" do
  get static_pages_home_url
  assert_response :success
  assert_select "title", "Ruby on Rails Tutorial Sample App"
end

これは、testメソッドが、引数に文字列とブロックを利用している。

fukumanfukuman

4.3.2ブロック

範囲オブジェクト0..16を使って、各要素の2乗を出力してください

(0 .. 16).map do | num |
puts num ** 2
end

?> (0 .. 16).map do | num |
?>     puts num ** 2
0
1
4
9
16
25
36
49
64
81
100
121
144
169
196
225
256
=> [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil]

yeller(大声で叫ぶ)というメソッドを定義してください。このメソッドは、文字列の要素で構成された配列を受け取り、各要素を連結した後、大文字にして結果を返します。例えばyeller(['o', 'l', 'd'])と実行したとき、"OLD"という結果が返ってくれば成功です。(ヒント: mapとupcaseとjoinメソッドを使ってみましょう。)

?> def yeller(str_ary)
?>     puts str_ary.join.upcase
=> :yeller

>> yeller(['o', 'l', 'd'])
OLD
=> nil
>>

shuffled_subdomainというメソッドを定義してください。このメソッドは、完全にシャッフルされたアルファベット8文字を文字列として返します。(ヒント: サブドメインを作るときに使ったRubyコードをメソッド化したものです。)

def shuffled_subdomain()
    puts ('a' .. 'z').to_a.shuffle[0 .. 7].join
end

>> shuffled_subdomain()
gldpcmns
=> nil

リスト 4.12の「?」の部分を、それぞれ適切なメソッドに置き換えてみてください。(ヒント:split、shuffle、joinメソッドを組み合わせると、メソッドに渡された文字列(引数)をシャッフルさせることができます。)

# 文字列をシャッフルするメソッド(「?」を置き換えてください)
>> def string_shuffle(s)
>>   s.?('').?.?
>> end
>> string_shuffle("foobar")
=> "oobfra"
def string_shuffle(s)
    s.split('').shuffle.join
end

string_shuffle("foobar")
fukumanfukuman

ハッシュ

  • インデックスとして整数値以外のものも使える
  • つまり、phpの連想配列
>> user = {}                          # {}は空のハッシュ
=> {}
>> user["first_name"] = "Michael"     # キーが "first_name" で値が "Michael"
=> "Michael"
>> user["last_name"] = "Hartl"        # キーが "last_name" で値が "Hartl"
=> "Hartl"
>> user["first_name"]                 # 要素へのアクセスは配列の場合と似ている
=> "Michael"
>> user                               # ハッシュのリテラル表記
=> {"last_name"=>"Hartl", "first_name"=>"Michael"}
fukumanfukuman

シンボル

  • ハッシュのキーにする。

{ :name => "Michael Hartl", :email => "michael@example.com" }

  • これは、下のように書ける。

{ name : "Michael Hartl", email : "michael@example.com" }

fukumanfukuman

ハッシュは配列と同じように、eachメソッドが使える。
ハッシュは、key, valueあるので、この場合のeachメソッドもkey, valueが使える。

flash = { success: "It worked!", danger: "It failed." }

flash.each do |key, val|
    puts "Key #{key.inspect} has value #{val.inspect}"
end
fukumanfukuman

4.3.3ハッシュとシンボル

person1、person2、person3という3つのハッシュを作成し、それぞれのハッシュに:firstと:lastキーを追加し、適当な値(名前など)を入力してください。その後、次のようなparamsというハッシュのハッシュを作ってみてください。1)キーparams[:father]の値にperson1を代入、2)キーparams[:mother]の値にperson2を代入、3)キーparams[:child]の値にperson3を代入。最後に、ハッシュのハッシュを調べていき、正しい値になっているか確かめてみてください。(例えばparams[:father][:first]がperson1[:first]と一致しているか確かめてみてください)

person1 = { first: '山田', last: '太郎' }
person2 = { first: '田中', last: '二郎' }
person3 = { first: '木村', last: '三郎' }

params = { father: person1, mother: person2, child: person3 }

puts params[:father][:first]
puts params[:mother][:last]
puts params[:child][:first]

userというハッシュを定義してみてください。このハッシュは3つのキー:name、:email、:password_digestを持っていて、それぞれの値にあなたの名前、あなたのメールアドレス、そして16文字からなるランダムな文字列が代入されています

user = { name: 'ふくしま', email: 'example@example.com', password_digest: ('a'..'z').to_a.shuffle[0..15].join }
puts user[:name]
puts user[:email]
puts user[:password_digest]

「Ruby API」や「るりまサーチ」を使って、Hashクラスのmergeメソッドについて調べてみてください。次のコードを実行せずに、どのような結果が返ってくるか推測できますか? 推測できたら、実際にコードを実行して推測があっていたか確認してみましょう

省略

fukumanfukuman

まじかよ!

実は、Ruby ではメソッド呼び出しの丸カッコは省略しても構いません。次の2つの行は等価です

# メソッド呼び出しの丸カッコは省略可能。
stylesheet_link_tag("application", "data-turbo-track": "reload")
# 上は以下のように書いても同じ
stylesheet_link_tag "application", "data-turbo-track": "reload"

隠してんじゃね〜よ!

def plus (x, y)
    puts x + y
end

# 以下2つは同じ。
plus(2, 8)
plus 2, 8
fukumanfukuman

ハッシュがメソッド呼び出しの最後の引数である場合は、波カッコを省略できます。
隠し事多いな。

# 最後の引数がハッシュの場合、波カッコは省略可能。
stylesheet_link_tag("application", { "data-turbo-track": "reload" })
# 上は以下のように書いても同じ
stylesheet_link_tag("application", "data-turbo-track": "reload")

ってことは、stylesheet_link_tag は第1引数に文字列、第2引数にハッシュを求めるんだ。

fukumanfukuman

4.4.1 コンストラクタ

1から10の範囲オブジェクトを生成するリテラルコンストラクタは何でしたか?(復習です)

num = (1 .. 10)

今度はRangeクラスとnewメソッドを使って、1から10の範囲オブジェクトを作ってみてください。(ヒント: newメソッドに2つの引数を渡す必要があります。)

num2 = Range.new(1, 10)

比較演算子==を使って、上記2つの課題で作ったそれぞれのオブジェクトが同じであることを確認してみてください

puts num == num2
true
=> nil
fukumanfukuman

4.4.2クラスの継承

Rangeクラスの継承階層を調べてみてください。同様にして、HashとSymbolクラスの継承階層も調べてみてください

num = Range.new(1, 10)
num.class.superclass
num.class.superclass.superclass

hush = {}
hush.class
hush.class.superclass
hush.class.superclass.superclass


sym = :sym
sym.class
sym.class.superclass
sym.class.superclass.superclass

リスト 4.15にあるself.reverseのselfを省略し、reverseと書いてもうまく動くことを確認してみてください

class Word < String
  def palindrome?
    self == reverse
  end
end

=> :palindrome?

s = Word.new("level")
s.palindrome?
fukumanfukuman

驚いたことに、Rubyに組み込まれている基本クラスは拡張可能なのです。

!?

組み込みクラスの変更はきわめて強力なテクニックですが、大いなる力には大いなる責任が伴います27 。このため、真に正当な理由がない限り、組み込みクラスにメソッドを追加することは無作法であると考えられています。

fukumanfukuman

4.4.3組み込みクラスの変更

palindrome?メソッドを使って、“racecar”が回文であり、“onomatopoeia”が回文でないことを確認してみてください。南インドの言葉「Malayalam」は回文でしょうか? (ヒント: downcaseメソッドで小文字にすることをお忘れなく。)

class String
  def palindrome?
    self == reverse
  end
end

"racecar".palindrome?
"onomatopoeia".palindrome?
"Malayalam".downcase.palindrome?

リスト 4.16を参考に、Stringクラスにshuffleメソッドを追加してみてください。(ヒント: リスト 4.12も参考になります。)

class String
  def shuffle
    self.split('').shuffle.join
  end
end

"foobar".shuffle

リスト 4.16のコードにおいて、self.を削除してもうまく動くことを確認してください

class String
  def shuffle
    split('').shuffle.join
  end
end

"foobar".shuffle
fukumanfukuman

4.4.4コントローラクラス

第2章で作ったToyアプリケーションのディレクトリでRailsコンソールを開き、User.newと実行することでuserオブジェクトが生成できることを確認してみましょう

生成したuserオブジェクトのクラスの継承階層を調べてみてください

>> user = User.new
=> #<User:0x00007fd03b3cd670 id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>
>> user.class
=> User(id: integer, name: string, email: string, created_at: datetime, updated_at: datetime)
=> ApplicationRecord(abstract)
=> ActiveRecord::Base
=> Object
=> BasicObject
=> nil
>>
fukumanfukuman

<%= link_to "sample app", '#', id: "logo" %>
link_to <a>タグの作成。

引数は、

  1. リンクテキスト
  2. リンク先URL
  3. html idの設定
fukumanfukuman

演習 5.1構造を追加する

Webページと言ったらネコ画像、というぐらいにはWebにはネコ画像が溢れていますよね。リスト 5.4のコマンドを使って、図 5.3のネコ画像をダウンロードしてきましょう11

curl -OL https://cdn.learnenough.com/kitten.jpg

mvコマンドを使って、ダウンロードしたkitten.jpgファイルを適切なアセットディレクトリに移動してください(参考: 5.2.1)

mv kitten.jpg app/assets/images/

image_tagを使って、kitten.jpg画像を表示してみてください(図 5.4)

# /sample_app/app/views/static_pages/home.html.erb
# 以下追記
<%= link_to image_tag("kitten.jpg", alt: "子猫", width: "200") %>
fukumanfukuman

演習 5.1.2BootstrapとカスタムCSS

リスト 5.10を参考にして、5.1.1.1の演習で使ったネコ画像をコメントアウトしてみてください。また、ブラウザのHTMLインスペクタ機能を使って、コメントアウトするとHTMLのソースからも消えていることを確認してみてください

<%#= image_tag("kitten.jpg", alt: "Kitten") %>

HTMLのソースからも消えていることを確認は省略。(dev toolsで確認)

リスト 5.11のコードをcustom.scssに追加し、すべての画像を非表示にしてみてください。うまくいけば、Railsのロゴ画像がHomeページから消えるはずです。先ほどと同様にインスペクタ機能を使って、今度はHTMLのソースコードは残ったままで、画像だけが表示されなくなっていることを確認してみてください。この演習による変更は、終わった後必ず元に戻してください(そうしないとこの後の画像が正しく表示されません)

省略。

fukumanfukuman

パーシャル(partial)という機能

これはコンポーネント的なことか?

<%= render 'layouts/header' %>

これで、app/views/layouts/_header.html.erbを表示する。

ファイル名_header.html.erbの先頭にあるアンダースコアに注目してください。このアンダースコアは、パーシャルで使う普遍的な命名規約であり、また、一目見ただけでディレクトリ中のすべてのパーシャルを識別することが可能になります。

なるほど。
パーシャルで利用したい場合は、_をファイル名につけて上げればいいのか。

fukumanfukuman

演習 5.1.3パーシャル(partial)

Railsがデフォルトで生成するheadタグの部分を、リスト 5.17のようにrenderに置き換えてみてください。(ヒント: 単純に削除してしまうと後でパーシャルを1から書き直す必要が出てくるので、削除する前にどこかに退避しておきましょう。)

# app/views/layouts/application.html.erb
<%= render 'layouts/rails_default' %>

リスト 5.17のようなパーシャルはまだ作っていないので、現時点ではテストは red になっているはずです。実際にテストを実行して確認してみましょう

省略。

layoutsディレクトリにheadタグ用のパーシャルを作成し、先ほど退避しておいたコードを書き込み、最後にテストが green に戻ることを確認しましょう

touch app/views/layouts/_rails_default.html.erb
# app/views/layouts/_rails_default.html.erbの中身をいかに変更
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>

テストはgreenになったことを確認。

fukumanfukuman

アセットディレクトリ

Railsのアセットパイプラインでは、静的ファイルを目的別に分類する、標準的な3つのディレクトリが使われています。

  • app/assets: 現在のアプリケーション固有のアセット
  • lib/assets: あなたの開発チームによって作成されたライブラリ用のアセット
  • vendor/assets: サードパーティのアセット(デフォルトでは存在しません)

静的ファイル(アセット)を上記の場所へそれぞれ配置すれば、マニフェストファイルを使って、それらをどのように1つのファイルにまとめるのかをRailsに指示することができます。

実際にアセットをまとめる処理を行うのはSprocketsというgemです。

また、マニフェストファイルはCSSとJavaScriptには適用されますが、画像ファイルには適用されません。1つの具体例として、アプリケーションのCSS用マニフェストファイルを見てみましょう

fukumanfukuman

ルーティングの修正

helpページへのルーティングは例えば以下のようにする。

# getで /helpにアクセスした時にstatic_pagesコントローラーのhelpメソッドが呼ばれる。

get  "/help", to: "static_pages#help"

また、このように設定すると、
help_path
help_url
が利用できるようになる。

help_path -> '/help'
help_url  -> 'https://www.example.com/help'

更にasを利用すれば、名前をつけることも可能。

 get  "/help",    to: "static_pages#help", as: 'helf'
fukumanfukuman

演習 5.3.2RailsのルートURL

実は名前付きルーティングは、as:オプションを使って変更することができます。有名なFar Sideの漫画に倣って、Helpページの名前付きルーティングをhelfに変更してみてください(リスト 5.28)

# routes.rb
Rails.application.routes.draw do
  root "static_pages#home"
  get  "/help",    to: "static_pages#help", as: 'helf'
  get  "/about",   to: "static_pages#about"
  get  "/contact", to: "static_pages#contact"
end

先ほどの変更により、テストが red になっていることを確認してください。リスト 5.27を参考にルーティングを更新して、テストを green にして見てください

  test "should get help" do
    get helf_path
    assert_response :success
    assert_select "title", "Help | #{@base_title}"
  end

エディタのUndo機能を使って、今回の演習で行った変更を元に戻してください

省略

fukumanfukuman

演習 5.3.3名前付きルーティング

リスト 5.28のようにhelfルーティングを作成し、レイアウトのリンクを更新してみてください

# routes.rb
Rails.application.routes.draw do
  root "static_pages#home"
  get  "/help",    to: "static_pages#help", as: 'helf'
  get  "/about",   to: "static_pages#about"
  get  "/contact", to: "static_pages#contact"
end
# _header.html.erb
<nav>
    <ul class="nav navbar-nav navbar-right">
    <li><%= link_to "Home",    root_path %></li>
    <li><%= link_to "Help",    helf_path %></li>
    <li><%= link_to "Log in", '#' %></li>
    </ul>
</nav>

前回の演習と同様に、エディタのUndo機能を使ってこの演習で行った変更を元に戻してみてください

省略

fukumanfukuman

統合テスト(Integration Test)

リンクが動くかチェックする。

$ rails generate integration_test site_layout

  1. ルートURL(Homeページ)にGETリクエストを送る。
  2. 正しいページテンプレートが描画されているかどうか確かめる。
  3. Home、Help、About、Contactの各ページへのリンクが正しく動くか確かめる。

/sample_app/test/integration/site_layout_test.rb

require "test_helper"

class SiteLayoutTest < ActionDispatch::IntegrationTest

  test "layout links" do
    get root_path
    assert_template 'static_pages/home'
    assert_select "a[href=?]", root_path, count: 2
    assert_select "a[href=?]", help_path
    assert_select "a[href=?]", about_path
    assert_select "a[href=?]", contact_path
  end
end

例えば、assert_select "a[href=?]", about_path?の部分に第2引数のabout_pathを入れている。
結果、<a href="/about">...</a>というリンクがHTMLの中にあるか確認する。

同じリンクがX個ある場合は、
count: Xと追記する。

assert_select HOGEHOGEには他にもたくさん種類があるよ!

test実行は、$ rails test:integration

fukumanfukuman

演習 5.3.4リンクのテスト

footerパーシャルのabout_pathをcontact_pathに変更してみて、テストが正しくエラーを捕まえてくれるかどうか確認してみてください

省略。なった。

リスト 5.34で示すように、Applicationヘルパーで使っているfull_titleヘルパーを、test環境でも使えるようにすると便利です。こうしておくと、リスト 5.35のようなコードを使って、正しいタイトルをテストすることができます。ただし、これは完璧なテストではありません。例えばベースタイトルに「Ruby on Rails Tutoial」といった誤字があったとしても、このテストでは発見することができないでしょう。この問題を解決するためには、full_titleヘルパーに対するテストを書く必要があります。そこで、Applicationヘルパーをテストするファイルを作成し、リスト 5.36の(コードを書き込む)の部分を適切なコードに置き換えてみてください。(ヒント: リスト 5.36ではassert_equal <期待される値>, <実際の値>といった形で使っていましたが、内部では==演算子で期待される値と実際の値を比較し、正しいかどうかのテストをしています。)

# test/helpers/application_helper_test.rb

require 'test_helper'

class ApplicationTest < ActionView::TestCase

  test "full title helper" do
    assert_equal full_title, "Ruby on Rails Tutorial Sample App"
    assert_equal full_title("Help"), "Help | Ruby on Rails Tutorial Sample App"
  end
end
fukumanfukuman

演習 5.4.1Usersコントローラ

表 5.1を参考にしながらリスト 5.40を変更し、users_new_urlではなくsignup_pathを使えるようにしてみてください

# routes.rb

Rails.application.routes.draw do
  root "static_pages#home"
  get  "/help",    to: "static_pages#help"
  get  "/about",   to: "static_pages#about"
  get  "/contact", to: "static_pages#contact"

  # user
  get '/signup',  to: 'users#new'
end
# users_controller_test.rb
require "test_helper"

class UsersControllerTest < ActionDispatch::IntegrationTest
  test "should get new" do
    get signup_path
    assert_response :success
  end
end

先ほどの変更を加えたことにより、テストが red になったことを確認してください。なお、この演習はテスト駆動開発(コラム 3.3)で説明した red / green のリズムを作ることを目的としています。このテストは次の5.4.2で green になるよう修正します

省略

fukumanfukuman

5.4.2ユーザー登録用URL

もしまだ5.4.1.1の演習に取り掛かっていなければ、まずはリスト 5.40のように変更し、名前付きルーティングsignup_pathを使えるようにしてください。また、リスト 5.42で名前付きルーティングが使えるようになったので、現時点でテストが green になっていることを確認してください

対応済みなので省略

先ほどのテストが正しく動いていることを確認するため、signupルートの部分をコメントアウトし、テスト red になることを確認してください。確認できたら、コメントアウトを解除して green の状態に戻してください

省略(問題なくできた。

リスト 5.31の統合テストにsignupページにアクセスするコードを追加してください(getメソッドを使います)。コードを追加したら実際にテストを実行し、結果が正しいことを確認してください。(ヒント: リスト 5.35で紹介したfull_titleヘルパーを使ってみてください。)

# site_layout_test.rb

require "test_helper"

class SiteLayoutTest < ActionDispatch::IntegrationTest

  test "layout links" do
    get root_path
    assert_template 'static_pages/home'
    assert_select "a[href=?]", root_path, count: 2
    assert_select "a[href=?]", help_path
    assert_select "a[href=?]", about_path
    assert_select "a[href=?]", contact_path
    assert_select "a[href=?]", signup_path
    
    get contact_path
    assert_select "title", full_title("Contact")
    
    get signup_path
    assert_select "title", full_title("Sign up")
  end
end
fukumanfukuman

model作成は、

$ rails generate model User name:string email:string

name:stringやemail:stringオプションのパラメータを渡すことによって、データベースで使いたい2つの属性をRailsに伝えます。このときに、これらの属性の型情報(この場合はstring)も一緒に渡します。

fukumanfukuman

db/migrate/20230206131623_create_users.rbといった感じで作成される。
テーブル名は通常複数形。
timestampsは自動(creared_at,updated_at)

ここらへんも、laravelのmigrationにそっくり。

class CreateUsers < ActiveRecord::Migration[7.0]
  def change
    create_table :users do |t|
      t.string :name
      t.string :email

      t.timestamps
    end
  end
end

これらが作成されたら、マイグレーションの実行を行う。

$ rails db:migrate

fukumanfukuman

演習 6.1Userモデル

省略。
確認してね系だけしかないので。

fukumanfukuman

演習 6.1.2モデルファイル

Railsコンソールを開き、User.newでUserクラスのオブジェクトが生成されること、そしてそのオブジェクトがApplicationRecordを継承していることを確認してみてください。(ヒント: 4.4.4で紹介したテクニックを使ってみてください。)

同様の方法で、ApplicationRecordがActiveRecord::Baseを継承していることも確認してみてください

>> User.new
>> user.class.superclass
=> ApplicationRecord(abstract)
>> user.class.superclass.superclass
=> ActiveRecord::Base
>> user.class.superclass.superclass.superclass
=> Object
>> user.class.superclass.superclass.superclass.superclass
=> BasicObject
>> user.class.superclass.superclass.superclass.superclass.superclass
=> nil
>>
fukumanfukuman

consoleにてサンドボックス状態(DBに保存されない = rollbackされる)で利用するには。

$ rails console --sandbox

fukumanfukuman

6.1.3ユーザーオブジェクトを作成する

user.nameとuser.emailが、どちらもStringクラスのインスタンスであることを確認してみてください

>> user.name.class
=> String
>> user.email.class
=> String

created_atとupdated_atは、どのクラスのインスタンスでしょうか?

>> user.created_at.class
=> ActiveSupport::TimeWithZone
>> user.updated_at.class
=> ActiveSupport::TimeWithZone
fukumanfukuman

userの作成方法

1つ目

user = User.new(hogefuga)
user.save

2つ目

User.create(hogefuga)
fukumanfukuman

userの検索

# idで検索
User.find(1)
# 属性を指定して検索
User.find_by(email: "michael@example.com")
# 最初のデータを取得
User.first
# 全部取得
User.all
fukumanfukuman

更新

user.email = "foo@bar.com"
user.save

わざわざsaveめんどい

user.update(name: "The Dude", email: "dude@abides.org")

バリデーションが1つでも失敗すると、updateの呼び出しは失敗するので特定の属性だけやりたい場合は↓

user.update_attribute(:name, "El Duderino")
fukumanfukuman

演習 6.1.5ユーザーオブジェクトを更新する

userオブジェクトへの代入を使ってname属性を使って更新し、saveで保存してみてください

>> user
=>
 id: 1,       _attribute(:name, "El Duderino")
 name: "El Duderino",  SAVEPOINT active_record_1
 email: "dude@abides.org",ATE "users" SET "name" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["name", "El Duderino"], ["updated_at", "2023-02-06 13:47:02.208205 created_at: Mon, 06 Feb 2023 13:31:14.133761000 UTC +00:00,
 updated_at: Mon, 06 Feb 2023 13:47:02.208205000 UTC +00:00>
>> 


>> user.name = "pero"
=> "pero"
user.save
  TRANSACTION (0.1ms)  SAVEPOINT active_record_1
  User Update (0.2ms)  UPDATE "users" SET "name" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["name", "pero"], ["updated_at", "2023-02-06 13:50:22.800382"], ["id", 1]]
  TRANSACTION (0.1ms)  RELEASE SAVEPOINT active_record_1
=> true
>> 

>> user
=> 
#<User:0x00007fee2432baf82023 13:47:02.208205000 UTC +00:00>
 id: 1,        "pero"
 name: "pero",
 email: "dude@abides.org",EPOINT active_record_1
 created_at: Mon, 06 Feb 2023 13:31:14.133761000 UTC +00:00,ed_at" = ? WHERE "users"."id" = ?  [["name", "pero"], ["updated_at", "2023-02-06 13:50:22.800382"], ["i updated_at: Mon, 06 Feb 2023 13:50:22.800382000 UTC +00:00>
>> 

今度はupdateを使って、email属性を更新および保存してみてください


user.update(email: "test@example.com")
  TRANSACTION (0.1ms)  SAVEPOINT active_record_1
  User Update (0.1ms)  UPDATE "users" SET "email" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["email", "test@example.com"], ["updated_at", "2023-02-06 13:51:23.843930"], ["id", 1]]
  TRANSACTION (0.1ms)  RELEASE SAVEPOINT active_record_1
=> true

>> user
=> #<User:0x00007fee2432baf82023 13:31:14.133761000 UTC +00:00,ed_at" = ? WHERE "users"."id" = ?  [["name", "pero"], ["updated_at", "2023-02-06 13:50:22.800382"], ["i id: 1,       on, 06 Feb 2023 13:50:22.800382000 UTC +00:00>
 name: "pero",(email: "test@example.com")
 email: "test@example.com",POINT active_record_1
 created_at: Mon, 06 Feb 2023 13:31:14.133761000 UTC +00:00,ted_at" = ? WHERE "users"."id" = ?  [["email", "test@example.com"], ["updated_at", "2023-02-06 13:51:23 updated_at: Mon, 06 Feb 2023 13:51:23.843930000 UTC +00:00>
>> 

同様に、マジックカラムであるcreated_atも直接更新できることを確認してみてください。(ヒント: 1.year.agoで更新すると便利です。これはRails流の時間指定の1つで、現在の時刻から1年前の日時を算出してくれます。)

user.update_attribute(:created_at, 1.year.ago)
>> user
=> 
#<User:0x00007fee2432baf82023 13:31:14.133761000 UTC +00:00,ted_at" = ? WHERE "users"."id" = ?  [["email", "test@example.com"], ["updated_at", "2023-02-06 13:51:23 id: 1,       on, 06 Feb 2023 13:51:23.843930000 UTC +00:00>
 name: "pero",_attribute(:created_at, 1.year.ago)
 email: "test@example.com",POINT active_record_1
 created_at: Sun, 06 Feb 2022 13:52:36.331750000 UTC +00:00,"updated_at" = ? WHERE "users"."id" = ?  [["created_at", "2022-02-06 13:52:36.331750"], ["updated_at",  updated_at: Mon, 06 Feb 2023 13:52:36.332187000 UTC +00:00>
>> 

fukumanfukuman

演習 6.2.2存在性を検証する

nameもemailも空の新しいユーザーuを作成し、作成した時点では有効ではない(invalid)ことを確認してください。なぜ有効ではないのでしょうか? エラーメッセージを確認してみましょう。

>> u = User.new(name: "   ", email: "    ")
=> #<User:0x00007f1811459c98 id: nil, name: "   ", email: "    ", created_at: nil, updated_at: nil>
>> u.save
=> false
>> u.valid?
=> false
>> u.errors.messages
=> {:name=>["can't be blank"], :email=>["can't be blank"]}
>> 

u.errors.messagesを実行すると、ハッシュ形式でエラーが取得できることを確認してください。emailに関するエラー情報だけを取得したい場合、どうやって取得すれば良いでしょうか?

>> u.errors.messages[:email]
=> ["can't be blank"]
fukumanfukuman

演習 6.2.2存在性を検証する

nameもemailも空の新しいユーザーuを作成し、作成した時点では有効ではない(invalid)ことを確認してください。なぜ有効ではないのでしょうか? エラーメッセージを確認してみましょう。

>> u = User.new(name: "   ", email: "    ")
=> #<User:0x00007f1811459c98 id: nil, name: "   ", email: "    ", created_at: nil, updated_at: nil>
u.name = 'testtest' * 10
=> "testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest"
u.email = 'test' * 100 + 'example.co.jp'
=> "testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttestte...
>> u.save
=> false

u.errors.messages
=> 
{:name=>
  ["is too long (maximum is 50 characters)",
   "is too long (maximum is 50 characters)",
   "is too long (maximum is 50 characters)",
   "is too long (maximum is 50 characters)"],
 :email=>
  ["is too long (maximum is 255 characters)",
   "is too long (maximum is 255 characters)",
   "is too long (maximum is 255 characters)",
   "is too long (maximum is 255 characters)"]}
>> 

長さに関するバリデーションが失敗した時、どんなエラーメッセージが生成されるでしょうか? 確認してみてください。

省略

fukumanfukuman

6.2.4フォーマットを検証する

Rubularの対応なので、省略

fukumanfukuman

6.3.2ユーザーがセキュアなパスワードを持っている

この時点では、userオブジェクトに有効な名前とメールアドレスを与えても、valid?で失敗してしまうことを確認してみてください。

>> user = User.new(name: "test", email: "test@example.com")
=> #<User:0x00007fa33da616d8 id: nil, name: "test", email: "test@example.com", created_at: nil, updated_at: nil, password_digest: nil>
user.valid?
  User Exists? (0.2ms)  SELECT 1 AS one FROM "users" WHERE "users"."email" = ? LIMIT ?  [["email", "test@example.com"], ["LIMIT", 1]]
=> false

なぜ失敗してしまうのでしょうか? エラーメッセージを確認してみてください

>> user.errors.messages
=> {:password=>["can't be blank"]}
fukumanfukuman

6.3.3パスワードの最小文字数

有効な名前とメールアドレスでも、パスワードが短すぎるとuserオブジェクトが有効にならないことを確認してみましょう。

上で失敗した時、どんなエラーメッセージになるでしょうか? 確認してみましょう

user = User.new(name: "test", email: "test@example.com", "password": "abc")
Loading development environment (Rails 7.0.4)
=> #<User:0x00007f12c5d09678 id: nil, name: "test", email: "test@example.com", created_at: nil, updated_at: nil, password_digest: "[FILTERED]">

user.valid?
  User Exists? (0.2ms)  SELECT 1 AS one FROM "users" WHERE "users"."email" = ? LIMIT ?  [["email", "test@example.com"], ["LIMIT", 1]]
=> false

>> user.errors.messages
=> {:password=>["is too short (minimum is 6 characters)"]}
fukumanfukuman

debugは以下で行ける。
var_dump的な。
<%= debug(params) if Rails.env.development? %>

fukumanfukuman

7.1.1デバッグとRails環境

ブラウザで/aboutにアクセスし、デバッグ情報が表示されていることを確認してください。このページを表示するとき、どのコントローラとアクションが使われていたでしょうか?paramsの内容から確認してみましょう。

paramsには以下の記述がある。

#<ActionController::Parameters {"controller"=>"static_pages", "action"=>"about"} permitted: false>

controllerstatic_pagesactionaboutというのがわかる。

Railsコンソールを開き、データベースから最初のユーザー情報を取得し、変数userに格納してください。その後、puts user.attributes.to_yamlを実行すると何が表示されますか? ここで表示された結果と、yメソッドを使ったy user.attributesの実行結果を比較してみましょう。

多分、
puts user.attributes.to_yamlと、

y user.attributesは内容変わってないっぽい

>> user = User.find(1)
  User Load (1.8ms)  SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
=> 
#<User:0x00007ff452f192c8
...
>> 
>> puts user.attributes.to_yaml
---
id: 1                                     
name: "   "                               
email: "    "
created_at: !ruby/object:ActiveSupport::TimeWithZone
  utc: 2023-02-11 08:38:40.308540000 Z
  zone: &1 !ruby/object:ActiveSupport::TimeZone
    name: Etc/UTC
  time: 2023-02-11 08:38:40.308540000 Z
updated_at: !ruby/object:ActiveSupport::TimeWithZone
  utc: 2023-02-11 08:38:40.308540000 Z
  zone: *1
  time: 2023-02-11 08:38:40.308540000 Z
password_digest: 
=> nil
>> 

>> y user.attributes
---
id: 1                                
name: "   "                          
email: "    "                        
created_at: !ruby/object:ActiveSupport::TimeWithZone
  utc: 2023-02-11 08:38:40.308540000 Z
  zone: &1 !ruby/object:ActiveSupport::TimeZone
    name: Etc/UTC
  time: 2023-02-11 08:38:40.308540000 Z
updated_at: !ruby/object:ActiveSupport::TimeWithZone
  utc: 2023-02-11 08:38:40.308540000 Z
  zone: *1
  time: 2023-02-11 08:38:40.308540000 Z
password_digest: 
=> nil
>> 

user = User.find(1)

fukumanfukuman

ルーティングに
resources :usersの1行追加するだけでREST fulなルートが提供される!

laravelの

Route::resource('rest','RestappController');

だ。

fukumanfukuman

7.1.2Usersリソース

ERBを使って、マジックカラム(created_atとupdated_at)の値をshowページに表示してみましょう(リスト 7.4)。

<%= @user.name %>, <%= @user.email %>
<br>
<%= @user.created_at %>, <%= @user.updated_at %>

ERBを使って、Time.nowの結果をshowページに表示してみましょう。ページを更新すると結果がどう変わるかも確認してみてください。

<%= @user.name %>, <%= @user.email %>
<br>
<%= @user.created_at %>, <%= @user.updated_at %>
<br>
<%= Time.now %>

→ 多分UTCっぽい時刻が表示されている。