Webエンジニア目指して#46

どうも激太りマンです。
前職のときは晩飯食わないことが多かったんですが、実家だと毎日出してくれるおかげでカロリー収益が鰻上りでございます。ヤバい。

Rails道場編も最後まで終わらせました。
bcrypt導入の流れで、passwordの初期値のケアの指示が無くてつまづく人多そうですね

挫折する人が多いと聞く中で割とすんなり終わったなあと思います。

githubは特に書き残すことはありませんでした。ついでにProgateのgitやります

gitとは

バージョン管理ツールで、保存履歴からバージョン復元や統合ができるため他人と共同開発をするのに使うらしい

gitにファイルをアップロードする(pushする)

ターミナルで開発アプリケーションのルートフォルダに移動して、以下のようにコマンドを実行する

git init
git使用準備初期化
|
git add ファイル名
ファイル選択
|
git commit -m "伝えたいこと"
ファイル記録(commit)と伝言(commit message)
|
git remote add リモートリポジトリ名 URL
共有する場所であるリモートリポジトリとやらを登録する。
URLは用意する必要あり
|
git push リモートリポジトリ名 master
対象リポジトリにファイルをアップロードする。プッシュするというらしい
ここで初めて相手と共有できるようになる。

gitからファイルをダウンロードする(pullする)

プル側もgit initgit remote addは必要。

準備が終わった上で、git pull origin masterを実行すると、リモートリポジトリからファイルがダウンロードされる

変更を加えたファイルを確認する

push前に、ローカルで自分がどのファイルに変更を加えたか確認するには
git statusを実行する。

リモートリポジトリと差異がある、つまり変更があるファイルは以下のようにコマンドラインに表示される。
git addされてない状態、
git addされている状態。
modified: index.html
modified: stylesheet.css

変更内容の詳細はgit diffで見れる。
が変更前、が変更後の内容。
だけの場合は追加された内容。

- <p>test</p>
+ <p>こんにちは</p>

commit履歴を見る

git logでcommit履歴が見れる。
様々なオプションがあり、git log -pで変更内容も見れる。

ログが多いと上下キーでスクロールできるようになり、Qキーで終了できる。

さて残るはrails tutorialですね。ではでは

Webエンジニア目指して#45

どうもエアコンのカビが辛いマンです。早う一人暮らしがしたいのう... 最近スプラも飽き気味なのでリフレッシュはぷよぷよに移行しつつあります。弥生時代習得したはずなのに反復してなさすぎて抜けちゃった〜と嘆いてたんですが、ぷよスポは先折GTRの時代らしくて助かった

さてやっていきましょうRails XIです。Railsもいよいよラスト

目標

パスワードを文字列そのまま保存しておくと盗まれたら大変なため、盗まれたとしても使い物にならないようハッシュ化する

ハッシュ化用gemのインストール

gemとはrubyの拡張パッケージ。

gemfileの記述

gemfileとは、アプリのルートディレクトリにある、railsにインストールしたいgemを記述するファイル。
f:id:misokatsu_sand:20210501011716p:plain

中身を見てみると、gem 'gem名' , 'バージョン'の形でたくさん並んでいる。
これらはrails newした時に最初にインストールされたものらしい

f:id:misokatsu_sand:20210501012055p:plain バージョンの頭に比較演算子みたいなのがついている。
これには以下のようなルールがある
・ x.0.0 : バージョン固定
・ >= x.0.0 : 演算子通り(x.0.0以上)
・ ~> x.0.0 : x.0.0以上X+x.9.9以下(一桁目固定)
・ '>= x.0.0' , '< x.3.0' : 演算子通りの複合
・ 記述なし : 最新バージョン

さて、今回は使いたいハッシュ化用gem"bcrypt"を記述する。

gem 'bcrypt', '~> 3.1.7'

既にgemfileにコメントアウトしてあった。 これを追加し保存したら、ターミナルでbundle installと入力するとgemのインストールが始まる。

...はずだった。 bundle installすると予期せぬエラーが返ってきた。
gem install 'bcrypt'で切り分けてみたら、permission deniedが返ってきた。 sudoすることで解決できたが、rootでbundler使うなと警告された。

色々調べてみると、過去にどこかしらでsudoでgem入れたことでメインユーザーディレクトリのアクセス権がsuになってたみたい

先人の知恵を借りてchownコマンドで対象ディレクトリのアクセス権を直した。 f:id:misokatsu_sand:20210504153717p:plain

これでgem install 'bcrypt'が成功した。 f:id:misokatsu_sand:20210504154527p:plain 改めてbundle installしてみたが、gem揃ってるぜ!みたいな感じだった

何も知らないままsudoしただろうからどのタイミングでやらかしたか分からないが、system領域でもないのに権限弾かれた時は対象のフォルダ権限を参照してみるのがいいかも

さて解決したところで次

bcryptの使い方

対象モデルクラスにhas_secure_passwordを定義する

has_secure_passwordはpasswordが存在するか自動チェックするため、

passwordカラムに対してのpresence:trueは不要になる。

class User < ApplicationRecord
  has_secure_password
  
  #削除 validates:password,{presence:true}
以下略

password_digestカラムの作成

bcriptはハッシュ化したパスワードをpassword_digestカラムへ保存するため、それ用にmigrationする。passwordカラムは不要になるため削除する

以下のようにremove_columnでカラムの削除ができる

class ChangeUsersColumns < ActiveRecord::Migration[6.1]
  def change
    add_column :users, :password_digest, :string
    remove_column :users, :password, :string
  end
end

password_digestの自動生成

下図のように、passwordに値が与えられるとbcryptによって自動でハッシュ化され
password_digestに代入される。 f:id:misokatsu_sand:20210507034047p:plain

この処理はpasswordカラムがなくても動作する。

そのため、今まで書いたコードでpasswordに代入していた処理をpassword_digestに置換する作業はいらない。 ログイン処理については変更があるので次項で。

ハッシュ化passwordでのログイン

has_secure_passwordを付与したモデルクラスはauthenticateというメソッドが使えるようになる。

authenticateメソッドは@user.authenticate("neko")のように記述し、 引数の文字列をハッシュ化しpassword_digestと一致するかどうかT/Fで判定する。

これを使ってログイン処理を書き替える。

以前書いたログイン処理はemailとpassがそれぞれ入力と一致した@userが存在すればログインする記述だった

def login
    @user=User.find_by(email: params[:email],
                       password: params[:password])
    if @user
以下略

今回はemailが一致する@userが存在し、且つその@userのauthenticateがTrueの場合にログインするようにする。

def login
    @user=User.find_by(email: params[:email])
    if @user && @user.authenticate(params[:password])
以下略

これでログインしてみる。viewは代わり映えしないのでサーバーログを見る f:id:misokatsu_sand:20210507042342p:plain authenticity_token辺りがそれっぽい。ログインは成功

ちなみにgemを入れたらサーバーは再起動しないとこんなエラーが出るみたい f:id:misokatsu_sand:20210507042626p:plain bcryptないわけないじゃん、bundle installでも確認したしと思って一発で再起動思いついたのえらいぞお

bcryptのセキュリティ性の高さは、平文にsalt(ランダムな文字列)を加えて文字数をかさ増ししてからハッシュ化することで総当りに強くなるところらしい。確かにnekoの4文字からでも大変な事になってた

ただ、専門的な突破口はあるみたいなので、お手軽セキュアって感じ?本格的に実装するなら他にもやらないとダメそう

というわけでrails XI終わり。後はrails tutorialとgithubかな
ではでは。

Webエンジニア目指して#44

どうも金欠マンです。 貯金して転職に備えたので金欠っていうほどそうではないんですけど、スマホ割れたり健康保険料や市民税だったりとお金がなくなっていくことに焦燥感を覚えます。せっかくの無職期間だしゆったりするか〜と思ってたけどあんまり悠長にしてられないですね。

というわけでやっていきましょうProgateのRails X!!!!!!!!!!!!!!

目標

イイネ機能の実装

イイネ用テーブルの用意

誰がどのツイートにイイネをしたか、user_idとpost_idを格納するLikeテーブルをいつもの感じで作る

投稿詳細でのイイネの表示

ログインユーザーがイイネした/してないを表示させる

post/details.html.erb

<% if Like.find_by(user_id:@current_user.id,post_id:@post.id) %>
    <p></p>
<% else %>
    <p></p>
<% end %>

よくある表現で書いた
即席なので文字だが、cssか画像素材でやるのが良さそう

イイネボタンの実装

routing

post 'likes/:post_id/save' => `likes#save`

post/details.html.erb

<% if Like.find_by(user_id:@current_user.id,post_id:@post.id) %>
    <p></p>
<% else %>
    <p><%= link_to("♡","/likes/#{@post.id}/save",{method:"post"}) %></p>
<% end %>

未イイネのテキストをlink_toでイイネ登録actionに飛ばす

likes_controller.rb

def save
    @like=Like.new(user_id:@current_user.id,
                   post_id:params[:post_id])
    @like.save
    redirect_to("/post/#{params[:post_id]}")
end

対象の投稿params[:post_id]にログインユーザー@current_user.idがイイネした、となるよう書いた

結果

f:id:misokatsu_sand:20210426025022g:plain

イイネ取り消しボタンの実装

イイネ登録のノリでdestroyするだけ!割愛! f:id:misokatsu_sand:20210426031420g:plain

link_toメソッドにhtml要素を仕込む

従来のlink_toだとlink_to("text","URL")のように文字列しか表示させることができない 以下の書き方だとhtmlタグを用いて表示させることができる

<%= link_to("URL") do %>
  <div class="sample"></div>
<% end %>

せっかくなのでこれでイイネのハートマークをアイコンで置き換える

<% if Like.find_by(user_id:@current_user.id,post_id:@post.id) %>
    <%= link_to("/likes/#{@post.id}/delete",{method:"post"}) do %>
      <span class="fa fa-heart btn unlike"></span>
    <% end %>
<% else %>
    <%= link_to("/likes/#{@post.id}/save",{method:"post"}) do %>
      <span class="fa fa-heart btn like"></span>
    <% end %>
<% end %>

結果

f:id:misokatsu_sand:20210427004245g:plain

countメソッドを使ってイイネ数を表示させる

countメソッドはモデルクラスに対してSQLのcountと同じ感じで使う

f:id:misokatsu_sand:20210427014253p:plain

これを使ってイイネ数を表示させる

投稿詳細actionに対象投稿idのイイネ数@likes_countを定義

@likes_count=Like.where(post_id: @post.id).count

投稿詳細viewで@likes_countを表示

<%= @likes_count %>

結果
何故かアンダーバーが割り込む。アイコンのボックスでもカウントのでも違くて急に現れる謎のアンダーバー。誰ですかあなた?????help????

f:id:misokatsu_sand:20210427023453g:plain

aタグらしい。ということはlink_toが悪さをしている...? f:id:misokatsu_sand:20210427024133p:plain

よく見たらアンダーバーじゃなくてtext-decorationの下線だった。
普通にaタグの下線消して解決

f:id:misokatsu_sand:20210427025330p:plain

ユーザーがいいねした投稿一覧をユーザー詳細に表示する

routing

get 'users/:id/likes' => 'users#likes'

users.controller

def likes
    @user=User.find(params[:id])
    @likes=Like.where(user_id: @user.id)
end

users/likes.html.erb

<p>このユーザーのイイネ</p>
<% @likes.each do |like| %>
  <% post=Post.find(like.post_id) %>
  <img src="<%= "/user_images/#{post.user.image_name}" %> ">
  <%= link_to(post.user.name,"/post/#{post.user.id}") %>
  <%= link_to(post.message,"/post/#{post.id}") %>
<% end %>

@usersに詳細表示中ユーザーidのレコードを取得して、
@likesでそのユーザーがイイネした投稿を全て取得する
viewで@likes.eachの中に各@likesから取り出されたレコードに対してpost_idの紐付けからpostsテーブルの投稿データを表示させる

結果

f:id:misokatsu_sand:20210430070943p:plain

これでRuby Xは終わり!
今まで知らなかったんですが、RailsにはCRUDという概念があるらしく
全くそれに沿わずaction名をcreate=>save, show=>details, destroy=>deleteと定義してました。他の技術者と意思疎通する際に困りそうなので次回構築するときはそれぞれ合わせていきたいですね

ではでは。

Webエンジニア目指して#43

どうもアニメマンです

ウマ娘めちゃくちゃ面白いですね。1期スルーしてましたが最近の盛り上がりがすごくて見てみたらハマりました。スマホゲーはやらないけど...

さあやっていきましょうProgateのRails IXです。

の前に、Rails道場編IIIの反省を書こうと思いましたが特にありませんでした。括弧の自動有力に慣れてしまってて、Progateでは{}が自動で閉じられなくてsintax errを出すみたいなしょうもないのくらいですね

あと、ログインユーザー本人のアカウント情報以外編集出来ないようにするメソッド作りましたが、postアクションだけ何故かリダイレクトせず、no routeエラーを返すようで困りましたが次触る時に調べることとします →ほっとくのはまずいと思って探ってみたらわかりました!!!!

エラー画像 f:id:misokatsu_sand:20210417045805p:plain よく見たら[GET]ってあるんですよね。updateメソッドはpostでroutingしてあるのでgetはおかしい
考えてみたらURLの直接入力はget扱いになるので当然のエラーでした。postは特定の処理しないとできないからね

get用のrouting作ってダミーactionで不正な処理として適当なページにリダイレクトさせようかな、と考えましたが、意図しないURLは全部こうなるんだから気にしないことにしました。

はい、というわけで今度こそRails IXやっていきます

目標

投稿とユーザーの紐付け、対応データの表示

投稿テーブルにユーザーidカラムを追加する

いつもの通りmigrationファイル作ってadd_columnしてdb:migrateからのvalidatesで{presence:true}書いてfinish

ユーザーidは数字のため型はintegerにする

ここで1点反省、最初の方に"post"でrails g controllerを実行してpost-modelが生成されているが、テーブル名はpostsで生成されていてめちゃくちゃ紛らわしい。
なので次回からはcontrollerやmodelを作るときは複数形で統一したい

投稿actionにログイン中のユーザーidを付与

def save
  @post=Post.new(
    message: params[:message],
    user_id: @current_user.id
  )

投稿詳細にユーザー情報を表示する

post.controller

def details
    @post=Post.find_by(id: params[:id])
    @user=User.find_by(id: @post.user_id)
  end
以下略

post/details.html.erb

<img src="<%= "/user_images/#{@user.image_name}" %>" >
<%= link_to(@user.name,"/users/#{@user.id}") %>

結果

f:id:misokatsu_sand:20210418161729p:plain

postモデルにインスタンスメソッドを定義する

class Post < ApplicationRecord
  def user
    return User.find(self.user_id)
  end
以下略

selfメソッドによって、インスタンスの値を利用できるため、
レコードを読み込ませた@postインスタンスに対して@post.userと扱うことで簡単にUserテーブルを参照できる

投稿一覧にユーザー情報を表示する

post/index.html.erb

<% @posts.each do |post| %>
  <img src="<%= "/user_images/#{post.user.image_name}" %> ">
  <%= link_to(post.user.name,"/post/#{post.user.id}") %>
  <%= link_to(post.message,"/post/#{post.id}") %>
<% end %>

先程の@post.userによって、@userを定義しなくても以下のように@postだけでUserテーブルの情報を持ってこられる

結果 f:id:misokatsu_sand:20210421060208p:plain

whereメソッドを使ってユーザー詳細ページに投稿を表示する

whereメソッドとは、SQLでいうwhereのようにテーブルに対し特定のカラムでフィルターをかけ、そのレコード群を配列として返すもの。
rails consoleでの例 f:id:misokatsu_sand:20210421062607p:plain

これを使って、対象のユーザーidの投稿を取得するインスタンスメソッドをUserモデルに定義する

class User < ApplicationRecord
  def posts
    return Post.where(user_id: self.id)
  end
以下略

そしてユーザー詳細viewで@user.postsの配列に対してeachメソッドを使って投稿データを並べる

<% @user.posts.each do |post| %>
  <img src="<%= "/user_images/#{post.user.image_name}" %> ">
  <%= link_to(post.user.name,"/post/#{post.user.id}") %>
  <%= link_to(post.message,"/post/#{post.id}") %>
<% end %>

結果 f:id:misokatsu_sand:20210421182928p:plain

投稿詳細画面で投稿者以外に編集制限をかける

基本的にはユーザー編集画面と同じ処理
せっかくなので@current_user.id@post.user.idで照合させる

post/details.html.erb

<% if @current_user.id == @post.user.id %>
  <%= link_to("編集","/post/#{@post.id}/edit") %>
  <%= link_to("削除","/post/#{@post.id}/delete",{method:"post"}) %>
<% end %>

post.controller

before_action :check_incorrect_user,{only:[:edit,:update,:delete]}

  def check_incorrect_user
    @post=Post.find(params[:id])
    if @current_user.id != @post.user.id
      flash[:notice]="権限がありません"
      redirect_to("/post/index")
    end
  end

結果

f:id:misokatsu_sand:20210424052545g:plain

これでRails IXはおわり!ではでは

Webエンジニア目指して#42

どうもギター売りマンです strandbergというメーカーの7弦ギターを何故か2本持っているので片方出品しました。何故でしょうねえ・・・ 手放すのが惜しいので元値からあまり下げてないけど、売れて欲しい、売れて欲しくない、売れて欲しい

そんな感じでやっていきましょう、ProgateのRails VIIIです

目標:ユーザーアカウント機能実装

ログインページの作成

view

<h1>ログイン</h1>
<% if @error_message %>
  <%= @error_message %>
<% end %>

<%= form_tag("/login") do %>
  <p>e-mail</p>
  <input name="email" value="<%= @email %>">
  <p>パスワード</p>
  <input type="password" name="password" value="<%= @password %>">
  <input type="submit" value="ログイン">
<% end %>

冒頭の記述でログインに失敗したとき用の@error_messageを表示させるようにする

後は今まで通りフォームを記述

inputのtype属性をpasswordにすることで以下の画像のように入力時に隠れるようになる

f:id:misokatsu_sand:20210403183649p:plain

パスワードの欄を増やしたので前回と同じようにデータベースにも専用のカラムを増やしておく。また、usersモデルにvalidates:password,{presence:true}のvalidationを設ける

routing

  get 'login' => 'users#login_form'
  post 'login' => 'users#login'

getとpostは同じURLにしても大丈夫らしい(自作するなら分けるかなあ)

action

def login
    @user=User.find_by(email: params[:email],
                       password: params[:password])
    if @user
      flash[:notice]="ログインしました"
      redirect_to("/post/index")
    else
      @error_message="メールアドレスかパスワードが間違っています"
      @email=params[:email]
      @password=params[:password]
      render("users/login_form")
    end
end

find_byでフォームに入力されたemailとpassが一致したレコードを取得し、取得の結果でif文を書く

このようにif 変数という書き方をすると変数がnilかどうかで分岐してくれるみたい(nilの場合にelseになる)

elseにはviewで言及したログイン失敗メッセージ用の変数とフォームの初期値に代入

結果

f:id:misokatsu_sand:20210408010941g:plain

ユーザー認証は出来たのでログインページは完成

ログイン処理

先程のユーザー認証はユーザーを特定したが飛んだページはただの投稿一覧なので、「このユーザーでログインしてますよ」という処理を書いていく

session変数

session[:キー名]の形で使う
この変数はブラウザが記憶してくれるらしく、ユーザーidなどを代入しておけばログイン中のユーザー名表示や権限などの実装ができる

action

先程のユーザー認証actionに、認証が成功したときにそのユーザーの情報をsession変数に代入させる

def login
    @user=User.find_by(email: params[:email],
                       password: params[:password])
    if @user
      session[:user_id]=@user.id
      session[:user_name]=@user.name
      flash[:notice]="ログインしました"
      redirect_to("/post/index")
    else
      @error_message="メールアドレスかパスワードが間違っています"
      @email=params[:email]
      @password=params[:password]
      render("users/login_form")
    end
  end

ユーザーidとユーザー名を取得させた。
ユーザー名を後述のviewで使う
ユーザーidは、まあそのうちなんか使い道あるでしょ

application.html

ヘッダーにその時ログインしているユーザーの名前を表示させる

<p>ログイン中のユーザー:<%= session[:user_name] %></p>

結果

右上のログイン中に注目 f:id:misokatsu_sand:20210408163418g:plain

割愛するが、新規登録の際にもパスワードを取得し、登録完了したらそのままログインできるようにする

ログアウト処理

session変数にnilを代入すればブラウザに記憶されたユーザーがリセットされる

view

ログアウトのlink_toに`{method:"post"}を書いている。フォームデータのやりとりをする場合以外に、session変数に変更を加える場合もpostでroutingする必要がある

また、先程のif 変数を利用してsession変数の中身があるかないか、つまりログイン状態かログアウト状態かどうかでヘッダーに表示させる項目を変えている

<% if session[:user_id] %>
  <div class="header-info">
    <p>ログイン中のユーザー:<%= session[:user_name] %></p>
  </div>
  <ul class="header-menu">
    <li><%= link_to("投稿一覧", "/post/index") %></li>
    <li><%= link_to("新規投稿", "/post/new") %></li>
    <li><%= link_to("ユーザー一覧", "/users/index") %></li>
    <li><%= link_to("ログアウト", "/logout",{method:"post"}) %></li>
  </ul>
<% else %>
  <ul class="header-menu">
    <li><%= link_to("新規登録", "/users/new") %></li>
    <li><%= link_to("ログイン", "/login") %></li>
  </ul>
<% end %>

routing

post 'logout' => 'users#logout'

action

def logout
    session[:user_id]=nil
    session[:user_name]=nil
    flash[:notice]="ログアウトしました"
    redirect_to("/login")
end

結果

f:id:misokatsu_sand:20210409024541g:plain

変数にログイン中ユーザーのレコードを代入する

上記までsession変数をid用name用で2つ用意していたが、session変数でユーザーid1つ取得しておけば下記のように同一レコードの他のデータを参照できる

<% current_user=User.find_by(id:session[:user_id]) %>
      <% if session[:user_id] %>
        <div class="header-info">
          <p>ログイン中のユーザー:
            <%= link_to(current_user.name,"/users/#{current_user.id}") %></p>
        </div>
-----以下略-----

ただ、この書き方だとユーザーデータを参照する全てのviewでレコード用の変数を定義する必要ができてしまう

そこで、下記のようにcontrollerに対してbefore_action :共通処理のactionと書くことでそのcontrollerの全てのactionの前に、指定した処理をさせるシステムを利用する

class ApplicationController < ActionController::Base
  before_action :set_current_user

  def set_current_user
    @current_user = User.find_by(id: session[:user_id])
  end
end

こんな感じでクラスの直下に書く。 ApplicationControllerに書いて全てのviewに対してログイン中のユーザーデータを利用、ということもできる

@current_userで定義したところで、先程viewに書いたcurrent_userを書き替える

<% if @current_user %>
  <div class="header-info">
    <p>ログイン中のユーザー:
    <%= link_to(@current_user.name,"/users/#{@current_user.id}") %></p>
  </div>
-----以下略-----

ついでにifの条件も書き替えた。ログイン/ログアウトactionで定義していたsession[:user_name]は使わないため削除しておく

アクセス制限

ユーザー認証メソッドを作る

application.controllerに以下のメソッドを追加する

def authenticate_user
    if @current_user == nil
      flash[:notice]="ログインが必要です"
      redirect_to("/login")
    end
end

先程before_actionで仕込んだメソッドによって、先に@current_userが定義されるため使い回しができる

before_actionにonlyを指定する

application.controllerにメソッドを作っただけでは作動しないため、users.controllerに以下のように書く

class UsersController < ApplicationController
  before_action :authenticate_user,{only: [:index,:show,:edit,:update]}
----以下略----

全てのcontrollerはapplication.controllerを継承しているため、このようにusersでも先程の認証用のメソッドを仕込める

また、before_actionの第2引数に{only: [:メソッド1, :メソッド2]}のように書くとそれらのメソッドに対してのみbefore_action処理が行われるようになる

これによって、ログインが必要な機能、不要な機能を分けることができる。
例えばログインや新規登録にログインが必要だったら困るため、それらを除外してbefore_action処理を行わせるように書く。 逆に、ログイン済でログインや新規登録の画面は表示させたくないのでログイン済みなら弾くメソッドを用意してbefore_actionで各actionに適用させておく

post.controllerは全てログインを必要とさせたいためonlyを使わずbefore_actionで認証用メソッドを仕込んでおく

結果

f:id:misokatsu_sand:20210412031400g:plain

他のユーザーにユーザー情報を編集させないようにする

ログイン中ユーザーとユーザー情報ページのidが一致している場合だけ編集と削除を表示させる

<% if @current_user.id == @user.id %>
  <%= link_to("編集","/users/#{@user.id}/edit") %>
  <%= link_to("削除","/users/#{@user.id}/delete",{method:"post"}) %>
<% end %>

表示は消したがURLに入力すれば編集ページに入れてしまうため、そこもカバーしていく

ログイン中のユーザーidとURLに入力されたidが違うなら弾くメソッドを作成し、 編集ページとactionに対して適用する

  before_action :check_incorrect_user, {only: [:edit,:update]}

  def check_incorrect_user
    if @current_user.id != params[:id].to_i
      flash[:notice]="権限がありません"
      redirect_to("/post/index")
    end
  end

params[:id]は文字列として取得してしまうため、いつかしらのパートで使ったto_iメソッドで数値へ変換して比較演算にかける

結果

f:id:misokatsu_sand:20210412031517g:plain

ということでRails VIIIはおわり! 書きすぎて長引いちゃった ではでは。

Webエンジニア目指して#41

どうも特に無いマンです。

暇ではないんですが、仕事ないとなんか足りないですね〜
腹が減ったときの飯が美味いのと同じで、ある程度ストレスないと娯楽も楽しみが減りますね。フリマに断捨離以外はやり残したことなくなってきたんで学習時間の増やしどころかも

さてやっていきましょうProgateのRails VIIです

目標は画像の投稿機能実装

テーブルへカラムを追加する

migrationファイルのみ作成しデータベースへ反映させる

$rails g migration ファイル名

試しに作ってみたものの中身がこれ

class AddImageNameToUsers < ActiveRecord::Migration[6.1]
  def change
  end
end

今回は空だが、実は今まで使っていた$rails g modelを実行した場合はchangeメソッドの中身は自動生成されていた

カラムを追加したい場合は以下のように書く

class AddImageNameToUsers < ActiveRecord::Migration[6.1]
  def change
    add_column :テーブル名, :カラム名, :データ型
  end
end

次に$rails db:migrateを実行するとchangeメソッドが実行され、対象のテーブルにカラムが追加される

試しに実行してみた f:id:misokatsu_sand:20210327013504p:plain

比較画像:実行前 f:id:misokatsu_sand:20210327014039p:plain 比較画像:実行後 f:id:misokatsu_sand:20210327014057p:plain image_nameカラムが増えたので成功

画像とリンクさせる

テーブルに追加したカラム名の文字列と画像ファイル名をレコードごとに一致させ、後はhtmlで参照するだけ

データベース

既存のレコードはimage_nameカラムが空だったので追加しておく f:id:misokatsu_sand:20210331003003p:plain

画像ファイルの保存場所

画像ファイル名とimage_nameカラムの値を一致させる f:id:misokatsu_sand:20210331010104p:plain

html

imgタグの参照先に先程一致させた値を使ってパスを埋める railsを使っているときはimgタグは/publicフォルダから参照されるため、それに合わせて記述する

<img src="<%= "/user_images/#{@user.image_name}" %>">

結果

f:id:misokatsu_sand:20210331014546p:plain

画像の投稿フォーム

html

<%= form_tag("/users/#{@user.id}/update",{multipart:true}) do %>
  <p>ユーザー名</p>
  <input name="name" value="<%= @user.name %>">
  <p>e-mail</p>
  <input name="email" value="<%= @user.email %>">
  <p>プロフィール画像</p>
  <input type="file" name="image">
  <p>-</p>
  <input type="submit" value="変更">
<% end %>

inputタグのtype属性をfileにするとファイルを選んで送信できる
また、name属性も指定しているため画像のデータが[:image]として送信される

ファイルの送信フォームがあるときは、form_tagの第2引数に{multipart:true}を入れる必要がある

見た目はこんな感じ

f:id:misokatsu_sand:20210401033819p:plain

画像を受け取り保存する

if params[:image]
  @user.image_name="#{@user.id}.jpg"
  image=params[:image]
  File.binwrite("puclic/user_images/#{@user.image_name}","image.read")
end

まずif params[:image]でファイルが選択されている場合のみの処理とし、
@user.image_name="#{@user.id}.jpg"でデータベースへファイル名を登録
image=params[:image]でファイルを受け取り、
File.binwrite("puclic/user_images/#{@user.image_name}",image.read)
指定ディレクトリへファイルを作成する。
画像データを受け取りファイル作成する場合は受け取ったハッシュに対してreadメソッドというものを使う

いやそもそもFile.binwriteってなんやねん、となるので説明

File.write("ファイル名のパス","ファイルの中身")

このようにFileクラスというものがあり、writeメソッドを使うことで指定のディレクトリへファイルを作成できる。
(パスはアプリケーションのルートディレクトリから参照するので注意)

File.write("public/test.txt","aiueo") を実行した場合は、/public/test.txtというファイルが作成され、その中身がaiueo、ということになる

File.binwriteはその中身が2進数のバージョンであり、readメソッドを使って受け取った画像ファイルを2進数のデータに変換してファイルを作成している。

失敗談だがimage.readは変数.メソッドなので"image.read"と書かないように注意(ファイル生成がうまく出来ていなかった)

結果

f:id:misokatsu_sand:20210401051226g:plain

これ、簡易的にtwitter作れるんじゃね・・・?

ってなわけでRails VIIはおわりです ではでは

Webエンジニア目指して#40

どうもダンレボ2クレで消費カロリー200kcal、すなわちリングフィット1時間分であることを知ったマンです。

体力がないのでこれだけで足の疲れが翌日に持ち越されてしまいます...三重にいた頃は手軽に行ける銭湯があってよかったなあ

というわけで引き続きProgate Rails VIです。 Rails道場編IIをやりましたが反復なので割愛しました。

重複バリデーション

class User < ApplicationRecord
  validates:email,{uniqueness:true}
end

{uniqueness:true}はデータベースに対して特定のカラムの重複登録を防いでくれる

できたもの

f:id:misokatsu_sand:20210322050125g:plain

VIはpostビューでtextareaだったものがinputになった以外はほぼ同じことをやってるのであんまり書くことありませんでした

ではでは