基本機能の実装と応用機能の実装がそれぞれ4時間くらいなんだけど、4時間で終わるわけない。
この時間で終わる人は理解してないが、もともと知ってた人だ。
基本機能の実装で丸1日かかったよ。それでも理解には程遠い。
進捗のアドバンテージもなくなった。
(中間テスト後に丸1週間復習期間があるから問題ないけど)
面倒な質問だと思うが、メンターさんに聞くと真摯に答えてくれる。
ありがたい。けどRailsの根本的な問題だったりして、結局解決には至らなかったり。
やったことメモ
あいまい検索
文字列 | 意味 |
---|---|
% | 任意の文字列(空白文字列含む) |
_ | 任意の1文字 |
実行例 | 詳細 |
---|---|
where(‘title LIKE(?)’, “a%”) | aから始まるタイトル |
where(‘title LIKE(?)’, “%b”) | bで終わるタイトル |
where(‘title LIKE(?)’, “%c%”) | cが含まれるタイトル |
where(‘title LIKE(?)’, “d_”) | dで始まる2文字のタイトル |
where(‘title LIKE(?)’, “_e”) | eで終わる2文字のタイトル |
routes.rbの書き方例
Rails.application.routes.draw do
devise_for :users
root 'tweets#index'
resources :tweets do
resources :comments, only:[:create]
end
resources :users, only:[:show]
# get 'tweets' => 'tweets#index'
# get 'tweets/new' => 'tweets#new'
# post 'tweets' => 'tweets#create'
# delete 'tweets/:id' => 'tweets#destroy'
# get 'tweets/:id' => 'tweets#show'
# get 'tweets/:id/edit' => 'tweets#edit'
# patch 'tweets/:id' => 'tweets#update'
# get 'users/:id' => 'users#show'
end
Rubyのバージョン管理とGemのバージョン管理
rubyはrubyで、gemはgemで独立して管理されている。
間違い -ruby 1 - gem1,gem2,gem3 -ruby 2 - gem1,gem2,gem3 正解 -Ruby- ruby1, ruby2 -gem - gem1, gem2, gem3
同一gemの別バージョンも同じように管理されているため、porjectが増えるとバージョンが混在して依存関係で問題がでる。このような場合にはbundle execをつけて実行することでgemfile内の情報を辿って指定のバージョンのgemを利用できる。
ざっくり言えばエラーが出ない限りはbundle execを付ける意味はない。エラーが出たらbundle execをつけましょう。
rails g modelやrails g controllerをする際にはルーティングファイルも参照されるのでエラーに注意
resoucesメソッド
resources:reviews, only:[:new, :create]
上は下と同義
get 'products/:product_id/reviews/new' => 'reviews#new'
post 'products/:product_id/reviews' => 'reviews#create'
resourcesメソッドのネスト
resources :books do
resources :reviews
end
Prefix Verb URI Pattern Controller#Action
book_reviews GET /books/:book_id/reviews(.:format) reviews#index
POST /books/:book_id/reviews(.:format) reviews#create
new_book_review GET /books/:book_id/reviews/new(.:format) reviews#new
edit_book_review GET /books/:book_id/reviews/:id/edit(.:format) reviews#edit
book_review GET /books/:book_id/reviews/:id(.:format) reviews#show
PATCH /books/:book_id/reviews/:id(.:format) reviews#update
PUT /books/:book_id/reviews/:id(.:format) reviews#update
DELETE /books/:book_id/reviews/:id(.:format) reviews#destroy
books GET /books(.:format) books#index
POST /books(.:format) books#create
new_book GET /books/new(.:format) books#new
edit_book GET /books/:id/edit(.:format) books#edit
book GET /books/:id(.:format) books#show
PATCH /books/:id(.:format) books#update
PUT /books/:id(.:format) books#update
DELETE /books/:id(.:format)
form_for
ヘルパーメソッド。特定のモデルを編集・追加するためのフォームを生成する。
特定のテーブルにレコードだけを新規作成・更新するときに使う。
<%= form_for(モデルクラスのインスタンス) do |f| %>
…
<% end %>
form_forは引数のインスタンスが何も情報を持っていなければ自動的にcreateアクションへ、
すでに情報を持っている場合はupdateアクションへ自動的に振り分ける。
<%= f.text_field :name %>
これは下のように変換される
<input id="モデル名_name" name="モデル名[name]" type="text" size="モデルで設定したsize">
f.label | labelのlabelタグを表示 |
f.text_field | textのinputタグを表示 |
f.date_select | モデルで設定したフィールドをselectタグで選べるようにして表示 |
f.check_box | checkboxのinputタグを表示 |
f.number_field | numberのinputタグを表示 |
f.submit | submitのinputタグを表示 |
form_forとform_tag
form_for と form_tag どちらを使うべきかは、基本的にモデルの有無で判断します。入力フォームで入力するデータのモデルがあれば form_for を使い、入力するデータが特にモデルを持っていなければ form_tag を使います。
resourceをネストさせた時の処理
routes.rb
resources :users do
resources :products do
resources :reviews
end
end
reviews_controller.rb
def new
@user = User.find(4)
@product = Product.find(2)
@review = Review.new
end
new.html.erb
<%= form_for ( [@user, @product, @review ] ) do |f| %>
(中略)
<% end %>
送られるリクエスト
# HTTPメソッド
POST
# パス
/users/4/product/2/reviews
requireメソッド
form_forに入力されたデータはparamsの中に以下のような形でコントローラに送られる。
<%= form_for(@book) do |f| %>
<%= f.text_field :name %>
<%= f.text_area :detail %>
<% end %>
params
# { book: { name: "入力された名前", detail: "入力された詳細" } }
キーはform_forの引数にあるインスタンスの モデル名(book) になり、バリューはカラム名と入力された値のハッシュです。
わざわざrequireメソッドを使っているが、ハッシュなので普通に取り出しても動く。
params.require(:review)
params[:review]
なのでparamsに対して.require().permit()とする理由は可読性だけ?
form_tagとform_forで返されるparamsの違い
form_tag ただのハッシュ
{
"utf8"=>"✓",
"authenticity_token"=>"/3QchQigdEcpc2VaOn+6IGIV14x1iJ5bDsM7GI5NA6k=",
"image"=>"http://sample/hoge.jpg",
"text"=>"hello!",
"controller"=>"tweets",
"action"=>"create"
}
form_for ハッシュのネスト
{
"utf8"=>"✓",
"authenticity_token"=>"/3QchQigdEcpc2VaOn+6IGIV14x1iJ5bDsM7GI5NA6k=",
"review"=> {
"rate"=>"5",
"review"=>"It's reasonable"
},
"action"=>"create",
"controller"=>"reviews",
"product_id"=>"4"
}
redirect_to
redirect_to controller: :コントローラー名, action: :アクション名
コントローラーとビューの変数の受け渡しと、form_forの返り値
コントローラで@変数を定義するとビューへ渡せる。
これをform_for [@aaa, @bbb]としてparamsに値を返した際、
paramsでは以下のようにハッシュのネストになる。ネストされたハッシュのキーは@bbbではなく、bbbとなる。
{
"utf8"=>"✓",
"authenticity_token"=>"/3QchQigdEcpc2VaOn+6IGIV14x1iJ5bDsM7GI5NA6k=",
"bbb"=> {
"rate"=>"5",
"review"=>"It's reasonable"
},
"action"=>"create",
"controller"=>"reviews",
"product_id"=>"4"
}
whereメソッド
Product.where('title LIKE(?)', "test%")
Review.where(name: "test")
Renderメソッド
<% @product.reviews.each do |review|%>
<%= render partial: "review", locals: { review: review } %>
<% end %>
render partial: “_なしのファイル名”, locals: { 部分テンプレート内で使う変数名: 引き渡したい変数}
ActiveRecord Relationクラス
whereメソッドやアソシエーションを利用してDBから複数のレコードをインスタンスとして取得した場合、取得した配列はActiveRecord Relationクラスに属す。
ActiveRecord Relationクラスには、whereを始めとして複数のメソッドが準備されている。
products = Product.all
products.class #=> ActiveRecord::Relation::ActiveRecord_Relation_Product
whereメソッドやアソシエーションで、DBに当てはまるレコードが存在しない場合、返り値は空の配列になる。
modelからインスタンスを取得すると配列になる。配列内の個々のモデルにはハッシュ?
クラスや変数の型の確認
is_a?(Hash)
is_a?(Array)
kind_of?(Hash)
kind_of?(Array)
kind_of?(Object)
kind_of?(Enumerable)
is_a?(Hash)が機能していない場合、respond_to?(:key)でtrueならハッシュのはず。
whereやfind等で取得したインスタンスは配列で格納される。
では個々のインスタンスの中身はハッシュなのか?配列なのか?
product = Products.find(1)
product.reviews
=> [#<Review:0x00007fac8d3e4c70
id: 1,
created_at: Fri, 19 Jul 2019 05:02:46 UTC +00:00,
updated_at: Fri, 19 Jul 2019 05:02:46 UTC +00:00>,
#<Review:0x00007fac900197f0
id: 2,
created_at: Fri, 19 Jul 2019 05:03:15 UTC +00:00,
updated_at: Fri, 19 Jul 2019 05:03:15 UTC +00:00>]
配列で値が返る。[]で括られているので明示的に配列だと思う。
product.reviews[1]
=> #<Review:0x00007fac900197f0
id: 2,
nickname: "あああ",
rate: 9,
review: "skakakいいいううう\r\nえええ\r\nおおお",
product_id: 10,
created_at: Fri, 19 Jul 2019 05:03:15 UTC +00:00,
updated_at: Fri, 19 Jul 2019 05:03:15 UTC +00:00>
配列?ハッシュ?
<>の括りはハッシュの意味だろうか?
[33] pry(main)> product.reviews[1].is_a?(Hash)
=> false
[34] pry(main)> product.reviews[1].is_a?(Array)
=> false
[35] pry(main)> product.reviews[1].respond_to?(:rate)
=> true
[36] pry(main)> product.reviews[1][1]
=> nil
[37] pry(main)> product.reviews[1][:rate]
=> 9
ハッシュかどうか尋ねてもfalseが返る。
しかしシンボルでキーを指定すると値を返すのでハッシュでいいと思う。
参考にした質問にrespond_to(:key)を使ったらとあるので試すとTrue。
正確かは保証しないが、ハッシュという認識でいいと思う。

averageメソッド
取得したモデルインスタンスの平均を出力できる。
以下の書き方をするが、カラム名をシンボルで指定する。
例)reviews.average(:rate)
モデル.average(カラム名 [, オプション])
配列に対して1つずつ中身(ハッシュ)を取り出して、シンボル指定で値を抜いている印象。
コードの実態が何をしているのかはわからないが、eachを当てているように見える。
limit / orderメソッド
limitメソッドもorderメソッドと同じように、allメソッドを省略できる。
@ranking = Product.limit(5)
layout ‘レイアウトファイル名’
コントローラ内でlayout ‘レイアウトファイル名’と書くと、そのコントローラでのアクションが呼ばれたあとに表示するビューのレイアウトファイルを指定できる。
仮にlayout ‘movie’に指定すると、MovieControllerのindexアクションが呼ばれたときに表示されるレイアウトはmovie.html.erbとなる。なにも指定しないとレイアウトファイルはapplication.html.erbとなる。
each.with_index
eachメソッドと with_indexメソッドを併用すると、要素の数だけブロックを繰り返し実行し、繰り返しごとに | で囲われている部分の i に番号が入る。デフォルトでは、iには0から入ります。
<% @ranking.each.with_index(1) do |product, i| %>
<% end %>
groupメソッド
テーブルのレコードを指定したカラムでまとめる。
.group(:key)のように使う。
Review.group(:product_id)
Review Load (10.5ms) SELECT `reviews`.* FROM `reviews` GROUP BY product_id
=> [#<Review id: 1, nickname: "まいき", rate: 1, review: "おもしろい", product_id: 21, created_at: "2014-11-05 16:03:39", updated_at: "2014-11-05 16:03:39", user_id: 4>,
#<Review id: 3, nickname: "えいちゃん", rate: 10, review: "感動した!また見たい", product_id: 22, created_at: "2014-11-05 16:03:39", updated_at: "2014-11-05 16:03:39", user_id: 4>,
#<Review id: 13, nickname: "ごとう", rate: 10, review: "思っていたより良かった", product_id: 23, created_at: "2014-11-05 16:03:39", updated_at: "2014-11-05 16:03:39", user_id: 4>
返る値はテーブル情報のハッシュ(?)が入った配列?
countメソッド
array.count(hogehoge)
count (Array)
配列の中で引数hogehogeと同じ要素の数を返す。
引数を省略すると、要素の数を返す。
order(‘count_カラム名’).count(カラム名)
countメソッドの引数にカラム名を指定できる。
こうするとorderメソッドでcount_カラム名でのソートが可能になり、そのカラムを持つレコードの数でソートする。
例として、reviewをproduct_idでまとめたレコードをレコード数でソートしてカウントする。
返り値として、カラム名とレコード数のハッシュで返す、という処理がされる。
limit()を併用したい場合にはcount()の前につける。
limitメソッドは複数のレコードの配列のような形であるActiveRecord::Relationに対するメソッド。
count(:product_id)の後に行うと返り値がハッシュになるため使えない。
自己矛盾?.countを実行する前にcount_カラム名が使えるのはおかしい気がする。
SQL上では先にcount関数が実行されてcount_カラム名が作られて一度中断、一連の処理が終わった後にcountの処理が再開されてハッシュを返している?
SQLのcountには集計する以上の機能がないのでハッシュにして返しているのはRails独自の機能かもしれない。なので先にcountが起動できて、別処理であるハッシュにする作業を後に行えるの感も?
.group.countと併用した場合
Review.group(:product_id).count
=> {21=>2, 22=>1, 23=>4}
arrayに対して使う.countメソッドを用いた場合ハッシュが返ることから、.groupで作られた新たな配列は、ハッシュを要素に持つ配列。そしてハッシュのキーはgroup()で指定したキー、valueはハッシュの形でテーブル情報を持つ配列
.group(:id)
[
{id=1: [ classinstance1{}, classinstance2{} ] },
{id=2: [ classinstance1{}, classinstance2{} ] },
]
.groupした際には内部的には配列がネストされている???
仮にそうだとしても上記に.countを当ててもハッシュの配列にしかならず、返り値がハッシュになる道理がない。
こちらを参考にさせていただいた。
どうも集計関数は特殊な存在で、使うとハッシュで値を返すようだ。そのためorderメソッドよりも前に.countを書くとエラーになる感じ。その割には.countが先に動作しないと並べ替えできないはずなので不思議。先に展開するけど、最終的にreturnするのは最後という指定なんだろうか?
ActiveRecordパターンというキーワードもよく調べる必要がありそう。
思考停止して書いちゃえば動くし見た目も分かりやすいんだけど、実態がよく読めない。
keysメソッド
ハッシュはkeysというメソッドを持っています。これはハッシュのキーだけを取り出して配列として返すメソッドです。
mapメソッド
mapメソッドは配列オブジェクトのインスタンスメソッドです。mapメソッドは配列の中身を1つずつ取り出してブロックという構文を繰り返し実行します。そして、ブロックの返り値を集めた新しい配列を作成します。
配列オブジェクト.map {|ele| ブロックの処理}
# eleには配列の要素が1つずつ代入される
# ブロックの処理は配列の要素の数だけ繰り返し実行される
ランキング機能の実例
def ranking
product_ids = Review.group(:product_id).order('count_product_id DESC').limit(5).count(:product_id).keys
@ranking = product_ids.map{|id| Product.find(id) }
end
to_sql
sql文を返す。
使ったことがないのでなんとも。
order.countやorder.limitなどで謎な挙動をした際に調べるのに使えるかも。
今の私にはSQL文を読むのは荷が重い。
Deviseのインストール
Gemfile
gem 'devise'
bundle install
rails g devise:install
ビューファイルの作成
rails g devise:views
ユーザーモデルの作成(migrationファイルも含まれる)
rails g devise user
$ rails g devise user
invoke active_record
create db/migrate/20190719114302_devise_create_users.rb
create app/models/user.rb
invoke test_unit
create test/models/user_test.rb
create test/fixtures/users.yml
insert app/models/user.rb
route devise_for :users
テーブルの作成
bundle exec rake db:migrate
サーバーの再起動
$ rails s
after_sign_out_path_forメソッド
deviseでサインアウトしたあとのリダイレクト先を指定するメソッドとしてafter_sign_out_path_forがあります。このメソッドでは返り値にサインアウト後のリダイレクト先URLを指定します。deiviseのメソッドを上書きしている関係上resourceを引数に渡さなけらばならないので、resourceを引数に渡します。
def after_sign_out_path_for(resource)
'/users/sign_in' # サインアウト後のリダイレクト先URL
end
コメント