- 投稿日:2020-03-22T21:42:55+09:00
Ruby on Railsで新規アプリケーションを立ち上げる自称最速手順(github連携)
目的
新規のアプリケーションを作成する際、
毎回「あれ?次どうするんだっけ?」ってなって調べている時間がムダなので、
最低限のものを最速で作る手順を残しておきます。
細かいことにこだわらず、とにかくサッと作成したいときに参考にしてください。
githubにも連携します。前提
マークアップはhaml,scssを使用
リセットcssはyui3を使用
ローカルでビューファイルを開くところまでを記載1.Railsのバージョン確認
ターミナル% cd ~ % ruby -v
バージョンの変更が必要な場合は以下のコマンドを実行
(--version=以降は適宜指定してください。)ターミナル% gem install rails --version="5.0.7.2" % rbenv rehash
2.アプリケーションの作成
ターミナル% cd % cd projects % rails _5.0.7.2_ new <アプリケーション名> -d mysql % cd <アプリケーション名> % rails db:create % git init3.GitHubとの連携
GitHub DesktopのCurrent Repositoryタブ
→Add
→Add Existing Repository
→Chooseから ~ projects/<アプリケーション名>を選択
→Add Repository
→コミット名をinitial commitとし、Commit to master
→Publish repository
→Publish repository
→GitHubのリポジトリに反映していればOK4.ルーティング/コントローラー/ビュー の設定
routes.rbRails.application.routes.draw do root to: 'posts#index' endターミナル% rails g controller posts
posts_controller.rbclass PostsController < ApplicationController def index end endGemfile#最下部に追加 gem "haml-rails"ターミナル% bundle install
app/assets/stylesheetsフォルダ内で以下を実行
application.cssを削除する
application.scssを新規作成する
posts.scssのファイル名を _posts.scssに変更する
_reset.scssを新規作成する
ダウンロード/yui3-3.18.1/src/cssreset/css/cssreset.cssのコードを_reset.scssに貼り付けるapplication.scss@import "reset"; @import "posts";app/views/postsフォルダ内で以下を実行
index.html.hamlを新規作成
index.html.hamlhello確認
ターミナル% rails s
ローカルでhelloと表示されれば完了
以上です。
最後まで読んでいただきありがとうございました。
- 投稿日:2020-03-22T21:06:40+09:00
【Rails】パスワードの2回入力は不要。1回にしてマスキング解除機能を実装【jQuery】
環境
Ruby on Rails 5.2.4(gem 'devise'は不使用)
⇒ gem 'font-awesome-sass'を使用
jquery-rails 4.3.5
haml 5.1.2前提
Webアプリケーションのユーザ新規登録画面で、確認の為にパスワードを2回入力させる事ってよくあると思うんですよ。
Railsの機能的にもpasswordとpassword_confirmationが合致しないとユーザ登録できない仕様になっていますし。これに関してはpassword用の入力フォームを1つ用意し、
userをsaveさせる前にpasswordの値をpassword_confirmationにも代入する事で、確認用のpassword_confirmationフォームを省くことが可能です。users_controller.rb# 色々省略 def create @user = User.create(user_params) @user.password_confirmation = user_params[:password] #ここでpasswordの値を代入する if @user.save session[:user_id] = @user.id redirect_to mypage_path flash[:notice] = "新規登録が完了しました" else flash[:alert] = @user.errors.full_messages redirect_to root_path end end private def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) endただ、このままだとユーザが新規登録の時に想定と異なる値を入力してしまった場合、間違いが分からないので再ログインができなくなりますよね?
ましてやその後のパスワードの変更手続きって面倒じゃないですか?という疑問を感じたので調べてみた所、ちょっと古いですが下記記事でこの件によるユーザビリティの低下が示されてました。
https://uxmilk.jp/18568じゃあどうするのって話ですが、上の記事内にもこの記事のタイトルにもありますが、マスキング(*マークでパスワードを非表示させる機能)を解除できる機能を作ればいいんですね。
解決策
それっぽく見えるものなら何でもいいですが、今回はfontawesomeの目を使います。
こんなフォームがあったとします。新規登録ビュー-# 色々省略 .signup .signup-top 新規登録 .signup__contents = form_with(model: @user, url: users_path, local: true) do |f| -# ユーザアカウント .form-group = f.label :アカウント名, class: "form-title" %br = f.text_field :name, class: 'form-control' %br -# メールアドレス .form-group = f.label :メールアドレス, class: "form-title" %br = f.email_field :email, class: 'form-control' %br -# パスワード .form-group = f.label :パスワード, class: "form-title" %br = f.password_field :password, class: 'form-control' #ここでマスキングが機能している .form-password = icon("fas","eye-slash") #目のアイコン。これのクリックで制御する %br .form-submit = f.submit "Signup", class: "btn-submit"このpassword_field部分がChromeの検証では下記画像のように表記されています。
この赤枠で囲われているtype="password"によってマスキングが動いているんですね。という事で、jQueryで目のアイコンをクリックした時にtype="text"に変更する制御を与えましょう。
また分かりやすくする為、ついでに目のアイコンも一緒に切り替えます。signup.js// パスワード入力フォームのスラッシュ目アイコンをクリックするとpassのマスキングを解除 function eyeslashClick(){ $(".fa-eye-slash").on("click", function(){ $(".fa-eye").off("click") // offを設定しないとクリックした回数分だけ重複作動してしまう為 let input = $(this).parents(".form-group").find("input"); input.attr("type", "text"); // typeをpasswordに変更しマスキングを解除する $(this).removeClass(); // 目を消す $(this).addClass("fas fa-eye"); // アイコンをスラッシュのない目に置き換える eyeClick(); // 追加したfas fa-eyeにクリックイベントを付与 }); } // パスワード入力フォームの目アイコンをクリックするとpassをマスキング function eyeClick(){ $(".fa-eye").on("click", function(){ $(".fa-eye-slash").off("click") // offを設定しないとクリックした回数分だけ重複作動してしまう為 let input = $(this).parents(".form-group").find("input"); input.attr("type", "password"); // typeをpasswordに変更しマスキングを有効にする $(this).removeClass(); // 目を消す $(this).addClass("fas fa-eye-slash"); // アイコンをスラッシュのある目に置き換える eyeslashClick(); // 追加したfas fa-eye-slashにクリックイベントを付与 }); } // 目アイコンのクリックイベント呼び出しをデフォルト化 eyeslashClick(); eyeClick();addClassで追加するだけでは置き換わった目のアイコンのクリックイベントが有効にならないので、忘れず付与しましょう。
総評
マスキングの解除を実装してもユーザが確認せず登録したら意味ないだろって?
そんなの知らぬ( ´_ゝ`)
- 投稿日:2020-03-22T20:11:02+09:00
7つのアクションのみのインクリメンタルサーチ
インクリメンタルサーチと7つのアクションのみのインクリメンタルサーチとの相違点
主に編集するファイル場所の違いと、それによるリクエストパスの違いになります。
1.検索に使用するコントローラーとビューの「名前」「場所」が異なる
2.構造が異なるため、ルーティングも異なる
3.ルーティングが異なるため、検索のリクエストパスが異なる
などの違いがあります。コントローラーとビューの「名前」「場所」が異なる
7つのアクションのみで検索機能を実装した場合、searches_controller.rbの作成にnamespaceを利用します。
そのため、コントローラーが格納されているディレクトリの構造が変わります。
app/controllers/tweets/searches_controller.rb
加えて、作成したsearches_controller.rbのindexアクションに対応するビューは、
app/views/tweets/searches/にindex.html.erbとして格納されます。
これらの「namespaceを利用したことによる構造と名前の違い」があります。検索のリクエストパスが異なる
ルーティングが変更されるため、検索機能を呼び出すリクエストを送信する処理を記述するJSファイルで、指定するリクエストパスを変更します。
jQueryの導入
jQueryにgem 'jquery-rails'が導入されていなければ導入します。
【例】gem 'jquery-rails'ターミナル
bundle install【例】
app/assets/javascripts/application.js// This is a manifest file that'll be compiled into application.js, which will include all the files // listed below. // // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's // vendor/assets/javascripts directory can be referenced here using a relative path. // // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the // compiled file. JavaScript code in this file should be added after the last require_* statement. // // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details // about supported directives. // //= require jquery #追加 //= require rails-ujs //= require activestorage //= require turbolinks //= require_treetextカラムにインデックスを設定します。
ターミナル$ rails g migration AddIndexToTweetsdb/migrate/20XXXXXXXXXXXX_add_index_to_tweets.rbclass AddIndexToTweets < ActiveRecord::Migration def change add_index :tweets, :text, length: 32 end endターミナル
$ rails db:migrateルーティングなどAPI側の準備
アクションの中でHTMLとJSONなどのフォーマット毎に条件分岐するため、respond_toを使用します。
【例】app/controllers/tweets/searches_controller.rbclass Tweets::SearchesController < ApplicationController def index @tweets = Tweet.search(params[:keyword]) respond_to do |format| format.html format.json end end endapp/views/tweets/searches/のディレクトリにindex.json.jbuilderファイルを作成します。
app/views/tweets/searches/index.json.jbuilderjson.array! @tweets do |tweet| json.id tweet.id json.text tweet.text json.image tweet.image json.user_id tweet.user_id json.nickname tweet.user.nickname json.user_sign_in current_user endテキストフィールドの作成
【例】
index.html.erb<%= form_with(url: search_tweets_path, local: true, method: :get, class: "xxxx") do |form| %> <%= form.text_field :keyword, placeholder: "投稿を検索する", class: "yyyy" %> <%= form.submit "検索", class: "zzzz" %> <% end %> <div class="aaaa"> <% @tweets.each do |tweet| %> <%= render partial: "tweet", locals: { tweet: tweet } %> <% end %> <%= paginate(@tweets) %> </div>テキストフィールドが入力されるたびにイベントが発火させる
app/assets/javascripts/のディレクトリにsearch.jsファイルを作成します。
app/assets/javascripts/search.js$(function() { $(".yyyy").on("keyup", function() { var input = $(".yyyy").val(); }); });イベント時に非同期通信できるように
【例】
app/assets/javascripts/search.js$(function() { $(".yyyy").on("keyup", function() { var input = $(".yyyy").val(); $.ajax({ #追加〜 type: 'GET', url: '/tweets/searches', data: { keyword: input }, dataType: 'json' }) #〜追加 }); });非同期通信の結果を得て、HTMLを作成
非同期通信の結果を元にビューを生成します。
【例】app/assets/javascripts/search.js$(function() { $(".yyyy").on("keyup", function() { var input = $(".yyyy").val(); $.ajax({ type: 'GET', url: '/tweets/searches', data: { keyword: input }, dataType: 'json' }) .done(function(tweets) { #追加〜 $(".contents.row").empty(); if (tweets.length !== 0) { tweets.forEach(function(tweet){ appendTweet(tweet); }); } else { appendErrMsgToHTML("一致するツイートがありません"); } }) #〜追加 }); });tweetsに投稿の情報が入っている場合のappendTweet関数と、
tweetsに投稿の情報が入っていない場合のappendErrMsgToHTML関数を定義します。
記述内容は同じです。
【例】_tweethtml.erb<div class="content_post" style="background-image: url(<%= tweet.image %>);"> <div class="more"> <span><%= image_tag 'arrow_top.png' %></span> <ul class="more_list"> <li> <%= link_to "詳細", tweet_path(tweet.id), method: :get %> </li> <% if user_signed_in? && current_user.id == tweet.user_id %> <li> <%= link_to '編集', "/tweets/#{tweet.id}/edit", method: :get %> </li> <li> <%= link_to '削除', "/tweets/#{tweet.id}", method: :delete %> </li> <% end %> </ul> </div> <%= simple_format(tweet.text) %> <span class="name"> <a href="/users/<%= tweet.user_id %>"> <span>投稿者</span><%= tweet.user.nickname %> </a> </span> </div>【例】
app/assets/javascripts/search.js$(function() { var search_list = $(".aaaa"); #追加〜 function appendTweet(tweet) { if(tweet.user_sign_in && tweet.user_sign_in.id == tweet.user_id){ var current_user = `<li> <a href="/tweets/${tweet.id}/edit" data-method="get" >編集</a> </li> <li> <a href="/tweets/${tweet.id}" data-method="delete" >削除</a> </li>` } else { var current_user = "" } var html = `<div class="content_post" style="background-image: url(${tweet.image});"> <div class="more"> <span><img src="/assets/arrow_top.png"></span> <ul class="more_list"> <li> <a href="/tweets/${tweet.id}" data-method="get" >詳細</a> </li> ${current_user} </ul> </div> <p>${tweet.text}</p><br> <span class="name"> <a href="/users/${tweet.user_id}"> <span>投稿者</span>${tweet.nickname} </a> </span> </div>` search_list.append(html); } function appendErrMsgToHTML(msg) { var html = `<div class='name'>${ msg }</div>` search_list.append(html); } #〜追加 $(".yyyy").on("keyup", function() { var input = $(".yyyy").val(); $.ajax({ type: 'GET', url: '/tweets/searches', data: { keyword: input }, dataType: 'json' }) .done(function(tweets) { search_list.empty(); if (tweets.length !== 0) { tweets.forEach(function(tweet){ appendTweet(tweet); }); } else { appendErrMsgToHTML("一致するツイートがありません"); } }) }); });エラー時の処理
app/assets/javascripts/search.js$(function() { var search_list = $(".aaaa"); function appendTweet(tweet) { if(tweet.user_sign_in && tweet.user_sign_in.id == tweet.user_id){ var current_user = `<li> <a href="/tweets/${tweet.id}/edit" data-method="get" >編集</a> </li> <li> <a href="/tweets/${tweet.id}" data-method="delete" >削除</a> </li>` } else { var current_user = "" } var html = `<div class="content_post" style="background-image: url(${tweet.image});"> <div class="more"> <span><img src="/assets/arrow_top.png"></span> <ul class="more_list"> <li> <a href="/tweets/${tweet.id}" data-method="get" >詳細</a> </li> ${current_user} </ul> </div> <p>${tweet.text}</p><br> <span class="name"> <a href="/users/${tweet.user_id}"> <span>投稿者</span>${tweet.nickname} </a> </span> </div>` search_list.append(html); } function appendErrMsgToHTML(msg) { var html = `<div class='name'>${ msg }</div>` search_list.append(html); } $(".yyyy").on("keyup", function() { var input = $(".yyyy").val(); $.ajax({ type: 'GET', url: '/tweets/search', data: { keyword: input }, dataType: 'json' }) .done(function(tweets) { search_list.empty(); if (tweets.length !== 0) { tweets.forEach(function(tweet){ appendTweet(tweet); }); } else { appendErrMsgToHTML("一致するツイートがありません"); } }) .fail(function() { #追加〜 alert('error'); }); #〜追加 }); });
- 投稿日:2020-03-22T20:01:51+09:00
direnv(linux)
はじめまして
みなさんはじめまして。本日やっと問題解決し、雄叫びをあげましたkyonです。
備忘録として初投稿いたします。
どうぞよろしくお願いいたします。環境
CentOS Linux release 8.1.1911 (Core)
mysql Ver 8.0.17 for Linux on x86_64 (Source distribution)
Visual Studio Code バージョン: 1.43.1ー
タイトルにもありますとおりlinuxでdirenvを使いたく、以下URLを参考とさせていただき、
参考URL Linuxでdirenvを使う
.envrcファイルを作成し、Railsプロジェクトの /binディレクトリにパスを通すところまですんなりとできましたが、その後ずっとコイツと戦っておりました。Access denied for user 'ENV['MYSQL_DATABASE']'@'localhost' (using password: YES)多分一週間くらい戦ったと思います。
調べれば直接パスワードをdatabase.ymlへ記述すればいいことは知っていましたが、先々のことを考えた結果direnvを使うことにしました。
が、しかしどうしても分からず。。.envrcexport 'DATABASE_USERNAME'="root" export 'DATABASE_PASSWORD'="password" ←mysqlのパスワード。。。。。?
database.ymldefault: &default adapter: mysql2 encoding: utf8mb4 username: ENV['DATABASE_USERNAME'] password: ENV['DATABASE_PASSW0RD']。。。。。。。。??
ただ調べていくうちに.envrcファイルは合ってそうだな、と思っていてdatabase.ymlのusernameとpasswordの書き方がずっと不明でした。
今日ふとdatabase.yml の管理方法いろいろ
こちらのURLに辿り着き、問題解決したと言うことです。問題解決
database.yml には <%= ... %> で Ruby コードを埋め込むことができます。そしてdatabase.ymlを以下のように記述しました。
database.ymldefault: &default adapter: mysql2 encoding: utf8mb4 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: <%= ENV['DATABASE_USERNAME'] %> password: <%= ENV['DATABASE_PASSWORD'] %>埋め込んでなかったんですね。
お恥ずかしい。駄文ですが最後まで読んでいただき、ありがとうございました。
これを機に投稿していきたいと思います。
kyonでした。
- 投稿日:2020-03-22T17:54:40+09:00
【Rails】いいね機能の実装
現在絶賛作成中のポートフォリオでいいね機能を実装する際、色々躓いてしまったので反省もかねてメモしとこうと思います。
環境
- Rails 5.2.2
- mysql 8.0.17
- macOS
実現したいこと
Twitterのようないいね機能の実装。
そのために必要な処理
いいねボタンを押下した場合、
- 「いいねしたユーザーのid:user_id」と「いいねされた投稿のid:post_id」が「中間テーブル:likesテーブル」に保存される。
- いいねボタンが「いいね追加」と「いいね削除」で切り替わる。
必要手順
- 中間テーブルの作成
- テーブルの紐付け
- ルーティングの設定
- コントローラー側の処理
- ビューでの表示処理
大体こんな感じです。
では順番に見ていきましょう。
中間テーブルの作成
まずは、「いいねしたユーザー」と「いいねされた投稿」を保存するテーブルの作成からです。
$ rails g model Like user:references post:referencesreferencesを指定することで、外部キー制約が自動で付きます。
非常に便利。問題なければこのまま
$ rails db:migrateしちゃいましょう。
テーブルの紐付け
お次はモデルでhas_manyを使ってテーブルを紐付けます。
これに関しては、
【Rails】投稿とユーザーの紐付け(一対多)のメモ
でも似たような記事を書きましたので、復習になります。post.rbhas_many :likes has_many :users, through: :likes def liked_by?(user) likes.where(user_id: user.id).exists? endいいねされた投稿に対して、いいねしたユーザーは複数いるのでhas_manyを使います。
ここで使われているthroughは、likeテーブルを経由して直接userテーブルと繋げるためのものです。
また、
post.rbdef liked_by?(user) likes.where(user_id: user.id).exists? endこの子は「すでにいいねしたかどうか」を判断するためのメソッドで、後にビュー側で必要となってきます。
一応簡単に説明すると、
likesテーブルの「いいねしたユーザー:user_id」カラムにuser.idが存在するのか探すという処理です。引数userにはビューでcurrent_userを指定して入れます。
user.rbhas_many :likes, dependent: :destroy has_many :like_posts, through: :likes, source: :postユーザー側でもしてることはほとんど変わりません。
一点気をつけるとすれば、user.rbhas_many :like_posts, through: :likes, source: :postの部分です。
多くの関連記事でlike_postsのところがpostsになっていたのですが、もし他に has_many :posts があるなら名前が被らないようにしましょう。
railsが混乱してエラーになってしまうので、like_postsのように名前が被らないように注意することをおすすめします。
ルーティングの設定
お次はルーティングです。
routes.rbresources :posts do post 'add' => 'likes#create' delete '/add' => 'likes#destroy' endpostのidをとってくるためにネストします。
中は普通にルーティング設定してあげればおkです。コントローラー側の処理
ここまできたらあともう少し。
処理を書くためにコントローラーを作成します。
$ rails generate controller likesコントローラーができたら以下の処理を記述します。
likes_controller.rbclass LikesController < ApplicationController before_action :authenticate_user! before_action :set_like def create user = current_user post = Post.find(params[:post_id]) like = Like.create(user_id: user.id, post_id: post.id) end def destroy user = current_user post = Post.find(params[:post_id]) like = Like.find_by(user_id: user.id, post_id: post.id) like.delete end private def set_like @post = Post.find(params[:post_id]) end endcreate/destroyアクションで共通する処理は以下になります。
likes_controller.rbuser = current_user post = Post.find(params[:post_id])
- いいねするユーザーであるcurrent_userを変数userに格納
- いいねされた投稿のidとPostテーブルのidが一致するものをfindで見つけて変数postに格納
createアクション
createアクションでは、いいねされた場合の処理を記述します。
likes_controller.rblike = Like.create(user_id: user.id, post_id: post.id)Likeテーブルに、
user_idが、先ほどcurrent_userを格納した変数userのidで、post_idが、いいねされたPostテーブルのidを格納した変数post
のデータをcreateで作成する処理です。destroyアクション
destroyアクションでは、いいねが取り消された場合の処理を記述します。
likes_controller.rblike = Like.find_by(user_id: user.id, post_id: post.id) like.deletefind_byで、user_idがcurrent_userのidと一致するもの且つ、post_idがいいねされたpostのidと一致するものを探して、変数likeに格納します。
そしてdeleteメソッドでlikeを削除。
set_post
地味に一番下のところに、
likes_controller.rbprivate def set_like @post = Post.find(params[:post_id]) endとありますが、こちらはjsのところで必要となってきますのでひとまずおいといてください。
ビュー側の処理
index.html.erbでは投稿一覧を表示しています。
この投稿一つ一つにいいねぼたんを表示させたいわけですが、そのためにはいくつか階層を分ける必要があります。とりあえず、いいねボタンを表示したいところに以下のように記述しましょう。
index.html.erb<div id="like-btn-<%= post.id %>"> <%= render 'likes/like', post: post %> </div>renderを使用することで、いいねだけを表示するviewを表示します。
post: post は、いいねだけを表示するviewのpostにpost(ここでは@postsをeachで回してます)の情報を入れるでというくらいの意味あいだと理解して問題ないかと。
例えば、postの情報とは違った情報も渡したいのであれば、それに適応したものを渡してあげたらおk(私の場合はブックマーク一覧のviewでも同じようにいいねできるようにしたかったので、別にviewを作成してブックマークされている投稿の情報を格納したlikeを post: like として設定していました)。
_like.html.erb
「いいねだけを表示するview」である_like.html.erbを作成します。
app/views/likes/_like.html.erb<% if post.liked_by?(current_user) %> <%= link_to(post_add_path(post), method: :delete, remote: true, id: :"like-button-#{post.id}") do %> <i class="fa-lg fas fa-heart icon-btn liked"></i> <% end %> <% else %> <%= link_to(post_add_path(post), method: :post, remote: true, id: :"like-button-#{post.id}") do %> <i class="fa-lg fas fa-heart icon-btn not-like"></i> <% end %> <% end %>if post.liked_by?(current_user)
まずこのif文は、先ほどmodelで定めたメソッドを使ってすでに現在のユーザーがいいねしてるかどうかを判定します。
- すでにいいねしてる場合 → link_toはmethod: :delete
- まだいいねしてない場合 → link_toはmethod: :post
となるわけですね。
remote: true
link_toに記述しているremote: trueですが、この子はコントローラーに値を送信する役割を果たし、ajaxを発火してくれる優秀な子です。
ajaxを利用することで、画面全体をリダイレクトする必要がなく、いいねアイコンの部分のみ更新してくれるので、アプリ全体の負担が減ります。では最後に、remote: trueで呼び出すためのjsファイルを作ってあげましょう。
create.js.erb/destroy.js.erb
_like.html.erbと同階層に次の二つのファイルを作成します。
create.js.erb$('#like-btn-<%= @post.id %>').html("<%= escape_javascript(render partial: "likes/like", locals: { post: @post }) %>");destroy.js.erb$('#like-btn-<%= @post.id %>').html("<%= escape_javascript(render partial: "likes/like", locals: { post: @post }) %>");内容はどっちも一緒です。
先ほどコントローラー側で設定したset_likeで、いいねされた投稿のidを取得して、それをいいねアイコンのidとしています。
ここまでできたら、いいねボタンの完成です!
あとはいいねしたときと外した時でスタイルを変更するなど、創意工夫してください。
まとめ
いいね機能はほとんどのアプリで使うと思うので、結構情報があったのですが、いまいち仕組みが理解ができなくて結構苦戦しました。
特に中間テーブルへの保存で時間を取られたと思います。できる範囲で細かく書いたので、誰かの助けになれば幸いです。
ではでは。
- 投稿日:2020-03-22T17:09:30+09:00
bundle install あれこれ、、、、(☝︎ ՞ਊ ՞)☝︎
railsで便利な機能を加えるために使われるコマンド
bundle installは、今まで何気なく使っていましたが、今回の、デプロイ作業の際に発生したエラーにより深く知る事ができました。。。
簡単にbundle installのあれこれについて簡潔に解説します
bundler
依存関係を持っている複数のものを一括でインストールしてくれるというものだ
gemfile
bundle installをする際の設計図的な機能を持つファイル
Gemfile.lock
gemfile.lockは実際にインストールした結果図
bundle install
gemfile・Gemfile.lock の内容を踏まえて必要な情報をインストールするコマンド
bundle update
gemfileの内容を踏まえて、情報をアップデートするコマンド
また、その更新された情報ををGemfile.lockに記述します
- 投稿日:2020-03-22T17:05:23+09:00
Unknown action [初心者備忘録]
Unknown action
railsでアプリを作成していたところ、初めて見るエラーが発生しました。
結果からいうとコードの誤記という恥ずかしいイージーミスでしたので、反省と忘れないために書きたいと思います。エラー発生
Users::RegistrationsControllerのnew_credit_cardアクションを実行したところ以下のエラーが発生しました。
Unknown actionThe action 'new_creditcard' could not be found for Users::RegistrationsControllerなるほど、アクションがないのか。
ということで、RegistrationsControllerを確認します。解決
以下がコントローラーで当該のアクションを呼び出してる箇所とroutes.rbです。
uses/registrations_controller.rbrender :new_credit_cardconfig/routes.rbget 'creditcards', to: 'users/registrations#new_creditcard'しっかりアクション名を書き間違えていました。
ということで、これを修正することでエラーが直りました。おわり
今回はアクション名がルーティングとコントローラーで不一致だったためエラーが起きましたが、呼び出したいアクションをコントローラーのprivate以下に書いた場合でも、今回と同じエラーが起きるそうです。
最後まで見ていただきありがとうございました。
- 投稿日:2020-03-22T15:17:33+09:00
【Rails】バリデーションによるエラーメッセージ表示の手順
はじめに
Railsで、バリデーションによるエラーメッセージの表示をしようとしたら
エラーが出てしまい、調べてみたらとても初歩的なミスを犯していたので
忘れないよう、エラーメッセージ表示の手順を、自分用のメモみたいな感じでまとめました!手順
「新規投稿」を例にとることにします。
まず、新規投稿が行われる手順から
順を追って、バリデーションによるエラーメッセージの表示方法について説明していきます。
1.まずは、modelフォルダの中のpost.rbファイルの中で、バリデーションを設定します。
これによってバリデーションに弾かれたデータには自動でエラーメッセージが取得されます。models/post.rbclass Post < ApplicationRecord validates :content, presence: true, length: {maximum: 140} end
2.「new.html.erb」で投稿ボタンを押すと、form_tagで指定したURL("/posts/create")へデータが送信されますnew.html.erb<%= form_tag("/posts/create") do %> <textarea name="content"> </textarea> <input type="submit" value="投稿"> <% end %>
3.送信されたデータをルーティングで取得し、createアクションへroutes.rbpost "posts/create" => "posts#create"
4.送信されて来たデータの保存が完了した場合→("/posts/index")画面へ。
バリデーションに弾かれ、保存されなかった場合→再び("/posts/new")画面へ転送する。posts_controller.rbdef create @post = Post.new(content: params[:content]) if @post.save redirect_to("/posts/index") else render("posts/new") end end
この後、データベースに保存された全ての投稿データを取得し、繰り返し処理によりビューで表示することで、投稿が一つずつ表示されるわけですが
私は、バリデーションに弾かれたエラーメッセージの取得が、手順3の段階でされるのだと思っていました。
それゆえ、以下のように3の手順で取得した@postから、エラーメッセージを表示させようとしたら
new.html.erb<% @post.errors.full_messages.each do |message| %> <%= message %> <% end %>「undefined method `errors' for nil:NilClass」
といったように、エラーメッセージが定義されてませんと怒られてしまいました…ごめんなさい…。
何故取得できないのかというと、2の手順でバリデーションに弾かれたデータは
newアクションへ転送され、newアクションで取得する必要があるからでした。つまり、弾かれたデータは直前のアクションに送信されます。
しかし、それ以前に「new.html.erb」で「createアクションで定義した変数」を使えるわけないのですから当然でした……ですので、以下のように、newアクションで変数を定義してあげることで
ここに、バリデーションで弾かれたデータが作成されるようになります。posts_controller.rbdef new @post = Post.new end
あとは、「new.html.erb」で、エラーメッセージを表示するためのコードを書いてあげれば
ちゃんとエラーメッセージが表示されるようになるはずです。new.html.erb<% @post.errors.full_messages.each do |message| %> <%= message %></p> <% end %>参考
- 投稿日:2020-03-22T14:56:22+09:00
クイズアプリにおけるデータベース設計のアンチパターン
想定する読者
- ポートフォリオとしてクイズ系アプリを作成している方
アンチパターン例 3つ
例えば4択クイズを作りたいというとき、以下のようなDB設計を思いつくかもしれません。
※筆者が思いつきました。
これらは明確なアンチパターンです。
何故アンチパターンかというと、仕様変更に弱いからです。part1 マジックナンバーカラム
id question choice1 choice2 choice3 choice4 answer_number 1 次のうち偶数なのは? 3 7 8 1 3 2 「令和」はどう読む? れいわ へいせい しょうわ たいせい 1 仕様変更さん「○×クイズもできるようにしたい〜〜選択肢を2つに減らすだけだし簡単ですよね?選択肢も4つだけじゃなくて、5つとかもあり得るかも♪」
教訓: カラム名にマジックナンバーが入ってたらアンチパターンの合図
part2 フィールドに配列ぶちこみ
id question choices answer_number 1 次のうち偶数なのは? [3, 7, 8, 1] 3 2 「令和」はどう読む? [れいわ, へいわ, しょうわ, たいせい] 1 3 今は平成か? [○, ×] 2 4 次のうち一番大きい数字はどれか? [5, 10, 9, 8, 4, 1] 2 仕様変更さん1「よーし、検索機能を入れることになりました!検索機能って基本的な機能なので簡単ですよね?選択肢の中の文でも検索がかけられるようにしたいです♪」
仕様変更さん2「特定の選択肢は後で編集ができるようにしたいですね〜〜ユーザーさんって誤字とかしちゃうこともあるじゃないですか♪」
教訓: フィールドに配列を入れたくなったらアンチパターンの合図
part3 テーブルを分けて1対多にする(が、分けきれてない)
questionsテーブル
id content answer_number 1 「令和」はどう読む? 1 2 今は平成か? 2 choicesテーブル
id content question_id 1 れいわ 1 2 へいせい 1 3 しょうわ 1 4 たいせい 1 5 ○ 2 6 × 2 仕様変更さん「複数の答えを用意できるようにしたいです! この複雑な世の中で、答えって一つじゃないと思うんです♪」
何がダメだったのか: answer_numberはどちらかというとchoicesテーブルにあるべき情報だと思います。関心の範囲と、テーブルの範囲を合致させるように気をつけると、仕様変更による影響が少なくできると思います。
(おそらく)正しい設計
questionsテーブル
id content 1 「令和」はどう読む? 2 次のうち偶数のものはどれか? choicesテーブル
id content is_answer question_id 1 れいわ true 1 2 へいせい false 1 3 しょうわ false 1 4 たいせい false 1 5 11 false 2 5 12 true 2 5 15 false 2 5 99 false 2 5 18 true 2 アンチパターンと上から目線で言いながら、これが最適かと言われれば微妙です笑
ただし、現役のエンジニアさんに見せたら「良いんじゃない?」って言われたんで、そこまで悪くない設計だとは思います。
マサカリ頂けると有難いです。ちなみにRailsだったら、どんなmigrationファイル作ればいいの
$ ruby -v ruby 2.6.5 $ rails -v Rails 5.2.4db/migrate/時間_create_questions.rbclass CreateQuestions < ActiveRecord::Migration[5.2] def change create_table :questions do |t| t.text :content, null: false # 問題文の中身 t.references :user, foreign_key: true # クイズを作った人を想定 t.timestamps end end enddb/migrate/時間_create_choices.rbclass CreateChoices < ActiveRecord::Migration[5.2] def change create_table :choices do |t| t.text :content, null: false t.boolean :is_answer t.references :question, foreign_key: true t.timestamps end end end
- 投稿日:2020-03-22T14:39:26+09:00
【Ruby on Rails】railsでやってみたいこと。
- 投稿日:2020-03-22T14:35:40+09:00
[ancestry]編集画面でカテゴリー表示、選択
フリマアプリの作成で編集画面に遷移した時、出品時に設定したカテゴリーを親(parent)、子(child)、孫(grandchild)まで表示させたい
※出品機能が完了した程で進めます
完成イメージ
コントローラー
items_controller.rbbefore_action :set_item ~~省略~~ def edit grandchild_category = @item.category child_category = grandchild_category.parent @category_parent_array = [] Category.where(ancestry: nil).each do |parent| @category_parent_array << parent.name end @category_children_array = [] Category.where(ancestry: child_category.ancestry).each do |children| @category_children_array << children end @category_grandchildren_array = [] Category.where(ancestry: grandchild_category.ancestry).each do |grandchildren| @category_grandchildren_array << grandchildren end end ~~省略~~ def set_item @item = Item.find(params[:id]) endedit内
上2行は孫と子のレコードを取得してます。下3セットは親、子、孫と配列を作り、親はnameを、子と孫はancestryを格納してます。
ビュー
edit.html.haml.main-items = form_with model: @item, local: true do |f| ~~ 省略 ~~ .wrapper.category-wrapper = f.label :category_id , class: 'wrapper__label category-wrapper-label', id: "wrapper__label--category" do カテゴリー: %span.required ※必須 .category-wrapper-box .category-wrapper-select .category-wrapper-select__box = f.select :parent_name, @category_parent_array, {selected:@item.category.parent.parent.name}, { class: 'category-wrapper__category form-control', id: 'parent_category'} .category-wrapper-select#children_wrapper .category-wrapper-select__box = f.select :child_id, options_for_select(@category_children_array.map{|b| [b.name, b.id, {data:{category: b.id}}]}, {prompt: "指定なし", selected: @item.category.parent.id}),{}, {class: 'category-wrapper__category form-control', id: 'child_category'} .category-wrapper-select#grandchildren_wrapper .category-wrapper-select__box = f.select :category_id, options_for_select(@category_grandchildren_array.map{|b| [b.name, b.id, {data:{category: b.id}}]}, {prompt: "指定なし", selected: @item.category.id}),{}, {class: 'category-wrapper__category form-control', id: 'grandchild_category'}各要素コントローラーで配列格納したインスタンス変数を使用
selectタグの
selected:
で遷移直後の表示を指定子と孫は
options_for_select
を使用してます。
これにより親要素を変更した際、指定なし
となり、カテゴリーを選択できるようにしてます。孫のidをitemテーブルに紐つけてカテゴリーを判断しているので、他でわかりやすくなるよう
:caregory_id
と指定してます。
- 投稿日:2020-03-22T14:24:14+09:00
docker-composeでrailsをdocker化したらBundlerバージョンで怒られる
最近は railsもdocker化するのが多くみられるのですが、ハマりポイントも多いみたいですね。
特にdocker-composeを使ったときには、キャッシュがかかってうまく修正反映できないことがあります。エラーで怒られた。
例えば、既存のbundlerバージョンが合わなくて、bundler version 2.x
You must use Bundler 2 or greater with this lockfile.というエラー。
dockerでrubyのイメージを使うときには、bundlerも一緒に入ってるわけですが、
その時に作った時のGemfileに書かれているBundlerバージョンと合わないことがあります。解決法
その時には、Gemfile.lockを削除してからgemライブラリ達をビルドし直す必要があります。
$ rm Gemfile.lock $ sudo docker-compose build $ sudo docker-compose upこの時に気を付けなければいけないのは、docker-compose buildを使うこと、
どうやら、docker buildとdocker-compose buildはキャッシュが別物みたいです。
なので、いくらDockerでビルドし直しても、docker-compose には反映されなくて詰みます
(私は数時間失いました)。
エラーも出ないが、反映もされないのでキャッシュトラブルはとてもややこしい。参考
https://qiita.com/azul915/items/5b7063cbc80192343fc0
https://qiita.com/yoshijbbsk1121/items/87250501b32c6433943e
- 投稿日:2020-03-22T13:34:27+09:00
_destroyキーの使い方 ~~親(item)の子(image)を削除する~
一次ソースはこちらです。
問題
出品した商品(item)の画像を削除したいが、削除ボタンを押し更新ボタンを押しても削除できない
edit.html.haml.main-items = form_with model: @item, local: true do |f| .wrapper.image-wrapper #image-box.image-wrapper__image-box = f.fields_for :images do |i| - if @item.persisted? = i.check_box :_destroy, data:{ index: i.index}, class: 'hidden-destroy' .image-wrapper__image-box__js.js-file_group{:id => "item_images_attributes_#{i.index}_id", data:{index: "#{i.index}"}} = i.label :content, class: "image-wrapper__image-box__js__label" do .image-wrapper__image-box__js__label__image.img_field{id: "img_field--#{i.index}", onClick: "$('#file').click()"} - if @item.images[i.index][:content].present? = image_tag asset_path(@images[i.index].content), class:"preview " - else = image_tag 'icon_camera.png', class: "image-wrapper__image-box__js__label__image__url", id: "default-img" = i.file_field :content, class: "image-wrapper__image-box__js__label__file js-file", id: "item_images_attributes_#{i.index}_content" .js-remove %span.js-remove__text 削除前準備
問題コードの
= i.check_box :_destroy, data:{ index: i.index}, class: 'hidden-destroy'
によって各画像のチェックボックスにチェックが入ったら_destroyに値"1"がついた画像を削除するようにしているよ!(チェックボックスは隠してます)モデル
item.rbaccepts_nested_attributes_for :images, allow_destroy: true
accepts_nested_attributes_for
を使用し、paramsのimages_attributes:キー内で値を取り、送ることで親モデル(item)に紐つく子モデル(image)の削除、更新を行える。
allow_destroy: true
で_destroyキーが使えるようになる。コントローラー
items_controller.rbprivate def item_params params.require(:item).permit( :name, :description, :price, :brand_id, :area, :condition, :fee, :category_id, :shipping_days, images_attributes: [:content, :id, :_destroy] ).merge(user_id: current_user.id) endストロングパラメーターに
images_attributes: [:content, :id, :_destroy]
を記述原因追求
コントローラー
items_controller.rbdef update binding.pry #デバック if @item.update(item_params) redirect_to item_path(@item) else flash.now[:alert] = '画像を1枚以上添付してください' redirect_to edit_item_path(@item) end endbinding.pryを使用し、更新ボタンを押した時のparams中身をターミナルで確認する
ターミナル
※横スクロール長くてすみません。
※配列の添字(index)により一枚目は"0"から始まっています。"item"=>{"images_attributes"=>{"0"=>{"_destroy"=>"0", "id"=>"2"}, "1"=>{"_destroy"=>"1", "id"=>"47"}, "2"=>{"_destroy"=>"1", "id"=>"48"}, "3"=>{"_destroy"=>"1", "id"=>"49"}, "4"=>{"_destroy"=>"0", "id"=>"50"}, "5"=>{"_destroy"=>"0"}},今回は5枚ある画像から2、3、4枚目を削除した。
しかし、paramsに入る内容は"4"=>{"_destroy"=>"0", "id"=>"50"}
までで良いはずだが、不要な"5"=>{"_destroy"=>"0"}}
が入ってしまっている。
("5"は一番右の新規画像追加用のカメラマークのこと)ブラウザ
この不要な"5"をChromeの検証ツールElementsで確認してみると
不要なhtmlタグみっけcheckboxによりこのタグがある。
nameがつけられparamsに入ってしまっていた。解決方法
- if @item.images[i.index][:content].present?
をfields_forの直下に持ってきて、既存の画像のみにチェックボックスが付与されるように分けよう!!edit.html.haml.main-items = form_with model: @item, local: true do |f| .wrapper.image-wrapper #image-box.image-wrapper__image-box = f.fields_for :images do |i| - if @item.images[i.index][:content].present? - if @item.persisted? = i.check_box :_destroy, data:{ index: i.index}, class: 'hidden-destroy' .image-wrapper__image-box__js.js-file_group{:id => "item_images_attributes_#{i.index}_id", data:{index: "#{i.index}"}} = i.label :content, class: "image-wrapper__image-box__js__label" do .image-wrapper__image-box__js__label__image.img_field{id: "img_field--#{i.index}", onClick: "$('#file').click()"} = image_tag asset_path(@images[i.index].content), class:"preview " .js-remove %span.js-remove__text 削除 -else .image-wrapper__image-box__js.js-file_group{:id => "item_images_attributes_#{i.index}_id", data:{index: "#{i.index}"}} = i.label :content, class: "image-wrapper__image-box__js__label" do .image-wrapper__image-box__js__label__image.img_field{id: "img_field--#{i.index}", onClick: "$('#file').click()"} = image_tag 'icon_camera.png', class: "image-wrapper__image-box__js__label__image__url", id: "default-img" = i.file_field :content, class: "image-wrapper__image-box__js__label__file js-file", id: "item_images_attributes_#{i.index}_content" .js-remove %span.js-remove__text 削除これでparamsを確認すると
"item"=>{"images_attributes"=>{"0"=>{"_destroy"=>"0", "id"=>"2"}, "1"=>{"_destroy"=>"1", "id"=>"47"}, "2"=>{"_destroy"=>"1", "id"=>"48"}, "3"=>{"_destroy"=>"1", "id"=>"49"}, "4"=>{"_destroy"=>"0", "id"=>"50"}}"5"が入ってないOK
binding.pryを外して更新すると削除できました!
- 投稿日:2020-03-22T12:50:20+09:00
【Rails】データベース関連の知識、操作方法
はじめに
Railsでアプリを開発するにあたって、データベース関連の設計、操作で色々苦労したので、自分の覚えの為にもまとめてみました。
環境
- Ruby 2.5.1
- Rails 5.0.7
そもそもデータベースとは
一定の形式で、「複数で共有、利用すること」と「検索、加工すること」を目的に整理されたデータの集まりの事を指す。プログラミングに限定された用語ではなく、日常生活で使う辞書等もデータベースみたいですね!
データベース種類
- 階層型
- ネットワーク型
- リレーショナル型(RDBと呼ばれ最も主流)
- Oracle Database
- MySQL
- PostgreSQL
- Microsoft SQL Server など
- NoSQL
MySQLを使って開発する事が多かったので、ここからMySQL(リレーショナル型)に絞って話を進めていきます。
リレーショナルデータベース(RDB)、SQLとは
RDBは現在主流のデータベースで、エクセルみたいな表で構成されたデータベース。行と列を持ち、表形式でデータの関係性を示す。SQLを用いたデータのアクセスが可能。
SQL・・・Structured Query Languageの略で、リレーショナルデータベース(RDB)の操作を行うための言語。日本語訳は「構造化された問い合わせ言語」みたいな感じですかね!とにかくデータベースから情報を参照する言語。
データベース設計で意識する事
正規化、アソシエーション、制約の話をまとめていきます。
正規化
正規化・・・データベースのデータ構造をより効率的で重複や無駄のないシンプルな構造にするための手順。正規化の話は複雑ですが以下例です。
×予約情報が重複している為望ましくない
○テーブルを2つに分け予約情報の重複を解消
アソシエーション
アソシエーション・・・テーブル間の関係性をモデルの上の関係として操作できるようにする仕組み。
アソシエーションを利用すると複数のテーブルにまたがるデータ操作もより直感的に利用できる。
【モデルへの書き方】〇〇.rb# 1対多 has_many :モデル名複数形 # 多対1 belongs_to :モデル名単数形 # 1対1 どちらかのモデルにhas_one,もう一方にbelongs_to has_one :モデル名単数形 belongs_to :モデル名単数形 # 多対多 中間テーブルが必要 has_many :中間テーブル名複数形 has_many :モデル名複数形 through: :中間テーブル名複数形制約による安全性
制約とは・・・特定のデータの保存を許さない事。例えば同じニックネームのユーザーを登録できないようにする、名前のデータが空のユーザーを保存を許さない等。主な制約は以下です。
制約種類
- NOT NULL制約・・・空(nil)レコードは保存できない。
- 一意性制約 ・・・同じ値を設定できない。一意性制約をかけるときは、インデックスの作成も必要。 全てのデータを検索しないと、過去のデータと重複しているか判断できない為。
- 主キー制約 ・・・Railsでは主キーはidカラムとして自動で作成(テーブルの一番左のカラム)。
- 外部キー制約 ・・・外部キー制約は、外部キーの対応するレコードが必ず存在しなくてはいけないという制約です。外部キーのカラムに値があっても、その値を主キーとして持つ他のテーブルにレコードが存在する必要あり。
create_users.rb# NOT NULL制約 nameはカラム名,stringはデータ型 t.string :name, null: false # 一意性制約 インデックスとセット usersはテーブル名、emailはカラム名、stringはデータ型 add_column :users, :email, :string add_index :users, :email, unique: true # 外部キー referencesはデータ型 これでuser_idというカラムが生成される t.references :user, foreign_key: trueデータ型種類
- string : 文字列
- text : 長い文字列
- integer : 整数
- float : 浮動小数(実数)
- datetime : 日時
- time : 時間
- date : 日付
- boolean : Boolean
Railsでのコマンド集
ターミナル/マイグレーションファイル<データベース全般> #データベース作成(色んなテーブルを入れる箱の作成) database.ymlの内容に基づく rails db:create #データベース削除 rails db:drop #マイグレーションファイルの適用 rails db:migrate #マイグレーションファイルがどこまで適用されているか確認 rails db:migrate:status #マイグレーションのバージョンを下げる デフォルトでは一つずつ rails db:rollback #マイグレーションのバージョンを複数下げる 例では3段階 rails db:rollback STEP=3 <モデル(テーブル)関連>コマンドでマイグレーションが作成されるので、そこで編集等を行う #モデル(テーブル作成) モデル名は単数形/頭文字を大文字にする rails g model モデル名 #最初からカラム付きでモデルを作成したい時,2行目は例 rails g model モデル名 カラム名:型 rails g model User name:string email:string #既存のモデル(テーブル)にカラムを追加/削除,2行目は例 rails g migration <マイグレーションファイル名> <追加するカラム名:型> rails g migration add_email_to_users email:string #以下マイグレーションファイル(add付きのファイル名でもカラムの削除は行える) # 追加(2行上のコマンドであればマイグレーション作成時からある) add_column :users, :email, :string # 削除(必要に応じて追加) remove_column :users, :gmail, :string # まとめて削除(必要に応じて追加) remove_columns :users, :column_1, :column_2 [, ...] # 追加する場所を指定する場合(必要に応じて追加) # 以下、nameカラムの直後にemailカラムを追加する場合 add_column :users, :email, :string, :after => :name #指定のテーブル削除,2行目は例 rails g migration Dropテーブル名 rails g migration DropUser #既存のテーブル名を変更する,2行目は例 rails g migration Rename変更前テーブル名To変更後テーブル名 rails g migration RenameUserToCustomer #既存のカラムの内容を変更(例.Userモデルのemailカラムに制約をつける場合) rails g migration ChangeColumnToUser #以下マイグレーションファイル # 変更内容 def up change_column :users, :email, :string, null: false end # 変更前の状態 def down change_column :users, :email, :string, null: true endその他の便利なメソッド
外部キー制約が要因で特定のレコードを削除できないエラーが起きる事があると思います。その時はdelete, delete_all, destroy, destroy_allメソッドが大変便利です。
こちらの記事が大変参考になりました。
https://qiita.com/kamelo151515/items/0fa7fb15a1d2c1e44db2参考URL
https://qiita.com/kamelo151515/items/0fa7fb15a1d2c1e44db2
https://qiita.com/ryouzi/items/2682e7e8a86fd2b1ae47
- 投稿日:2020-03-22T11:53:15+09:00
[Rails]Swiperで画像スライド作成
完成イメージ
Swiperとは
スライダー(カルーセル)が作れるJavaScriptライブラリです。
しかもPCでもスマホでも使えて、レスポンシブ対応可能!
引用元 https://garigaricode.com/swiper/使用準備
今回はCDNから読み込む方法で行きます。
application.html.haml!!! %html %head %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/ %title FrimaApp %script{src: "https://js.pay.jp/", type: "text/javascript"} = csrf_meta_tags = csp_meta_tag = stylesheet_link_tag 'application', media: 'all' %link{:href => "https://unpkg.com/swiper/css/swiper.min.css", :rel => "stylesheet"}/ = javascript_include_tag 'application' %body %script{:src => "https://unpkg.com/swiper/js/swiper.min.js"} = yield
%link{:href => "https://unpkg.com/swiper/css/swiper.min.css", :rel => "stylesheet"}/
は%haad内に
%script{:src => "https://unpkg.com/swiper/js/swiper.min.js"}
は%body内に記述します。ビュー
show.html.haml/ メイン表示部分、各スライド .swiper-container.swiper1 .swiper-wrapper - @images.each do |images| = image_tag asset_path(images.content), class:"swiper-slide" / 次へ、前へボタン .swiper-button-prev.prev1 .swiper-button-next.next1 / ページネーション .swiper-pagination / 2段目、各スライド .swiper-container.swiper2 .swiper-wrapper - @images.each do |images| = image_tag asset_path(images.content), class:"swiper-slide"JSファイル
javascripts/item/show.js$(function(){ var mySwiper1 = new Swiper('.swiper1', { loop: true, // ループ loopedSlides: (".swiper1").length, // loop: trueの場合必須 総スライド数の半分以上の値を設定 navigation: { nextEl: '.next1', // 次ページボタンのセレクタ名を指定 prevEl: '.prev1', // 前ページボタンのセレクタ名を指定 }, pagination: { el: '.swiper-pagination', // ページネーションのセレクタ名を指定 type: 'bullets', // ●○◯◯形式 clickable: true, // type: 'bullets'の時のみ有効 ◯クリックで直接そのスライドへ移動 }, }); var mySwiper2 = new Swiper('.swiper2', { loop: true, loopedSlides: (".swiper2").length, // 最初の前、最後の後に複製される非表示のスライド数を指定 slideToClickedSlide: true, // スライドクリックでそのスライドに移動する centeredSlides: true, // センター表示 slidesPerView: 3, // 一度に表示するスライド数を指定 controller: { control: mySwiper1, // 連動させるSwiperの定義名を指定 by: 'slide', // 連動Swiperをスライド単位で制御 }, }); mySwiper1.controller.control = mySwiper2; });
mySwiper1.controller.control = mySwiper2
はなぜ1個目と2個目のSwiperでcontollerの指定方法が違っているかというとですね、コードは上から順番に実行されるため、1個目のSwiperを作っている最中は2個目のSwiperは定義前なのでまだ存在しません。
この状態で連携しようとしても「そんなものないですよ」という判断になってしまいます。
2個目のSwiperを作っている時には、1個目のSwiperはすでに存在しているため、var mySwiper2 = … の中で書くことができます。
なので、2個目のSwiperも作り終わってから1個目のcontoller指定をしてやるとうまくいきます。
引用元 https://garigaricode.com/swiper/SCSS
item.show.scss.swiper-container { width: 400px; font-size: 0; } /* 2段目のSwiper全体のスタイル */ .swiper2 { margin-top: 8px; } /* 2段目のSwiperのスライドのスタイル */ .swiper2 .swiper-slide { height: 100px; line-height: 100px; -webkit-filter: brightness(0.5); filter: brightness(0.5); // 画像の明度調節 } /* 2段目のSwiperの現在のスライドのスタイル */ .swiper2 .swiper-slide-active { -webkit-filter: brightness(1); filter: brightness(1); }
-webkit-
-webkit-とは?
webkitの記述は、ベンダープレフィックスと呼ばれCSS3で実装予定の機能をブラウザ各社が先行して実装した機能を各ブラウザで使えるようにしたものです。
引用元 https://code-schools.com/css-webkit/完成
引用を多用しました。
ご指摘等あればぜひコメントお願いします!!
- 投稿日:2020-03-22T10:41:52+09:00
#Rails の Controller でネストした params を permit / require するのはメソッドチェーンじゃいかんともしがたいからメソッドを何度も実行するしかないのか? ( ActionController::Parameters )
- require / permit / permit! の引数の受け取り方も、返り値も使い方もなんだか不揃いで、なんとも使いがたい
- メソッドチェーンも使いづらくパラメータがネストされている時に permit / require しづらい
- permit! は引数を受け取れず、破壊的に params を変えてしまう
- require と permit を別々に実行して、頑張って組み立て直す必要があるかもしれない
- 具体ケースとしては JSON リクエストを受け取った結果を Controller の params で扱っているのだが、Rails のもともとのフレームワークのレールからは外れる部分が多いのか、苦労がある。
# ネストした params params = ActionController::Parameters.new( name: 'Alice', age: 22, contact: ActionController::Parameters.new( tel: 07011112222, email: 'user@example.com' ) ) # => <ActionController::Parameters {"name"=>"Alice", "age"=>22, "contact"=><ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: false>} permitted: false> # require は必須パラメータチェックのためだけに利用して、返り値は使わない params.require([:name, :age, :contact]) params[:contact].require([:tel, :email]) # permit する時はネストの構造に合わせた書き方をして、返り値を permit された params として利用する peritted_params = params.permit(:name, :age, contact: [:tel, :email]) # => <ActionController::Parameters {"name"=>"Alice", "age"=>22, "contact"=><ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: true>} permitted: true>僕はもう疲れたよ
公式
- tap のブロックで頑張る方法が書かれていた
- 結局 Action Controller Parameters は頼りに出来ない気がした
def person_params params.require(:person).permit(:name).tap do |person_params| person_params.require(:name) # SAFER end endexample
# Execute with rails console # Rails like Nested params with ActionController::Parameters instance params = ActionController::Parameters.new( name: 'Alice', age: 22, contact: ActionController::Parameters.new( tel: 07011112222, email: 'user@example.com' ) ) # <ActionController::Parameters {"name"=>"Alice", "age"=>22, "contact"=><ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: false>} permitted: false> # Ooops # Args must be Array params.require(:name, :age, :contact) # ArgumentError: wrong number of arguments (given 3, expected 1) # It works params.require([:name, :age, :contact]) # => ["Alice", 22, <ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: false>] # But with method chain # How to permit multiple params Require and Permit ? # require method returns values Array # it is not work for generate permitted params # because .permit .require both method does not change "params" params.require([:name, :age, :contact])[2].permit(:tel, :email).require([:tel, :email]) # => [941921426, "user@example.com"] # require method returns values Array # Unable to use methods chain params.require([:name, :age, :contact])[2].require([:tel, :email]).permit(:tel, :email) # NoMethodError: undefined method `permit' for [941921426, "user@example.com"]:Array # It is answer? # User require and permit methods # Without return values # Without method chains # # And last execute permit! params.require([:name, :age, :contact]) params[:contact].require([:tel, :email]) peritted_params = params.permit(:name, :age, contact: [:tel, :email]) # <ActionController::Parameters {"name"=>"Alice", "age"=>22, "contact"=><ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: true>} permitted: true> # OR params.permit(:name, :age, contact: [:tel, :email]) params.permit! params # => <ActionController::Parameters {"name"=>"Alice", "age"=>22, "contact"=><ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: true>} permitted: true>Original by Github issue
- 投稿日:2020-03-22T10:28:12+09:00
#Rails / permit and require Nested params / Without method chains / ActionController::Parameters
# Execute with rails console # Rails like Nested params with ActionController::Parameters instance params = ActionController::Parameters.new( name: 'Alice', age: 22, contact: ActionController::Parameters.new( tel: 07011112222, email: 'user@example.com' ) ) # <ActionController::Parameters {"name"=>"Alice", "age"=>22, "contact"=><ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: false>} permitted: false> # Ooops # Args must be Array params.require(:name, :age, :contact) # ArgumentError: wrong number of arguments (given 3, expected 1) # It works params.require([:name, :age, :contact]) # => ["Alice", 22, <ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: false>] # But with method chain # How to permit multiple params Require and Permit ? # require method returns values Array # it is not work for generate permitted params # because .permit .require both method does not change "params" params.require([:name, :age, :contact])[2].permit(:tel, :email).require([:tel, :email]) # => [941921426, "user@example.com"] # require method returns values Array # Unable to use methods chain params.require([:name, :age, :contact])[2].require([:tel, :email]).permit(:tel, :email) # NoMethodError: undefined method `permit' for [941921426, "user@example.com"]:Array # It is answer? # User require and permit methods # Without return values # Without method chains # # And last execute permit! params.require([:name, :age, :contact]) params[:contact].require([:tel, :email]) peritted_params = params.permit(:name, :age, contact: [:tel, :email]) # <ActionController::Parameters {"name"=>"Alice", "age"=>22, "contact"=><ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: true>} permitted: true> # OR params.permit(:name, :age, contact: [:tel, :email]) params.permit! params # => <ActionController::Parameters {"name"=>"Alice", "age"=>22, "contact"=><ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: true>} permitted: true>Original by Github issue
- 投稿日:2020-03-22T09:32:12+09:00
#Rails permit nested params ( Action Controller Parameters ) example
# Rails like Nested params with ActionController::Parameters instance params = ActionController::Parameters.new( name: 'Alice', age: 22, contact: ActionController::Parameters.new( tel: 07011112222, email: 'user@example.com' ) ) # <ActionController::Parameters {"name"=>"Alice", "age"=>22, "contact"=><ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: false>} permitted: false> # Permit only flat params permitted_params = params.permit(:name, :age) # params not changed # see "permitted: false" params # => <ActionController::Parameters {"name"=>"Alice", "age"=>22, "contact"=><ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: false>} permitted: false> # permit return value is permitted only flat params permitted_params # => <ActionController::Parameters {"name"=>"Alice", "age"=>22} permitted: true> # permit all params params.permit! # permitted include all nested params # see flags "permitted: true" params # => <ActionController::Parameters {"name"=>"Alice", "age"=>22, "contact"=><ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: true>} permitted: true> params.permitted? # => true params[:contact].permitted? # => true # permit only nested params params2 = ActionController::Parameters.new( name: 'Alice', age: 22, contact: ActionController::Parameters.new( tel: 07011112222, email: 'user@example.com' ) ) contact_permitted_params = params2[:contact].permit(:tel, :email) # => <ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: true> partly_permitted_params2 = params2.merge(contact: contact_permitted_params) # <ActionController::Parameters {"name"=>"Alice", "age"=>22, "contact"=><ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: true>} permitted: false> partly_permitted_params2.permitted? # => false partly_permitted_params2[:contact].permitted? # => true # NOTE # permit! bang does not receive arguments oh no ... # params2[:contact].permit!(:tel, :email) # ArgumentError: wrong number of arguments (given 2, expected 0) # In this way wee need permit nested params with two times execute methods # # 1. permit with args # 2. permit! # params2[:contact].permit(:tel, :email) # => <ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: true> # params2[:contact].permit! # => <ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: true> # params2 # => <ActionController::Parameters {"name"=>"Alice", "age"=>22, "contact"=><ActionController::Parameters {"tel"=>941921426, "email"=>"user@example.com"} permitted: true>} permitted: false> params_include_nil = ActionController::Parameters.new( name: 'Alice', age: 22, contact: nil ) # => <ActionController::Parameters {"name"=>"Alice", "age"=>22, "contact"=>nil} permitted: false> # flat permit allowed params_include_nil.permit(:name, :age, :contact) # => <ActionController::Parameters {"name"=>"Alice", "age"=>22, "contact"=>nil} permitted: true> # nexted permit ignored params_include_nil.permit(:name, :age, contact: [:tel, :email]) # => <ActionController::Parameters {"name"=>"Alice", "age"=>22} permitted: true> # When nested params has empty values instance params_include_empty = ActionController::Parameters.new( name: 'Alice', age: 22, contact: ActionController::Parameters.new ) # => <ActionController::Parameters {"name"=>"Alice", "age"=>22, "contact"=><ActionController::Parameters {} permitted: false>} permitted: false> # NG : When specified as flatten key # nested params deleted params_include_empty.permit(:name, :age, :contact) # => <ActionController::Parameters {"name"=>"Alice", "age"=>22} permitted: true> # NG : When specified Empty hash # nested params deleted params_include_empty.permit(:name, :age, contact: []) # => <ActionController::Parameters {"name"=>"Alice", "age"=>22} permitted: true> # NG : When specified some key # Nothing in permitted nested params but not deleted params_include_empty.permit(:name, :age, contact: [:tel]) # => <ActionController::Parameters {"name"=>"Alice", "age"=>22, "contact"=><ActionController::Parameters {} permitted: true>} permitted: true>Original by Github issue
- 投稿日:2020-03-22T08:50:38+09:00
jQueryでFlash, error_messageを閉じる方法(Bootstrap xボタンが反応しない場合)。
はじめに
Bootstrapの
flash
,error_messages
などに使われる
<div class= "alert alert-○○">
が閉じない場合の対処法です。
私の環境が悪く閉じるアクションができないのか定かではありませんが
jQuery
を使って閉じるアクションを可能にする方法を書いておきます。私の場合、xマークのボタンは表示できましたがクリックをしても一切反応しませんでした。
開発環境
- bootstrap-sass (3.3.7)
- devise (4.7.1)
- jquery-rails (4.3.1)
- rails (5.2.4.1)
Flashやerror_messagesにxボタンをつける
こちら Bootstrap4版
こちら Bootstrap3版
上記のサイトのサンプルを確認すると<div class="alert alert-success alert-dismissible"> <button type="button" class="close" data-dismiss="alert">×</button> <strong>Success!</strong> Indicates a successful or positive action. </div>サイトでは、↓
To close the alert message, add a .alert-dismissible class to the alert container. Then add class="close" and data-dismiss="alert" to a link or a button element (when you click on this the alert box will disappear).
簡単に要約するとalertなどを閉じる時は
class=alert-dismissible
をalertコンテナ
に
<button>
タグにclass="close"
,data-dismiss="alert"
を記入すればxボタンクリック時に消えますよと。(私の環境で何か導入し忘れのものがある、もしくは、Javascript,jQueryで閉じるアクションする前提なのかもしれません。わかる方宜しければコメントなどで教えてください。)
jQueryで閉じるイベント
applivation.js$(function() { $(".close").click(function() { $(".alert").hide(); }); });参考サイト
w3schools.com
(https://www.w3schools.com/bootstrap/bootstrap_alerts.asp)
(https://www.w3schools.com/bootstrap4/bootstrap_alerts.asp)終わりに
xボタンを押した際にBootstrapを適用したクラスのFlashやエラーメッセージを閉じることができると思います。
jQuery、Javascriptの重要性を再確認できました。
- 投稿日:2020-03-22T07:58:02+09:00
jQueryでFlash, Alertsを閉じる方法(Bootstrap xボタンが反応しない場合)。
はじめに
Bootstrapの
flash
,error_messages
などに使われる
<div class= "alert alert-○○">
が閉じない場合の対処法です。
私の環境が悪く閉じるアクションができないのか定かではありませんが
jQuery
を使って閉じるアクションを可能にする方法を書いておきます。私の場合、xマークのボタンは表示できましたがクリックをしても一切反応しませんでした。
開発環境
- bootstrap-sass (3.3.7)
- devise (4.7.1)
- jquery-rails (4.3.1)
- rails (5.2.4.1)
Alertやerror_messagesにxボタンをつける
こちら Bootstrap4版
こちら Bootstrap3版
上記のサイトのサンプルを確認すると<div class="alert alert-success alert-dismissible"> <button type="button" class="close" data-dismiss="alert">×</button> <strong>Success!</strong> Indicates a successful or positive action. </div>サイトにでは、↓
To close the alert message, add a .alert-dismissible class to the alert container. Then add class="close" and data-dismiss="alert" to a link or a button element (when you click on this the alert box will disappear).
簡単に要約するとalertなどを閉じる時は
class=alert-dismissible
をalertコンテナ
に
<button>
タグにclass="close"
,data-dismiss="alert"
を記入すればxボタンクリック時に消えますよと。(私が何か導入し忘れのものがある、もしくは、Javascript,jQueryで閉じるアクションする前提なのかもしれません。わかる方よければコメントなどで教えてください。)
JQueryで閉じるイベント
applivation.js$(function() { $(".close").click(function() { $(".alert").hide(); }); });参考サイト
w3schools.com
(https://www.w3schools.com/bootstrap/bootstrap_alerts.asp)
(https://www.w3schools.com/bootstrap4/bootstrap_alerts.asp)終わりに
xボタンを押した際にBootstrapを適用したクラスのFlashやエラーメッセージを閉じることができると思います。
jQuery、Javascriptの重要性を再確認できました。
- 投稿日:2020-03-22T07:58:02+09:00
jQueryでFlash, error_messageを閉じる方法(Bootstrap xボタンが反応しない場合)。
はじめに
Bootstrapの
flash
,error_messages
などに使われる
<div class= "alert alert-○○">
が閉じない場合の対処法です。
私の環境が悪く閉じるアクションができないのか定かではありませんが
jQuery
を使って閉じるアクションを可能にする方法を書いておきます。私の場合、xマークのボタンは表示できましたがクリックをしても一切反応しませんでした。
開発環境
- bootstrap-sass (3.3.7)
- devise (4.7.1)
- jquery-rails (4.3.1)
- rails (5.2.4.1)
Alertやerror_messagesにxボタンをつける
こちら Bootstrap4版
こちら Bootstrap3版
上記のサイトのサンプルを確認すると<div class="alert alert-success alert-dismissible"> <button type="button" class="close" data-dismiss="alert">×</button> <strong>Success!</strong> Indicates a successful or positive action. </div>サイトでは、↓
To close the alert message, add a .alert-dismissible class to the alert container. Then add class="close" and data-dismiss="alert" to a link or a button element (when you click on this the alert box will disappear).
簡単に要約するとalertなどを閉じる時は
class=alert-dismissible
をalertコンテナ
に
<button>
タグにclass="close"
,data-dismiss="alert"
を記入すればxボタンクリック時に消えますよと。(私の環境で何か導入し忘れのものがある、もしくは、Javascript,jQueryで閉じるアクションする前提なのかもしれません。わかる方宜しければコメントなどで教えてください。)
JQueryで閉じるイベント
applivation.js$(function() { $(".close").click(function() { $(".alert").hide(); }); });参考サイト
w3schools.com
(https://www.w3schools.com/bootstrap/bootstrap_alerts.asp)
(https://www.w3schools.com/bootstrap4/bootstrap_alerts.asp)終わりに
xボタンを押した際にBootstrapを適用したクラスのFlashやエラーメッセージを閉じることができると思います。
jQuery、Javascriptの重要性を再確認できました。
- 投稿日:2020-03-22T07:32:34+09:00
Ruby on Rails チュートリアル第9章メモ
メモ
8章でおこなった基本的なログイン機構にremember me機能の追加を永続クッキーを生成。
永続的セッションシステムの構築を目指す。
機能そのものへの理解を深めたいのでテストは飛ばす。Remember me
ブラウザを閉じた後でも、ユーザーのログイン状態を有効にする機能。この機能を使うと、ユーザーが明示的にログアウトを実行しない限り、ログイン状態を維持。
トークンとは
パスワードの平文と同じような秘匿されるべき情報を指します。パスワードとトークンとの一般的な違いは、パスワードは使用者が自身で作成・管理する情報であるのに対し、トークンはコンピューターなどが生成した情報であるということ。
記憶トークンと暗号化
- 記憶トークンにはランダムな文字列を生成して用いる。
- ブラウザのcookiesにトークンを保存するときは、有効期限を設定。
- トークンはハッシュ値に変換してからDBに保存。
- ブラウザのcookiesに保存するユーザーIDは暗号化しておく。
- 永続ユーザーIDを含むcookiesを受け取ったら、そのIDでDBを検索し、記憶トークンのcookiesがDB内のハッシュ値と一致することを確認。
記憶トークンをハッシュ化した値を保存する場所remember_digestカラムを追加、マイグレーション。
$ rails generate migration add_remember_digest_to_users remember_digest:string #トークンはユーザーがいじるものじゃない(かついじられてはいけない)から完成したファイルに特にインデックスを追加するなどはないのでそのまま $ rails db:migrate
- トークン生成メソッドを追加。
app/models/user.rbclass User < ApplicationRecord before_save { self.email = email.downcase } validates :name, presence: true, length: { maximum: 50 } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false } has_secure_password validates :password, presence: true, length: { minimum: 6 } # 渡された文字列のハッシュ値を返す def User.digest(string) cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost BCrypt::Password.create(string, cost: cost) end # ランダムなトークンを返す def User.new_token SecureRandom.urlsafe_base64 end endユーザーを記憶するには、記憶トークンを作成して、そのトークンをダイジェストに変換したものをDBに保存。
モデルにクラスメソッドとして定義。
- 記憶トークンをユーザーと関連付け、トークンに対応する記憶ダイジェストをDBに保存。
マイグレーションファイルを作成した際にremember_digestカラムを作成したが、remember_token(記憶トークン)カラムは作成していない。なので、user.remember_tokenメソッドを使ってトークンにアクセスし、かつ、トークンをDBに保存せずに実装する必要がある。
そこで、パスワードの実装と同様の手法でこれを解決。パスワードではpassword属性とDB上にあるセキュアなpassword_digest属性の2つにを使っていた。仮想のpassword属性はhas_secure_passwordメソッドで自動作成されていたが、今回はremember_tokenのコードを自分で書く必要がある。これを書くために、attr_accessorメソッドでremember_tokenを「仮想の」属性として宣言し、Userインスタンスに値を持たせる際には、暗号化し、remember_digestとする。
app/models/user.rbclass User < ApplicationRecord attr_accessor :remember_token <--注目 before_save { self.email = email.downcase } validates :name, presence: true, length: { maximum: 50 } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false } has_secure_password validates :password, presence: true, length: { minimum: 6 } # 渡された文字列のハッシュ値を返す def User.digest(string) cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost BCrypt::Password.create(string, cost: cost) end # ランダムなトークンを返す def User.new_token SecureRandom.urlsafe_base64 end # 永続セッションのためにユーザーをデータベースに記憶する def remember self.remember_token = User.new_token <--注目 update_attribute(:remember_digest, User.digest(remember_token)) <--記憶ダイジェストを更新 #remember_digestカラムにUser.digest(remember_token)を代入 end endポイント①:マイグレーションを行ってあるので、Userモデルには既にremember_digest属性が追加されているが、remember_token属性はまだ追加されていない。user.remember_tokenでトークンにアクセス出来るように、かつトークンをデータベースに保存せずに実装する必要があるためattr_accessorを使ってアクセス可能な属性を作成している。
ポイント②:selfを付けないとremember_tokenがローカル変数になってしまうただの変数代入になってしまうのがRuby。今欲しいのはローカル変数ではない。selfキーワードを与えると、この代入によってユーザーのremember_token属性が期待どおりに設定される。
ログイン状態の保持
user.rememberメソッドが動作するようになったので、ユーザーの暗号化済みIDと記憶トークンをブラウザの永続cookiesに保存して、永続セッションを作成する準備ができた。
永続セッションを実際に行うにはcookiesメソッドを使用する。
cookiesメソッドは、sessionのときと同様にハッシュとして扱える。
個別のcookiesは、1つのvalue (値) と、オプションのexpires (有効期限) からできている。cookies[:remember_token] = { value: remember_token, expires: 20.years.from_now.utc }ただし20年期限は定番化しているので、permanentメソッドで省略できる。
cookies.permanent[:remember_token] = remember_tokenユーザーIDをcookiesに保存するには、sessionメソッドで使ったのと同じパターンを使う。
cookies[:user_id] = user.idしかし、このままではIDが生のテキストとしてcookiesに保存されてしまうため、signedメソッド(暗号化させる)を使う。
cookies.signed[:user_id] = user.id
ユーザーIDと記憶トークンはペアで扱う必要があるので、cookiesも永続化しないといけない。そこで、signedとpermanentをメソッドチェーンでつないで
cookies.permanent.signed[:user_id] = user.idcookiesを設定すると、以後のページのビューでこのようにcookiesからユーザーを取り出せるようになる。
User.find_by(id: cookies.signed[:user_id])cookies.signed[:user_id]では自動的にユーザーIDのcookiesの暗号が解除され、元に戻る(便利!!)
最後に渡されたトークンがユーザーの記憶ダイジェストと一致することをチェック!
BCrypt::Password.new(remember_digest) == remember_tokenこのコードは奇妙。bcryptで暗号化されたパスワードをトークンと直接比較している。ということは == 比較する際にダイジェストを復号化しているのか?しかし、bcryptのハッシュは復号化できないはずなので、復号化しているはずがない。そこでbcrypt gemのソースコードを詳しく調べてみると、なんと比較に使っている==演算子が再定義されている。実際の比較をコードで表すと、次のようになっている。
BCrypt::Password.new(remember_digest).is_password?(remember_token)実際の比較では、== の代わりにis_password?という論理値メソッドが使われている。
これを記憶トークンと記憶ダイジェストを比較するauthenticated?メソッドとして定義して、まとめると
app/models/user.rbclass User < ApplicationRecord ・ ・ # 渡されたトークンがダイジェストと一致したらtrueを返す def authenticated?(remember_token) BCrypt::Password.new(remember_digest).is_password?(remember_token) end end*注意*
このメソッドのremember_tokenはローカルメソッドである。
remember_digestはUsersカラムの属性である。 まちがえないようにセッションヘルパーに
userモデル定義のrememberメソッドを呼びだして記憶トークンを作成→データベースにダイジェストを記録→その語cookiesに引数で値(ID)とオプションの期限を与える、といった流れのrememberメソッドを定義。このrememberヘルパーメソッドはsessionsコントローラーのヘルパーメソッドであり、先ほどモデルで定義したuser.rememberメソッドとは違う点に注意。実際に、user.rememberメソッドとは違って引数としてuserを取っている。
app/helpers/sessions_helper.rbmodule SessionsHelper # 渡されたユーザーでログインする def log_in(user) session[:user_id] = user.id end # ユーザーのセッションを永続的にする def remember(user) <---------これ user.remember cookies.permanent.signed[:user_id] = user.id cookies.permanent[:remember_token] = user.remember_token end # 現在ログインしているユーザーを返す (いる場合) def current_user @current_user ||= User.find_by(id: session[:user_id]) end ・ ・ログインのアクション時に組み込む
app/controllers/sessions_controller.rbclass SessionsController < ApplicationController def new end def create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) log_in user remember user redirect_to user else flash.now[:danger] = 'Invalid email/password combination' render 'new' end end def destroy log_out redirect_to root_url end endしかし、今のままだと
# 現在ログインしているユーザーを返す (いる場合) @current_user ||= User.find_by(id: session[:user_id])current_userメソッドでは一時セッションしか扱っていないので、このままでは正常に動作しない。
永続セッションの場合は、session[:user_id]が存在すれば一時セッションからユーザーを取り出し、それ以外の場合はcookies[:user_id]からユーザーを取り出して、対応する永続セッションにログインする必要がある。書き換えるとapp/helpers/sessions_helper.rb# 記憶トークンcookieに対応するユーザーを返す def current_user if (user_id = session[:user_id]) #<--このコードを言葉で表すと、 #「(ユーザーIDにユーザーIDのセッションを代入した結果) ユーザーIDのセッションが存在すれば」 @current_user ||= User.find_by(id: user_id) elsif (user_id = cookies.signed[:user_id]) user = User.find_by(id: user_id) if user && user.authenticated?(cookies[:remember_token]) log_in user @current_user = user end end endユーザーを忘れる
記憶と逆を行う。
user.forgetメソッドによって、user.rememberが取り消される。
具体的には、記憶ダイジェストをnilで更新。app/models/user.rbclass User < ApplicationRecord ・ ・ # ユーザーのログイン情報を破棄する def forget update_attribute(:remember_digest, nil) end end同じ流れ。
Forgetヘルパーメソッドを追加してlog_outヘルパーメソッドから呼び出す。
よく見てみると、forgetヘルパーメソッドではuser.forgetを呼んでからuser_idとremember_tokenのcookiesを削除していることがわかる。app/helpers/sessions_helper.rbmodule SessionsHelper # 渡されたユーザーでログインする def log_in(user) session[:user_id] = user.id end . . . # 永続的セッションを破棄する def forget(user) user.forget cookies.delete(:user_id) cookies.delete(:remember_token) end # 現在のユーザーをログアウトする def log_out forget(current_user) session.delete(:user_id) @current_user = nil end end
- 投稿日:2020-03-22T03:07:05+09:00
ユーザー情報にアイコンとプロフィールを追加する方法を丁寧に解説(rails)
はじめに
こんにちは。今回はSNSなどで普段よく目にするアイコンとプロフィールの実装方法を解説していきます。
Line、Twitter、Instagram、YouTubeなどで目にするアイコンは全て丸い形をしているので、そのようなデザインにする方法も一緒に解説します。このCSSのレイアウト調整が意外と苦戦しました(汗)それにしてもなんでアイコンって正方形じゃなくて丸いんだろう、とふと疑問に思ったので調べてみたのですが、どうやらTwitterなどにある添付画像が四角いのでパッと見でアイコンだと区別がつくように丸くしたようです。
余談はこの辺にして実際の完成イメージからご紹介します。
完成イメージ
今回は編集画面でアイコンを追加、追加したアイコンを詳細画面に表示のみご紹介しますが、
新規登録時にアイコンを追加できるようにしたり、投稿と紐付けてユーザー名の横にアイコンを表示させたりも
今回の実装をベースにすれば容易に実現可能だと思います。容量の関係で画質荒めですが、イメージはこんな感じです。
編集画面では非同期で画像をプレビューし、Updateボタンで詳細画面に遷移させるといった流れになります。それでは行きましょう!
環境
- ruby 2.5.1
- Rails 5.2.3
- mysql
- Haml 5.1.2
- Ruby Sass 3.7.4
前提条件
- deviseを用いてユーザーの新規登録・ログイン・ログアウト・編集・削除・詳細画面の実装済みであること
- Jqueryが使えるようにgemやapplication.jsに必要な記載を追加・実装済みであること
- carrierwave, minimagickをgemで導入済みであること
開発の流れ
- Usersテーブルにアイコンとプロフィールのカラムを追加
- Usersコントローラーにアイコンとプロフィールのカラムを追加
- 画像アップロード用のファイルを作成
- 編集画面のビューファイルにアイコンとプロフィールを追加
- 編集画面のビューに対応するscssファイルでレイアウトを調整
- 非同期で画像プレビューするためにJSファイルを作成
- 詳細画面に表示する用のビュー、レイアウトを調整
手順
1. Usersテーブルにアイコンとプロフィールのカラムを追加
下記のようにターミナルからコマンドを打ちカラムを追加します。
アイコンはファイル名で保存されるので文字列のstring型、プロフィールは不定長文字列のtext型で定義します。ターミナル.$ rails g migration AddImageAndProfileToUsers image:string profile:text $ rails db:migrate2. Usersコントローラーにアイコンとプロフィールのカラムを追加
user_paramsでimageカラムとprofileカラムをpermit内に追加して、データを更新できるようにしておきましょう。
showアクションでアイコンやプロフィールを表示するためにそれぞれ@image, @profileにデータを入れてあげます。
editアクションでも既にimageにデータが入っている場合に表示させるので@imageを追加してあげます。
ここでプロフィールやその他のデータがeditアクション内に無いのは後ほど手順5で解説します。users_controller.rbclass UsersController < ApplicationController def edit user = User.find(params[:id]) @image = user.image end def update if current_user.update(user_params) redirect_to user_path(current_user) else render :edit end end def show user = User.find(params[:id]) @id = user.id @name = user.name @image = user.image @profile = user.profile @videos = user.videos.order("created_at DESC") end private def user_params params.require(:user).permit(:id, :name, :email, :image, :profile) end end機能的に無くても動作には影響しませんが、ついでにストロングパラメーターにimageカラムを追加してあげましょう。
ストロングパラメーターは意図しない登録・更新を防ぐ、いわば脆弱性対策です。application_controller.rbclass ApplicationController < ActionController::Base (省略) def configure_permitted_parameters devise_parameter_sanitizer.permit(:sign_up, keys: [:name, :image]) end end3. 画像アップロード用のファイルを作成
画像をアップロードするためにCarrierWave, MiniMagickの仕組みを使います。
ターミナルで下記を入力することで、uploadersフォルダ下にファイルが作成されます。ターミナル.$ rails g uploader Image作成されたファイルを開き、上から4行目あたりにある下記の1行がコメントアウトされているので外してください。
uploaders/image_uploader.rbclass ImageUploader < CarrierWave::Uploader::Base (省略) include CarrierWave::MiniMagick (省略) endImageUploaderをuserモデルに追加することで先ほど作成したImageUploaderが適用されます。
追加で、これは好みですがプロフィール文の文字数を最大250文字に制限しています。model/user.rbclass User < ApplicationRecord (省略) validates :profile, length: { maximum: 250 } mount_uploader :image, ImageUploader end4. 編集画面のビューファイルにアイコンとプロフィールを追加
ここでかなり苦戦しました。
何故苦戦したかと言うと、アイコンの枠>既に画像が存在する場合の表示>新しく選択した画像>ファイル選択用のボタンといった具合に、アイコンの部分だけでも4つの要素を用意しなければならず、その関係性を崩さないようにCSS、JQueryで調整しなければならなかったからです。他に簡略的に書ける良い方法がある場合は下のコメント欄にてご指摘ください。users/edit.html.haml(省略) = form_for(current_user) do |f| //アイコン追加部分 .account-page__inner--icon .account-page__inner--icon__label .account-page__inner--icon__input - if @image.present? = image_tag @image.url, class: 'account-page__inner--icon__input__image', width: '100%' .account-page__inner--icon__input__image2 = f.file_field :image, class: 'account-page__inner--icon__input__btn', id: 'upload-icon' .account-page__inner--user-form (省略) // プロフィール追加部分 .field .field-label = f.label :profile .field-input = f.text_area :profile, autocomplete: 'off' (省略)アイコンの解説
form_forを用いてfile_fieldでファイル選択用のフォームを用意します。
form_forについて忘れちゃった・詳しく知りたいという方はこちら(https://pikawaka.com/rails/form_for)要素を整理するとこのようになります。
.account-page__inner--icon__input(アイコンの枠)
.account-page__inner--icon__input__image(既に画像が存在する場合の表示)
.account-page__inner--icon__input__image2(新しく選択した画像)
.account-page__inner--icon__input__btn(ファイル選択用のボタン)手順2のUsersコントローラーのshowアクションでimageだけ@image=user.imageとしていた理由ですが、既に画像が存在する場合(if @image.present?)という条件をつけ、ある場合はその画像を表示する必要があるためです。プロフィールについてはtext_area部分でカラムに対して直接書き込むだけで、アイコンのような特殊な表示切り替えを行っていないため、わざわざ変数に置き換えてやる必要がありません。
画像の表示にはimage_tagを使用し、@image.urlで画像データを呼び出します。width: '100%'にすることでアイコンの枠(幅:180px, 高さ:180px)に対して幅いっぱいとなるので画像がぴったり真ん中に来るようになります。
JQueryで非同期で画像プレビューさせるのはファイル選択用のボタンを発火源とするので、
ここにid名:upload--iconをつけておきます。Jqueryの実装については手順6で後ほど解説します。プロフィールの解説
これは簡単ですね。
複数行のテキストを保存したいのでtext_areaを使用して、autocomplete: 'off'で入力候補の表示をオフにするだけです。
ついでにlabelで入力フォームの上にprofileという文字を表示しておきましょう。5. 編集画面のビューに対応するscssファイルでレイアウトを調整
さて、いよいよレイアウトの調整に入っていくわけですが、非常に複雑ですのでポイント毎に順を追って解説していきます。
アイコン枠を丸くする方法
幅と高さを同じサイズにし、border-radius: 100pxで要素の角が取れて丸くなります。
このinputは.account-page__inner--icon__input(アイコンの枠)です。user.scss&__input { width: 180px; height: 180px; border-radius: 100px; }親要素からはみ出した部分を非表示にする
下記を追加しないとアイコンの枠は丸くしたものの、追加する画像が四角い場合、親要素の丸からはみ出て表示されてしまいます。
user.scss&__input { overflow: hidden; }アイコン枠内に、既に画像が存在する場合の表示、新しく選択した画像、ファイル選択用のボタンを全て入れ込む
少し長いですが内容としては簡単です。親要素であるinputにposition: rerative、小要素全てにposition: absoluteを記述して、あとは配置を調整。btn部分は表示する必要がないのでopacity: 0で透明化し、cursor: pointerでマウスホバー時にカーソルマークが変わるようにしています。btnに対してopacity: 0では無くdisplay: noneするとボタンが無効化されてしまうので注意してください。
btn部分で幅と高さを1000pxにして親要素に対してright: 0;にしているのには理由があります。このやり方は正攻法ではないと思いますが、私なりに調べ模索した方法になります。このような記述にしないといけなくなった問題は下記の通りです。
file_fieldでファイル選択用のボタンが追加されますが、「ファイルを選択」という部分にはcursor: pointerが有効にならない。一方で「選択されていません」という文字部分にはcursor: pointerが適用されるといった謎な仕様になっています。そこでサイズをめちゃくちゃ大きくし親要素に対してright: 0とすることでカーソルが適用される「選択されていません」部分だけでアイコン枠を埋めてしまおうといった魂胆です。良い子はマネしないで下さい。
user.scss&__input { position: relative; &__image { position: absolute; top: 0; left: 0; } &__image2 { position: absolute; top: 0; left: 0; } &__btn { position: absolute; top: 0; right: 0; width: 1000px; height: 1000px; opacity: 0; cursor: pointer; }プロフィールは大したことないですが、一応載せておきます
user.scsstextarea { margin: auto; width: 400px; height: 150px; background-color: black; color: white; border: solid 1px rgb(150, 150, 150); border-radius: 5px; padding: 10px; }6. 非同期で画像プレビューするためにJSファイルを作成
ファイル名は○○.jsなら何でも良いです。
先ほど作成した編集画面のビューファイルの中でファイル選択用のボタンを発火源とするため、id名:upload-iconを追加しました。この発火源を\$fileField = $('#upload-icon')と書きます。画像を格納したい部分.account-page__inner--icon_input_image2を$previewに代入し、emptyで一旦空に、その後appendで画像を追加します。ここでimgタグを用いていますが、動画素材の場合はvideoタグを指定してやれば動画のプレビューも作ることができます。細かいところは私もまだまだ勉強中なので解説できませんが、流れとしてはこのような感じです。app/assets/javascripts/icon_preview.js$(function(){ $fileField = $('#upload-icon') $($fileField).on('change', $fileField, function(e) { file = e.target.files[0] reader = new FileReader(), $preview = $(".account-page__inner--icon__input__image2"); reader.onload = (function(file) { return function(e) { $preview.empty(); $preview.append($('<img>').attr({ src: e.target.result, width: "100%", class: "preview", title: file.name })); }; })(file); reader.readAsDataURL(file); }); });7. 詳細画面に表示する用のビュー、レイアウトを調整
最後の工程です!ここまででアイコン・プロフィールの追加は完了しているので、追加したこれらをユーザー詳細画面に表示していきましょう。手順2でコントローラーは既に追記済みなのでビューファイルに書くだけです。
アイコンは編集画面のビューファイル同様に画像が存在する場合(-if @image.present?)という条件をつけてあげないと、画像がない状態で@imageを呼び出そうとしてエラーになります。
また、text_areaで追加したプロフィール文の改行を受付けるためにはsimple_formatをつけます。これを付けずに@profileだけ記載すると、編集画面で入力した際の改行が全て無効となってしまいます。
users/show.html.haml.content .userbox .userbox__top .userbox__top__left // アイコン表示部分 .userbox__top__left__icon -if @image.present? = image_tag @image.url, width: '100%' (省略) // プロフィール表示部分 .userbox__bottom = simple_format(@profile) (省略)レイアウトは手順5を参考に実装してください。アイコン枠、現在の画像のみの表示なので難しくないですね。
これで完成イメージのようなアイコン・プロフィールが作れたかと思います!お疲れ様でした!
参考
https://qiita.com/shlia/items/d4fdd952b38d4140062d
https://qiita.com/akr03xxx/items/82ba45f7ef4fdbd5c702
- 投稿日:2020-03-22T01:03:06+09:00
Railsプロジェクト作成手順(Vue版)
環境
- macOS Mojave 10.14.6
- rbenv 1.1.2
- Homebrew 2.2.10
- ruby 2.7.0
- Rails 6.0.2.2
方法
1. ディレクトリを作成する。
$ mkdir practice_project2. 作ったディレクトリに移動する。
$ cd practice_project3. Gemfileを生成する。
$ bundle init4. Gemfileを編集する。
Gemfile# frozen_string_literal: true source "https://rubygems.org" git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } gem "rails" ←コメントアウトを外す5. Gemをインストールする.
bundle install --path vendor/bundle6. Railsプロジェクトを生成する。
$ bundle exec rails new . -d mysql --skip-test --webpack=vue Overwrite /Users/koji/practice/vue_practice/Gemfile? (enter "h" for help) [Ynaqdhm] Yおわりに
『もっと簡単にできる方法あるよーーー!』
『ここわかりにくいよーーー!』
『ここ間違っているよーーー!』
等あればコメントいただけるとめちゃくちゃ嬉しいです!!!
twiiter → https://twitter.com/jiko797torayo
- 投稿日:2020-03-22T01:03:06+09:00
Railsプロジェクト作成手順
環境
- macOS Mojave 10.14.6
- rbenv 1.1.2
- Homebrew 2.2.10
- ruby 2.7.0
- Rails 6.0.2.2
方法
1. ディレクトリを作成する。
$ mkdir practice_project2. 作ったディレクトリに移動する。
$ cd practice_project3. Gemfileを生成する。
$ bundle init4. Gemfileを編集する。
Gemfile# frozen_string_literal: true source "https://rubygems.org" git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } gem "rails" ←コメントアウトを外す5. Gemをインストールする.
bundle install --path vendor/bundle6. Railsプロジェクトを生成する。
$ bundle exec rails new . -d mysql --skip-test Overwrite /Users/koji/practice/vue_practice/Gemfile? (enter "h" for help) [Ynaqdhm] Y(Vueを使う場合)
$ bundle exec rails new . -d mysql --skip-test --webpack=vue Overwrite /Users/koji/practice/vue_practice/Gemfile? (enter "h" for help) [Ynaqdhm] Yおわりに
『もっと簡単にできる方法あるよーーー!』
『ここわかりにくいよーーー!』
『ここ間違っているよーーー!』
等あればコメントいただけるとめちゃくちゃ嬉しいです!!!
twiiter → https://twitter.com/jiko797torayo