- 投稿日:2020-05-29T23:40:53+09:00
[JavaScript]入力フォーム漏れがあった時のアラート表示方法
はじめに
RailsでECサイトを作成しています。
カートに商品を追加する時に、数量選択がなかった場合に以下のようなポップアップを表示させたいと思いました。今回はその備忘録です。
補足情報
- Ruby 2.5.1
- Ruby on Rails 5.2.4.2
フォームの内容
上記写真のページは以下のようなViewファイルになっています。
.showMain .showMain__Boxes .showMain__Boxes__leftBox = image_tag @product.image, width: 450, height: 450, class: "showMain__Boxes__leftBox__image" .showMain__Boxes__rightBox = form_with url: add_item_carts_path, local: true, name: "formForCart" do |f| .showMain__Boxes__rightBox__name = @product.name .showMain__Boxes__rightBox__namejap = @product.namejap .showMain__Boxes__rightBox__description = @product.description .showMain__Boxes__rightBox__orderQuantity = f.label :quantity, "数量", class: "showMain__Boxes__rightBox__orderQuantity__title" = f.select :quantity, stock_array_maker(@stock), {include_blank: "選択してください"}, {class: "showMain__Boxes__rightBox__orderQuantity__form"} .showMain__Boxes__rightBox__line .showMain__Boxes__rightBox__line__price = "本体価格 : ¥#{convertToJPY(@product.price)}" .showMain__Boxes__rightBox__line__fav - if user_signed_in? && current_user - if @product.bookmark_by?(current_user) = link_to product_bookmark_path(@product.id), class: "showMain__Boxes__rightBox__line__fav__btn.fav", method: :delete, remote: true do %i.fas.fa-star.star - else = link_to product_bookmarks_path(@product.id), class: "showMain__Boxes__rightBox__line__fav__btn.fav", method: :post, remote: true do %i.far.fa-star.star-o (お気に入りに追加) .showMain__Boxes__rightBox__line .showMain__Boxes__rightBox__addCart = f.hidden_field :product_id, value: @product.id = f.hidden_field :cart_id, value: @cart.id = f.submit "Add to Cart", {class: "showMain__Boxes__rightBox__addCart__btn", id: "addToCart"}JSの記載方法
submitタグ、quantityのselectタグの2つのノードを取得しています。
$(function(){ $("#addToCart").on('click', function(e){ if(document.getElementById('quantity').value === ""){ alert("数量を選択してください。"); return false; }else{ return true; } }) });フォームのsubmitボタンが押された時に発火するように設定をして、= f.select :quantity ~ のidのvalueがない場合にfalseを返し、アラートで警告します。
返り値がtrueならサブミットし、falseならサブミットしません。
因みに、"return false"がないと、ポップアップが出ますが、次のページに進んでしまいます。上記JSは以下の方法でもOK
$(function(){ $("#addToCart").on('click', function(e){ if(!(document.getElementById('quantity').value)){ alert("数量を選択してください。"); return false; }else{ return true; } }) });参考サイト
- 投稿日:2020-05-29T23:09:20+09:00
【初心者】この記事を見ながらRubyでAtCoderの問題を解こう!
はじめに
RubyでAtCoderをはじめました。
これが面白くてどハマりしてしまい、プログラミングというものの面白さが心の底から分かった瞬間でした。さて、僕はプログラマではないのですが、
AtCoderを始めるにあたって、知っておいた方が良いTIPSをまとめました。
以下を参照にAtCoderに挑むことにより、なんとか戦えると思います。AtCoderのアルゴリズムの勉強を通して
Rubyの面白さ、プログラミングの面白さを伝わるといいと思います。文字列を分割する(失敗例)
gets.split('') aaa => ["a", "a", "a", "\n"]/nが入っちゃってますね。
文字列を分割する(成功例)
gets.chomp.split('') aaa => ["a", "a", "a"]chompを入れたらいい感じに分割できました。
数字を出力する方法(失敗例)
gets.chomp 1 => "1"出力が文字列になっていますね。
数字を出力する方法(成功例)
gets.to_i 1 => 1getsは文字列で出力されます。
数字にした配列を出力する方法
gets.split.map(&:to_i) 1 2 3 => [1, 2, 3]入力は、数字ごとにスペースを入れてください。
入れ子の配列を複数作る方法
3.times.map{gets.split.map(&:to_i)} 1 2 3 1 2 3 1 2 3 => [[1, 2, 3], [1, 2, 3], [1, 2, 3]]複数の数字が入った入れ子構造の配列ができました。
1つの入力方法+それをN回繰り返して、入力値の配列を作る方法
N = gets.to_i 3 => 3 b = N.times.map{gets.to_i} 1 2 3 => [1, 2, 3]偶数か奇数か判別する方法
a = 2 => 2 a.even? => true a.odd? => falseeven?は偶数か判別して
odd?は奇数か判別します。変数の長さを確認する
a = "aaaa" => "aaaa" a.size => 4 a.length => 4 a = [1,2] => [1, 2] a.size => 2 a.length => 2sizeとlengthは一緒ですね。
また、文字列も配列もいけます。三項演算子をかく
if true puts "Yes" else puts "No" end #上記は一行で書ける puts true ? "Yes": "No"これはA問でよく見かけるので覚えておきましょう!
配列の中の最大値、最小値を出力する
a = [0, 1 ,2, 3] => [0, 1, 2, 3] a.min => 0 a.max => 3配列の中で重複をなくす
a = [1, 1, 2, 2, 3] => [1, 1, 2, 2, 3] a.uniq => [1, 2, 3]配列の中の合計値を出力する
a = [1, 1, 2, 2, 3] => [1, 1, 2, 2, 3] a.sum => 9 a.inject(:+) => 9sumとuniqどっちでも良いです。
配列の末尾に追加する
a = [] => [] a << 1 => [1] a << 2 => [1, 2] a << 3 => [1, 2, 3]配列の末尾を指定する
a = [1, 1, 2, 2, 3] => [1, 1, 2, 2, 3] a[-1] => 3指定した番号の配列を取り出す
#5番目を取り出す n = 5 => 5 a = [1, 1, 2, 2, 3] => [1, 1, 2, 2, 3] a[n] => nil a[n - 1] => 3配列は0から始まるので-1しなくてはいけませんね。
配列のある値になったときにループから抜ける
a = [1, 2, 3, 4] a.each do |i| if i == 2 puts i break end endちなみにBreakとReturnの違いは、ループから抜けるかメソッドから抜けるかの違いです。
Returnはメソッドの中でしか使えません。ある値の時にカウントする方法
a = [1, 2, 3, 4] count = 0 a.each do |i| if i == 1 count += 1 end end puts count =>1ある値で割り切れる時の条件分岐
k = 2 a, b = [1, 3] (a..b).each do |c| if c % k == 0 puts 'OK' break end if c == b puts 'NG' end endtrueまでループし続ける方法
x = 2 yen = 100 count = 0 while yen > x do yen += yen / 100 count = count + 1 end puts countwhileはtrueであり続けかぎりループします。
条件式が必要なく、自分でBreakで
抜けて終了する場合は、loop do ...endを使いましょう!
もちろんwhile true do ...endでも良いです。putsで出力するかpで出力するかprintfで出力するか
結論を先に言うとputsが良いです。
pは""がついてしまって答えが合いません。a = "aaa" => "aaa" p a "aaa" => "aaa" puts a aaa => nil printf a aaa=> nil
- 投稿日:2020-05-29T23:09:20+09:00
【初心者】記事を見ながらRubyでAtCoderの問題を解こう!
はじめに
RubyでAtCoderをはじめました。
これが面白くてどハマりしてしまい、プログラミングというものの面白さが心の底から分かった瞬間でした。さて、僕はプログラマではないのですが、
AtCoderを始めるにあたって、知っておいた方が良いTIPSをまとめました。
以下を参照にAtCoderに挑むことにより、なんとか戦えると思います。AtCoderのアルゴリズムの勉強を通して
Rubyの面白さ、プログラミングの面白さを伝わるといいと思います。文字列を分割する(失敗例)
gets.split('') aaa => ["a", "a", "a", "\n"]/nが入っちゃってますね。
文字列を分割する(成功例)
gets.chomp.split('') aaa => ["a", "a", "a"]chompを入れたらいい感じに分割できました。
数字を出力する方法(失敗例)
gets.chomp 1 => "1"出力が文字列になっていますね。
数字を出力する方法(成功例)
gets.to_i 1 => 1getsは文字列で出力されます。
数字にした配列を出力する方法
gets.split.map(&:to_i) 1 2 3 => [1, 2, 3]入力は、数字ごとにスペースを入れてください。
入れ子の配列を複数作る方法
3.times.map{gets.split.map(&:to_i)} 1 2 3 1 2 3 1 2 3 => [[1, 2, 3], [1, 2, 3], [1, 2, 3]]複数の数字が入った入れ子構造の配列ができました。
1つの入力方法+それをN回繰り返して、入力値の配列を作る方法
N = gets.to_i 3 => 3 b = N.times.map{gets.to_i} 1 2 3 => [1, 2, 3]偶数か奇数か判別する方法
a = 2 => 2 a.even? => true a.odd? => falseeven?は偶数か判別して
odd?は奇数か判別します。変数の長さを確認する
a = "aaaa" => "aaaa" a.size => 4 a.length => 4 a = [1,2] => [1, 2] a.size => 2 a.length => 2sizeとlengthは一緒ですね。
また、文字列も配列もいけます。三項演算子をかく
if true puts "Yes" else puts "No" end #上記は一行で書ける puts true ? "Yes": "No"これはA問でよく見かけるので覚えておきましょう!
配列の中の最大値、最小値を出力する
a = [0, 1 ,2, 3] => [0, 1, 2, 3] a.min => 0 a.max => 3配列の中で重複をなくす
a = [1, 1, 2, 2, 3] => [1, 1, 2, 2, 3] a.uniq => [1, 2, 3]配列の中の合計値を出力する
a = [1, 1, 2, 2, 3] => [1, 1, 2, 2, 3] a.sum => 9 a.inject(:+) => 9sumとinjectどっちでも良いです。
配列の末尾に追加する
a = [] => [] a << 1 => [1] a << 2 => [1, 2] a << 3 => [1, 2, 3]配列の末尾を指定する
a = [1, 1, 2, 2, 3] => [1, 1, 2, 2, 3] a[-1] => 3指定した番号の配列を取り出す
#5番目を取り出す n = 5 => 5 a = [1, 1, 2, 2, 3] => [1, 1, 2, 2, 3] a[n] => nil a[n - 1] => 3配列は0から始まるので-1しなくてはいけませんね。
配列のある値になったときにループから抜ける
a = [1, 2, 3, 4] a.each do |i| if i == 2 puts i break end endちなみにBreakとReturnの違いは、ループから抜けるかメソッドから抜けるかの違いです。
Returnはメソッドの中でしか使えません。ある値の時にカウントする方法
a = [1, 2, 3, 4] count = 0 a.each do |i| if i == 1 count += 1 end end puts count =>1ある値で割り切れる時の条件分岐
k = 2 a, b = [1, 3] (a..b).each do |c| if c % k == 0 puts 'OK' break end if c == b puts 'NG' end endtrueまでループし続ける方法
x = 2 yen = 100 count = 0 while yen > x do yen += yen / 100 count = count + 1 end puts countwhileはtrueであり続けかぎりループします。
条件式が必要なく、自分でBreakで
抜けて終了する場合は、loop do ...endを使いましょう!
もちろんwhile true do ...endでも良いです。putsで出力するかpで出力するかprintfで出力するか
結論を先に言うとputsが良いです。
pは""がついてしまって答えが合いません。a = "aaa" => "aaa" p a "aaa" => "aaa" puts a aaa => nil printf a aaa=> nil
- 投稿日:2020-05-29T22:39:18+09:00
【Rails6+Bootstrap4】コピペだけ!Bootstrap4をRails6で使うための手順完全版
環境
ruby 2.6.4
Rails 6.0.2.2
rbenv 1.1.2
bootstrap 4.3.1
mysql2 0.5.3
やりたいこと
コピペだけでBootstrap4をRails6で使える手順を共有します
手順通りに進めれば魔法のように導入できちゃいますよ
完全手順
1. bootstrap 4.3.1を追加
$ yarn add bootstrap@4.3.1 jquery popper.js2. application.jsに下記コード追加
app/javascript/packs/application.jsimport "bootstrap"3. webpack/environment.jsを修正
config/webpack/environment.jsconst { environment } = require('@rails/webpacker') const webpack = require("webpack") environment.plugins.append("Provide", new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery', Popper: ['popper.js', 'default'] })) module.exports = environment4. application.cssを修正
app/assets/stylesheets/application.css/* * This is a manifest file that'll be compiled into application.css, which will include all the files * listed below. * * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's * vendor/assets/stylesheets directory can be referenced here using a relative path. * * You're free to add application-wide styles to this file and they'll appear at the bottom of the * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS * files in this directory. Styles in this file should be added after the last require_* statement. * It is generally better to create a new file per style scope. * *= require bootstrap #add this line *= require_tree . *= require_self */
5. custom.css.scssを作成&下記コード追加
app/assets/stylesheets/custom.css.scss@import 'bootstrap/dist/css/bootstrap';あとはあなた好みのカスタマイズをするだけです!
参考文献
【Rails6/Bootstrap4】Bootstrap4をRails6で使うための手順完全版
https://www.twinzlabo.com/rails6_bootstrap4_install/【Vue/BootstrapVueコピペだけ】シンプルなチャットアプリ(掲示板) の作成方法を徹底解説
https://www.twinzlabo.com/vue-chatapp-create/
- 投稿日:2020-05-29T22:39:18+09:00
【Rails6+Bootstrap4】コピペだけでBootstrap4をRails6で使うための手順完全版
環境
ruby 2.6.4
Rails 6.0.2.2
rbenv 1.1.2
bootstrap 4.3.1
mysql2 0.5.3
やりたいこと
コピペだけでBootstrap4をRails6で使える手順を共有します
手順通りに進めれば魔法のように導入できちゃいますよ
完全手順
1. bootstrap 4.3.1を追加
$ yarn add bootstrap@4.3.1 jquery popper.js2. application.jsに下記コード追加
app/javascript/packs/application.jsimport "bootstrap"3. webpack/environment.jsを修正
config/webpack/environment.jsconst { environment } = require('@rails/webpacker') const webpack = require("webpack") environment.plugins.append("Provide", new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery', Popper: ['popper.js', 'default'] })) module.exports = environment4. application.cssを修正
app/assets/stylesheets/application.css/* * This is a manifest file that'll be compiled into application.css, which will include all the files * listed below. * * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's * vendor/assets/stylesheets directory can be referenced here using a relative path. * * You're free to add application-wide styles to this file and they'll appear at the bottom of the * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS * files in this directory. Styles in this file should be added after the last require_* statement. * It is generally better to create a new file per style scope. * *= require bootstrap #add this line *= require_tree . *= require_self */
5. custom.css.scssを作成&下記コード追加
app/assets/stylesheets/custom.css.scss@import 'bootstrap/dist/css/bootstrap';あとはあなた好みのカスタマイズをするだけです!
参考文献
【Rails6/Bootstrap4】Bootstrap4をRails6で使うための手順完全版
https://www.twinzlabo.com/rails6_bootstrap4_install/【Vue/BootstrapVueコピペだけ】シンプルなチャットアプリ(掲示板) の作成方法を徹底解説
https://www.twinzlabo.com/vue-chatapp-create/
- 投稿日:2020-05-29T21:38:58+09:00
【Rails】セレクトボックスを活用した多階層構造データの表示について
経緯
とあるプログラミングスクール受講生です。
存知の方も多いでしょう「フリマクローンサイト」作成にあたり、出品カテゴリーをajaxで実装しましたので、自分の頭の整理を兼ねてまとめていきたいと思います。なお、カテゴリーの設定は「ancestry」というGemを使用しております。
今回の記事につきましては、カテゴリー設定後を想定しております。※説明が必要ない方はコードのみ追ってもらえれば実装できると思います。
完成イメージ
コード
ビュー(items.new.html.haml).form-title =f.label "カテゴリ" .form-title__required %label 必須 .form-input-select = f.select :category, @category_parent_array, {}, {class: 'listing-select-wrapper__box--select', id: 'parent_category'} .listing-product-detail__category
- 今回、重要なのは下3行のみです。他はご自由に。
- = f.select :category, @category_parent_array, {}, {class: 'listing-select-wrapper__box--select', id: 'parent_category'} の {} は超重要ですので消さないでください。
→ 軽く触れておきます。{}の部分が2つありますね。一つ目が「オプション」の記載、二つ目(今回記載がある方)が「HTMLオプション」となります。
「オプション」を設定しない場合は空欄でも{}の記載しておかないと、「HTMLオプション」が反映されません。コントローラー(items_controller.rb)def new @category_parent_array = ["---"] Category.where(ancestry: nil).each do |parent| @category_parent_array << parent.name end end def get_category_children @category_children = Category.find_by(name: "#{params[:parent_name]}", ancestry: nil).children end def get_category_grandchildren @category_grandchildren = Category.find("#{params[:child_id]}").children endデモを見てもらうと、カテゴリーが三段階で表示されているのが確認できると思います。
・ 一段目の表示がnew
・ 二段目の表示がget_category_children
・ 三段目の表示がget_category_grandchildren
となっております。複雑な部分はnewのコードでしょうか。
@category_parent_array に "---" しか入っていない配列を代入していますね。
次の行のeach文で先ほど設定した@category_parent_arrayの配列にカテゴリを1件ずつ代入しています。
(ancestry: nil)につきましては、「ancestry」でカテゴリーを設定した後に保存データを確認してみてください。意味がわかると思います。二段落目、三段落目の記載はajaxでの処理となりますので、アクションを分けています。
params[:parent_name]、params[:child_id]については、ajaxでJavaScriptから飛んでくる値です。
あとで設定しますので、覚えておきましょう。routes.rbresources :items do collection do get 'get_category_children', defaults: { format: 'json' } get 'get_category_grandchildren', defaults: { format: 'json' } end end先ほどコントローラーで出てきた2つのajax用アクションを設定しています。
itemsにネストしております。
軽くcollectionについて簡単すると、routingにidが付くのがmember、付かないのがcollectionです。
今回は、「個(id)」を特定する必要がないので、collectionですね。コントローラーは基本的に処理をビューに返すのですが、
defaults: { format: 'json' }の設定をしておくと、デフォルトでjsonファイルに処理を返すようになります。
(コントローラーでrespond_toを使用してjsonファイルに振り分ける必要が無くなります。)get_category_children.json.jbuilderjson.array! @category_children do |child| json.id child.id json.name child.name endget_category_grandchildren.json.jbuilderjson.array! @category_grandchildren do |grandchild| json.id grandchild.id json.name grandchild.name endファイルの場所、間違えないでくださいね!!ビューと同じ場所に格納します。
コントローラーで二段落目、三段落目のアクション処理を行うと、このjsonファイルに飛びます。
(routes.rbで先ほど設定しましたね。)
コントローラーで設定した変数をここでajax用データに変換している訳ですね。ちなみに、json.array!は配列形式のデータをコントローラーから受け取る時に設定します。
ajax処理の流れは、
ビュー(カテゴリー選択)→JavaScript(発火)→コントローラー(処理)→json.jbuilder(データ変換)→JavaScript(処理)→ビュー
の繰り返し(と認識しています)。さて、最後にJavaScriptのお出ましです。
JS(items.js)$(function)(){ //子カテゴリー、孫カテゴリーのセレクトボックスの選択肢 function appendOption(category){ var html = `<option value="${category.name}" datacategory="${category.id}">${category.name}</option>`; return html; } //子カテゴリーのビュー作成 function appendChildrenBox(insertHTML){ var childSelectHtml = ''; childSelectHtml = `<div class='listing-select-wrapper__added' id= 'children_wrapper'> <div class='listing-select-wrapper__box'> <select class="listing-select-wrapper__box--select" id="child_category" name="category_id"> <option value="---" data-category="---">---</option> ${insertHTML} <select> </div> </div>`; $('.listing-product-detail__category').append(childSelectHtml); } //孫カテゴリーのビュー作成 function appendGrandchildrenBox(insertHTML){ var grandchildSelectHtml = ''; grandchildSelectHtml = `<div class='listing-select-wrapper__added' id= 'grandchildren_wrapper'> <div class='listing-select-wrapper__box'> <select class="listing-select-wrapper__box--select" id="grandchild_category" name="category_id"> <option value="---" data-category="---">---</option> ${insertHTML} </select> </div> </div>`; $('.listing-product-detail__category').append(grandchildSelectHtml); } //親カテゴリーが選択された時の処理(子カテゴリーの表示) $("#parent_category").on('change', function(){ //選択された親カテゴリーの値を取得 var parentCategory = document.getElementById('parent_category').value; //選択された親カテゴリーが"---"(初期設定)のままだとfalse、変わっているとtrue if (parentCategory != "---"){ $.ajax({ url: 'get_category_children', type: 'GET', //コントローラーに飛ばす値です。 data: { parent_name: parentCategory }, dataType: 'json' }) .done(function(children){ //まず、既に表示されている子、孫カテゴリーを削除 $('#children_wrapper').remove(); $('#grandchildren_wrapper').remove(); //insertHTMLという変数にカテゴリーのセレクトボックスの選択肢を入れる。(一番最初の段落で設けた変数) var insertHTML = ''; children.forEach(function(child){ insertHTML += appendOption(child); }); //2段落目で設定した子カテゴリーのビューの呼び出し appendChildrenBox(insertHTML); }) .fail(function(){ alert('カテゴリー取得に失敗しました'); }) }else{ $('#children_wrapper').remove(); $('#grandchildren_wrapper').remove(); } }); //子カテゴリーが選択された時の処理(孫カテゴリーの表示) $('.listing-product-detail__category').on('change', '#child_category', function(){ var childId = $('#child_category option:selected').data('category'); if (childId != "---"){ $.ajax({ url: 'get_category_grandchildren', type: 'GET', data: { child_id: childId }, dataType: 'json' }) .done(function(grandchildren){ if(grandchildren.length != 0) { $('#grandchildren_wrapper').remove(); $('#size_wrapper').remove(); $('#brand_wrapper').remove(); var insertHTML = ''; grandchildren.forEach(function(grandchild){ insertHTML += appendOption(grandchild); }); appendGrandchildrenBox(insertHTML); } }) .fail(function(){ alert('カテゴリー取得に失敗しました'); }) }else{ $('#grandchildren_wrapper').remove(); $('#size_wrapper').remove(); $('#brand_wrapper').remove(); } }); });はい。言いたいことはわかります。私、エスパーですから。
そんな皆さんに私から頑張れという便利な言葉を送ります。ここにつきましてはあまりに長いので、コードにコメントアウトで処理の説明をしています。
イマイチわかりにくかったら各自調べてもらえればと思います。だらだらと長い記事を最後まで読んでいただき、ありがとうございました。
○○キャンプの受講生はLGTM必須で。参考とさせていただいたサイト
https://qiita.com/ATORA1992/items/bd824f5097caeee09678
@ATORA1992様(とてもわかりやすい記事でした。ありがとうございました!!)
- 投稿日:2020-05-29T21:34:18+09:00
【Ruby on rails】rails db:migrate:resetコマンドでFATAL: Listen error: unable to monitor directories for changes.と表示される。【Rails tutorial】
エラーについて
以下コマンド実行時にエラーが発生。
rails db:migrate:resetFATAL: Listen error: unable to monitor directories for changes. Visit https://github.com/guard/listen/wiki/Increasing-the-amount-of-inotify-watchers for info on how to fix this.対処法
調べてみると、inotifyのインスタンス上限を変更することで対処可能なようです。
catで以下ファイルについて確認。cat /proc/sys/fs/inotify/max_user_instances128128から524288に変更していきます。
設定ファイルに追記します。
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
fs.inotify.max_user_watches=524288追記したファイルを反映します。
sudo sysctl -p再びエラーだったコマンドを実行。
rails db:migrate:reset無事にコマンドが通りました!
- 投稿日:2020-05-29T21:25:08+09:00
Ruby ハッシュについて
ハッシュとは
ハッシュとは、キーと値の組み合わせでデータを管理するオブジェクトです。
配列は複数の値を並べて管理するのに対して、ハッシュはそれぞれの値にキーと呼ばれる名前をつけて管理します。ハッシュの作成
ハッシュを作成する場合は、以下のような構文(ハッシュリテラル)を使用します。
index.rb#キーと値の組み合わせを2つ格納するハッシュ {キー1 => 値1, キー2 => 値2}(例)
キーと値の間は、=>で繋ぎます。要素と要素はコンマ(,)で区切ります。index.rb{"fruit" => "apple", "color" => "red}ハッシュは、Hashクラスのオブジェクトです。
コンソール上で空のハッシュ{}を作成し、そのクラス名を確認すると、Hashとなっていることがわかります。{}.class
結果
=> Hashハッシュを変数に代入
ハッシュを変数に代入できます。
(例)index.rbfruit = {"apple" => "red", "lemon" => "yellow", "melon" => "green"} puts fruit結果
{"apple" => "red", "lemon" => "yellow", "melon" => "green"}要素の出力
ハッシュの各要素の値は、対応するキーを使って出力することができます。
(例)
index.rbfruit = {"apple" => "red", "lemon" => "yellow", "melon" => "green"} puts fruit["apple"]結果
red
- 投稿日:2020-05-29T21:24:37+09:00
マイグレーション入門
マイグレーション入門
マイグレーションとは
text
マイグレーションは、データベーススキーマの継続的な変更を、簡単に行なうための便利な手法です。マイグレーションではRubyのDSLを使っているので、生のSQLを作成する必要がなく、スキーマへの変更をデータベースの種類に依存せずに済みます。なるほど本来であればテーブルを追加したときや、テーブルに対して属性を追加した時などは
SQLを作成する必要がありますよね。CREATE TABLE HOGE
こんな感じで生のSQLを書いてデータベースへの変更をするがそれをする必要がなくなります。
またデータベースの種類に依存しません。
なんとなく使ってたけどすごいよねー。マイグレーションの例
productsというテーブルを追加する例です。
nameというstringカラムと、descriptionというtextカラムが含まれています。主キーはidという名前で暗黙に追加されます。idはActive Recordモデルにおけるデフォルトの主キーです。
ruby.rbclass CreateProducts < ActiveRecord::Migration[5.0] def change create_table :products do |t| t.string :name t.text :description t.timestamps end end endこのマイグレーションを実行することでテーブルが生成されます。
またロールバックをすることでこのテーブルを削除することもできます。マイグレーションを作成
rails generate migration AddPartNumberToProducts part_number:string上記のコードを実行すると以下のようなマイグレーションファイルが生成されます。
productsに対してカラムを追加するという内容になっています。ruby.rbclass AddPartNumberToProducts < ActiveRecord::Migration[5.0] def change add_column :products, :part_number, :string end endDBに反映される
これまでのマイグレーションファイルをDBに反映させます。
#実行 rails db:migrate#ロールバック rails db:rollback以上のようにしてマイグレーションを実行、またはロールバックができるというわけですね。
ざっくりとですがマイグレーションについてまとめてみました。
今までなんとなく使っていた私ですが、
どういったものなのか少しずつ理解できるようになってきました。本日は以上です
1人前のエンジニアになるまであと92日
- 投稿日:2020-05-29T20:35:11+09:00
【Rails】TwitterのOGPを設定する
Gemなしです。
TwitterのOGPを表示させるために必要なmetaタグについては以下の記事を参考にしました。
【2020年版】Twitterカードとは?使い方と設定方法まとめ実装
app/helper/application_helper.rbdef full_title(page_title = '') base_title = 'MC BATTLE CHANNEL' if page_title.empty? base_title else "#{page_title} | #{base_title}" end end def full_url(path) domain = if Rails.env.development? 'http://0.0.0.0:3000' else 'https://mcbattle-ch.jp' end "#{domain}#{path}" endapp/views/layouts/application.html.erb<head> <!-- ogp --> <meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:site" content="@McBattleChannel" /> <meta property="og:title" content="<%= full_title(yield(:title)) %>" /> <meta property="og:url" content="<%= request.url %>" /> <meta property="og:description" content="<%= content_for?(:description) ? yield(:description) : 'MCバトルの総合サイトです。' %>" /> <meta property="og:image" content="<%= content_for?(:image_url) ? yield(:image_url) : full_url('/assets/other/ogp_default.png') %>" />app/views/hoge/fuga.html.erb<%= content_for(:title, "この個別ページのタイトル!") %> <%= content_for(:description, "この個別ページの説明文!") %> <%= content_for(:image_url, full_url("assets/mc/hoge.img")) %>
- 投稿日:2020-05-29T20:06:40+09:00
RailsのViewでの微妙に異なる繰り返しの書き方をいくつか
概要
以下みたいなコードを見ると思う。4件だからいいが10件とかあったり、ネストが深いと悲しくなってくる。
どうにか簡単に、繰り返しを少なく書きたい。ついいろいろ試してしまう。<ul> <li> <div>ユーザ名</div> <div><%= user.name %></div> </li> <li> <div>メールアドレス</div> <div><%= user.email %></div> </li> <li> <div>性別</div> <div><%= user.sex == 1 ? '男性' : '女性' %></div> </li> <li> <div>役割</div> <div><%= user.role %></div> </li> </ul>1. 単純な繰り返しの場合
DraperとActive Recordモデルの翻訳を使うと以下のようにかける。
単純な繰り返しで良ければこれで大丈夫<!-- sex_textメソッドをUserDecoratorに追加すること。 --> <ul> <% %i[name email sex_text role].each do |key| %> <li> <div><%= User.human_attribute_name(key) %></div> <div><%= user.send(key) %></div> </li> <% end %> </ul>2. 部分的な繰り返しの場合
途中にモデル外のものが混ざってしまう場合、単純なループではだめになる。3回以上同じ記述が現れてしまうが、以下のようにすれば少しは減らせる
<ul> <% %i[name email].each do |key| %> <li> <div><%= User.human_attribute_name(key) %></div> <div><%= user.send(key) %></div> </li> <% end %> <!-- 途中に今日の天気を入れる指示が来た --> <li> <div>今日の天気</div> <div><%= Wheather.today %></div> </li> <% %i[sex_text role].each do |key| %> <li> <div><%= User.human_attribute_name(key) %></div> <div><%= user.send(key) %></div> </li> <% end %> </ul>3. Helperを使う
htmlの記述を3回繰り返すのは嫌なので、せめてメソッドにする。
class UsersHelper def user_param_li(label, value) tag.li do tag.div(label) + tag.div(value) end end end<ul> <% %i[name email].each do |key| %> <%= user_param_li(User.human_attribute_name(key), user.send(key)) %> <% end %> <!-- 途中に今日の天気を入れる --> <%= user_param_li('今日の天気', Wheather.today) %> <% %i[sex_text role].each do |key| %> <%= user_param_li(User.human_attribute_name(key), user.send(key)) %> <% end %> </ul>4. クラスを用意してしまう
やっぱり読みづらいし、繰り返し構造が見えないので悲しい。オブジェクトを用意してしまう。
# このオブジェクトは、「サービスオブジェクト」「Presenter」「view_object」「PORO」など様々な呼ばれ方をされます。 # この役割なら、僕はプレゼンターという名前をつけると思います。 class UserParamsPresenter attr_reader :user def initialize(user) @user = user end def params [ user_param(:name), user_param(:email), wheather_param, user_param(:sex_text), user_param(:role), ] end private def wheather_param ['今日の天気', Wheather.today] end def user_param(key) [User.human_attribute_name(key), user.send(key)] end end<ul> <!-- このPresenterはcontrollerからも渡せます。僕はどっちがいいのかわかりません --> <% UserParamsPresenter.new(user).params.each do |label, value| %> <%= user_param_li(label, value) %> <% end %> </ul>5. やりすぎたので反省してViewだけで見てもわかりやすく書く
このケースではこんなに複雑にする必要はなかったかもしれない。デザイナーが困る。
ちょっとならrubyのコードをerbに書いちゃってもいいよね?Presenterは消せる。<% user_params = [ [User.human_attribute_name(:name), user.name)], [User.human_attribute_name(:email), user.email)], ['今日の天気', Wheather.today], [User.human_attribute_name(:sex_text), user.sex_text)], [User.human_attribute_name(:role), user.role)], ] %> <ul> <% user_params.each do |label, value| %> <%= user_param_li(label, value) %> <% end %> </ul>6. ここだけであればそもそももっと簡単にかけそうだ
HelperやらDraper,I18nなんてついやってしまったが、別にそれもなくてもそこそこきれいに書けるのでは?
急ぎのプロジェクトだしなあ。<% user_params = [ ['ユーザ名', user.name], ['メールアドレス', user.email], ['今日の天気', Wheather.today], ['性別', user.sex == 1 ? '男性' : '女性'], ['役割', user.role], ] %> <ul> <% user_params.each do |label, value| %> <li> <div><%= label %></div> <div><%= value %></div> </li> <% end %> </ul>まとめ
という感じで、いくつも書き方は思い浮かんでしまい、逡巡することになります。
この内、どれがいいかは場況を見るしかありません。
自分ではあまり書いたことのない書き方があれば、まずはぜひ手を動かして書いてみてください。
とりあえず書いてみて、数週間後の仕様変更や、同僚からのコメントで書き方が適切だったかどうかが試されます。
- 投稿日:2020-05-29T20:00:53+09:00
【Rails】whenever による定時バッチ処理
【Rails】whenever による定時バッチ処理
Wheneverを使ってRailsでのバッチ処理を実装したのでまとめます。
目次
動作環境
OS : macOS Mojave 10.14.6
ruby : 2.6.3p62
rails : 5.2.4
やりたいこと
決まった時間に定期的に実行するタスクを作成したい
手順
wheneverのインストール
Gemfilegem 'whenever', require: falseインストールの実行
Bundle install
railsにlibを加える
Railsがlibフォルダを読み込めるように設定します
libフォルダにバッチ処理をするファイルを置くためです.class Application < Rails::Application config.autoload_paths += Dir["#{config.root}/lib"] endバッチ処理の記載
libにbatchというフォルダを作成し、その中に定期実行するファイルを作成します。
Batch::以降は任意の名前で良いです。例
batch/deadline_cleaner.rb
deadline_cleanerclass Batch::DeadlineClear def self.deadline_clear puts DateTime.now puts 'Test' end end確認
$ bundle exec rails runner Batch::DeadlineClear.deadline_clear Running via Spring preloader in process 77676 testschedule.rbの作成・編集
アプリケーションのルートフォルダに移動し、以下のコマンドを実行
$ cd blog-app $ bundle exec wheneverize .実行すると
config/schedule.rb
が作成されます。その後、以下のようにschedule.rbにスケジュールと実行したいタスクを記載します。
set :output, 'log/crontab.log' set :environment, :development every 1.day, at: '00:00 am' do runner 'Batch::DeadlineClear.deadline_clear' end以下のコマンドでCRONへ反映します.
$ bundle exec whenever --update-crontab実行結果
実際に1分ごとにバッチ処理がされていることがわかります.
$ cat log/crontab.log Running via Spring preloader in process 78244 2020-05-29T19:50:01+09:00 test Running via Spring preloader in process 78534 2020-05-29T19:51:01+09:00 test Running via Spring preloader in process 78598 2020-05-29T19:52:00+09:00 test Running via Spring preloader in process 78652
- 投稿日:2020-05-29T19:31:00+09:00
rubyでtripleDESやったらめちゃハマった話
やりたいこと
tripleDESのCBCモードで暗号化したデータを先方のサーバーにリクエストすると、同様の方式でデータがレスポンスするので復号する。
で、このtripleDESについて全く無知だったのでググってみると、Rubyの記事って案外情報が見つかりにくいんですね。
色々検索してこちらのページにたどり着きました。
http://timolshansky.com/2011/10/23/ruby-triple-des-encryption.html実際ここで書かれているコードを実行すると、ちゃんと動くんです。が、ここからが長かった。
先方の認証サーバーにリクエストするもうまくいかない
いただいた鍵を使って、その他パラメータを何度確認してもミスはない。ぐぬぬ...
恥を忍んで先方の方が暗号化の際に使っているコード(別の言語)を見せていただくと....おや?暗号化したバイトの先頭に何かくっつけているぞ?
初期化ベクトルというもう一つの鍵
実は、今回の暗号化方式(des-ede3-cbc)では、秘密鍵の他に初期化ベクトルという物を実質鍵のように使って暗号化しているんですね。
これに気づくのに随分と時間がかかった。なんでかっていうと、↑に挙げたページやその他ぐぐったページでは 'pkcs5_keyivgen' というメソッドを使っているんですよ。
要は インスタンス作る -> pkcs5_keyivgenで鍵と初期化ベクトルをセット -> 暗号、復号する という流れなんですが、これだと同じ初期化ベクトルを使っていることになる。だから復号できる。が、今回の条件は こちらの手元で暗号化 -> 相手サーバーで復号 となるので、相手は秘密鍵だけでなく初期化ベクトルの情報を知らなければいけないんですね。 (先頭に初期化ベクトル入れるってのはtripleDESの仕様なのでしょうか?これはググってもわからんかった)
実装
というわけで、実際にやってみたのがこちら。今回は、先頭8byteが初期化ベクトル。
class TripleDES class << self IV_LENGTH = 8 SECRET_KEY = 'your__awesome_Secret_Key' def get_cipher cipher = OpenSSL::Cipher.new('des-ede3-cbc') cipher.key = SECRET_KEY cipher end def encrypt(plain_string) cipher = get_cipher cipher.encrypt # 初期化ベクトルを生成する iv = OpenSSL::PKCS5.pbkdf2_hmac(SecureRandom.alphanumeric(10), SecureRandom.alphanumeric(10), 2, IV_LENGTH, 'sha1') cipher.iv = iv output = cipher.update(plain_string) output << cipher.final # 生成した暗号の前に初期化ベクトルを入れる iv + output end def decrypt(encrypted_byte_string) cipher = get_cipher cipher.decrypt # 初期化ベクトルと本文をそれぞれ取り出す iv = encrypted_byte_string.byteslice(0, IV_LENGTH) cipher.iv = iv target_bytes = encrypted_byte_string.byteslice(IV_LENGTH, encrypted_byte_string.chars.count) output = cipher.update(target_bytes) output << cipher.final end end end余談
pkcs5_keyivgenは非推奨メソッドってちゃんと書いてあるのにしばらく気づかなかった。笑
https://docs.ruby-lang.org/ja/latest/method/OpenSSL=3a=3aCipher/i/pkcs5_keyivgen.htmlこちらの方が詳しい。そうなんですよ、pkcs5_keyivgenはivを取って来れないんですよね。
https://techmedia-think.hatenablog.com/entry/20110527/1306499951今後実装される方のヒントになれば幸いです。久々にハマった....
- 投稿日:2020-05-29T18:04:46+09:00
【rails】javascriptのファイル名がコントローラと同じ場合にイベント発火しなかった時の記録
javascriptのイベントが発火しない。
app/assets/javascripts/users.js$(function(){ $("#user-search-field").on("keyup", function() { console.log("OK"); }); });jsのファイル名を変えてみた。
app/assets/javascripts/test.js$(function(){ $("#user-search-field").on("keyup", function() { console.log("OK"); }); });イベント発火した。
いろいろなファイル名を試した。
○ test.js
○ ttt.js
○ user.js
× users.js
× groups.js
○ group.jsコントローラと同じファイル名だけダメ。
よくみたら次のようなファイルがあることに気付いた。
app/assets/javascripts/...
groups.coffee
users.coffeecoffee scriptの影響を疑い、coffeeファイルを削除すると、イベント発火するようになった。
もう一回、railsを再起動するとエラー。
LoadError: cannot load such file -- coffee_scriptcofee-railsをuninstall
Gemfile# gem 'coffee-rails', '~> 4.2'
terminalbundle install
terminalrails tmp:cache:clear
解決!
参考
- 投稿日:2020-05-29T18:01:14+09:00
Ruby 複数の配列を結合する
はじめに
配列は要素に別の配列を持つことができるので、
[1,2,3,[4,5,6],7,8,9] とか [[1,2,3],[4,5,6],[7,8,9]]みたいな形の配列を作ることができます。
元々別々の配列を上記のように一つの配列にする場合、配列を結合・追加するメソッドや演算子はたくさんあります。
push
<<
+
concat
unshift
ですが、色々使っていたら混乱してきました。
しかも使い方が少しずつ違うようですのでいくつか試してみましたのでこちらにまとめます。1.
push
メソッドpushメソッドは配列の最後尾に要素の一つとして指定された値を追加します。
a = [1,2,3] b = [4,5,6],[7,8,9] c = [10,11,12],[13,14,15] a.push(b) => [1, 2, 3, [[4, 5, 6], [7, 8, 9]]] b.push(a) => [[4, 5, 6], [7, 8, 9], [1, 2, 3]] b.push(c) => [[4, 5, 6], [7, 8, 9], [[10, 11, 12], [13, 14, 15]]]追加する配列が複数であってもレシーバーの配列の要素一つ分になるようです。
なのでb = [4,5,6],[7,8,9]
は[[4,5,6],[7,8,9]]
として最後尾に追加されています。また、
push
メソッドは破壊的メソッドです。レシーバーは値が変更されます。a.push(b) => [1, 2, 3, [[4, 5, 6], [7, 8, 9]]] a => [1, 2, 3, [[4, 5, 6], [7, 8, 9]]]2.
<<
演算子
<<
演算子はpush
メソッドと同じ動作のようです。a = [1,2,3] b = [4,5,6],[7,8,9] c = [10,11,12],[13,14,15] a << b => [1, 2, 3, [[4, 5, 6], [7, 8, 9]]] b << a => [[4, 5, 6], [7, 8, 9], [1, 2, 3]] b << c => [[4, 5, 6], [7, 8, 9], [[10, 11, 12], [13, 14, 15]]]同じですね!
破壊的メソッドであるという点も同じです。a << b => [1, 2, 3, [[4, 5, 6], [7, 8, 9]]] a => [1, 2, 3, [[4, 5, 6], [7, 8, 9]]]3.
+
演算子+演算子は追加する配列の要素を一つずつ追加します。
a = [1,2,3] b = [4,5,6],[7,8,9] c = [10,11,12],[13,14,15] d = [-1,-2,-3] a + b => [1, 2, 3, [4, 5, 6], [7, 8, 9]] b + a => [[4, 5, 6], [7, 8, 9], 1, 2, 3] b + c => [[4, 5, 6], [7, 8, 9], [10, 11, 12], [13, 14, 15]] a + d => [1, 2, 3, -1, -2, -3]
a
の配列では要素は数値一つ一つですので、値が最後尾に追加されていきます。
b
の配列では要素は配列一つ一つですので、配列のまま最後尾に追加されていきます。[[1, 2, 3], [4, 5, 6], [7, 8, 9]]としたい場合には
[a] + b => [[1, 2, 3], [4, 5, 6], [7, 8, 9]]というように
a
自体を配列の要素としてあげれば良さそうです。
+
演算子は値に変更を加えません。a + b => [1, 2, 3, [4, 5, 6], [7, 8, 9]] a => [1, 2, 3]4.
concat
メソッド
concat
メソッドは+
演算子とほとんど同じ動作をします。a = [1,2,3] b = [4,5,6],[7,8,9] c = [10,11,12],[13,14,15] d = [-1,-2,-3] a.concat(b) => [1, 2, 3, [4, 5, 6], [7, 8, 9]] b.concat(a) => [[4, 5, 6], [7, 8, 9], 1, 2, 3] b.concat(c) => [[4, 5, 6], [7, 8, 9], [10, 11, 12], [13, 14, 15]] a.concat(d) => [1, 2, 3, -1, -2, -3]同じです。
[a].concat(b) => [[1, 2, 3], [4, 5, 6], [7, 8, 9]]配列の要素として扱う方法もこちらでも有効です。
ただし、
concat
メソッドは破壊的メソッドです。a.concat(b) => [1, 2, 3, [4, 5, 6], [7, 8, 9]] a => [1, 2, 3, [4, 5, 6], [7, 8, 9]]ここが
+
演算子との違いですね。おまけ.
unshift
メソッド
unshift
メソッドはpush
メソッドによく似ています。
push
メソッドは最後尾に追加します。unshift
メソッドは先頭に追加します。[[1, 2, 3], [4, 5, 6], [7, 8, 9]]この形を作りたい場合
+
演算子等でのやり方を載せましたが、unshift
メソッドを使っても実現できます。a = [1,2,3] b = [4,5,6],[7,8,9] b.unshift(a) => [[1, 2, 3], [4, 5, 6], [7, 8, 9]]さいごに
配列を配列のまま結合する方法をまとめた記事を見つけられなかったので、自分でまとめてみました。
間違い等ありましたら教えてください。
- 投稿日:2020-05-29T17:59:11+09:00
0 から始める Jekyll 超入門 #2 - 独自テーマ作成編
この記事は 0 から始める Jekyll 超入門 の 2番目の記事です。
- #1 環境構築編
- #2 独自テーマ作成編 <-今回
独自テーマとは
Jwkyll では独自にテーマを作成してオリジナルなデザインのサイトを作ることができます。
#1 の記事で、記事の作成の時に layout: post としたのを覚えているでしょうか?
これは、テーマの post レイアウトを使用する、という意味です。Jekyll で独自テーマを作成するのは非常に簡単で、
_layouts
ディレクトリにレイアウトを作り、それを posts や page でこの前やった通りに指定することで適用する、という感じになります。ここでは、トップページと投稿ページに独自テーマを適用するようにしていこうと思います。
注)なお、あくまで操作やロジックの部分に焦点を当てたいため、デザインの部分はかなり手抜きします。
きれいに作りたい方は自分で1から scss を書いたり、Bootstrap や Bulma などの cssフレームワークを利用したりしてください。それでは早速作っていこうと思います。
テーマを作る前の準備
不要なファイルを削除したり必要なファイルを作成
まず最初に、
作業ルートディレクトリに_layouts
ディレクトリを作ってください。
前回からの確認のため、ディレクトリの図を一応貼っておきます。
(自動生成される .gitignore に指定されている物は除外しています。. ├── 404.html ├── Gemfile ├── Gemfile.lock ├── _config.yml ├── _layouts ├── _posts │ ├── 2020-05-19-hello-world.md │ └── 2020-05-19-welcome-to-jekyll.markdown ├── about.markdown └── index.markdownindex.markdown と about.markdown はとりあえず不要なので削除しましょう。
設定ファイルを編集する
_config.yml
を開き以下の通りに上書きします。_congig.ymltitle: 0 から始める Jekyll 超入門 # サイトのタイトル description: >- # サイトの説明を書く サイトの説明を書いてください。 200文字程度がいいと一般には言われていたりします。 baseurl: "" # ここは無視してOKです url: "https://<type-your-domain>" # サイトのドメインを入力します。 plugins: # 使用するプラグインがあればここに追加します - jekyll-feed exclude: # サイトの生成に含めないものをここに追加します - .sass-cache/ - .jekyll-cache/ - gemfiles/ - Gemfile - Gemfile.lock - node_modules/ - vendor/bundle/ - vendor/cache/ - vendor/gems/ - vendor/ruby/前準備などは以上です。
Jekyll の Lquid 構文
レイアウトを作っていく前に、簡単な構文の解説をします。
まず、 Jekyll は基本的に html をベースに書くことができます。
その上で、 Liquid と呼ばれる構文を使用することもできます。
Liquidは3つの主なパート:オブジェクト、タグ、フィルタを持つテンプレート言語とされています。オブジェクト
Liquid では、
{{
と}}
で各種オブジェクト(変数など)を括ることで、出力することができます。
例えば、<title> {{ page.title }} </title> <!-- page.title = "こんにちは, 世界!" ->このようなソースがある場合、生成される html ファイルは、以下のようになります。
<title> "こんにちは, 世界!" </title>タグ
Liquid タグを使用することで、ロジックのあるテンプレートを使用することができます。
Liquid タグを使用する場合は、{%
と%}
で囲みます。例えば以下のような場合、 page.title が
true
の場合、
すなわち値が存在する場合には{{ page.title }} - {{ site.title }}
が出力される、
すなわち "こんにちは, 世界! - Jekyll 超入門" が出力されます。
false
の場合は、
{{ site.title }} すなわち "Jekyll 超入門" が出力されるわけです。<title> {% if page.title %}{{ page.title }} - {{ site.title }}{% else %}{{ site.title }}{% endif %} </title> <!-- page.title = "こんにちは, 世界!", site.title = "Jekyll 超入門" ->他にも Jekyll では様々な Liquid タグが用意されているので、こちら を読んでおくといいかもしれません。
フィルタ
Liquid フィルターは、Liquid オブジェクトの出力を加工することができます。
オブジェクトの出力を|
で区切ることで実装できます。例えば、以下のソースの場合を見てみましょう。
ちなみに、page.date
は記事の作成日を出力するオブジェクトで、
記事のファイルの命名規則であるyyyy-mm-dd-<title>.md
に基づいて出力されます。<p>公開日: {{ page.date | date: "%Y年 %m月%d日" }}</p> <!-- page.date = 2020-01-01 00:00:00 +0900 -->この場合は、次のように生成されます。
<p>公開日: 2020年 01月 01日</p>他にも様々な便利なフィルターがあります。
こちら の公式Docs を見ておくといいかもしれません。Front Matter
Jekyll では Front Matter を利用してページに変数を追加することができます。
ちなみに #1 で layout に post を指定しましたが、これも一種の変数で、
page.layout で得ることができます。Front Matter は各 markdown ファイルや html ファイルの先頭に、
---
と---
で挟んだ yaml 形式で書きます。
#1 で Jekyll Admin 経由で生成した _posts/2020-05-19-hello-world を見て見ましょう。2020-05-19-hello-world.md--- title: Hello, world! layout: post --- # Hello, world! ## こんにちは、世界! **コンニチハ, セカイ** ハロー、ワールド先頭に
---
と---
で挟まれたものがあります。
これが Front Matter です。上位のレイアウトで、これを
page.title
やpage.layout
で参照することができます。
また、この変数は独自に設定もできるため、例えば layout の下にtext: hello!
を追加すると、
page.text
で参照でき、 "hello!" が出力されます。scss/sass を利用する
Jekyll ではデフォルトで scss/sass を利用できる機能があります。
プロジェクトルートに_sass
ディレクトリを作成し、その配下に sass/scss ファイルを追加していきます。また、プロジェクトルート配下の適当なところに index.scss を作成し、
作成したファイルを@import
で読み込みます。(この時相対パスの起点は_sass
ディレクトリです。例えば
_scss
ディレクトリが以下のような場合、. ├── _errors.scss ├── _global.scss ├── _index.scss ├── _navbar.scss ├── _page.scss ├── _post.scss ├── _variables.scss └── bulma ├── ...index.scss--- --- @import 'bulma/bulma'; @import 'bulma/bulma-tooltip/index'; @import 'variables'; @import 'global'; @import 'navbar'; @import 'index'; @import 'page'; @import 'post'; @import 'errors';assets 配下に index.scss を作成し、
<link href="{{ "/assets/index.css" | absolute_url }}" rel="stylesheet">
をレイアウトに追加することで、
読み込むことでできます。レイアウトを追加する
それではテーマ作りを始めていこうと思います。
前述の通りレイアウトを追加していくだけなので簡単です。大きく分けると、
- 全てのレイアウトの基幹になる default レイアウト
- 投稿用の post レイアウト
- 固定(静的?)ページの page レイアウト
の3つがあるとだいたいきれいなサイトができます。
なお、あくまで操作やロジックの部分に焦点を当てていくので、デザインの部分はかなり手抜きします。
MVP.css を使用します。
これは HTML タグからそのままスタイル適用してくれる css フレームワークです。default レイアウト
まずは全てのページの基幹となる default レイアウトを追加しましょう。
この辺りの実装方法は個人差がかなり出てくるものなのですが、
とりあえずは僕は default レイアウトからさらに別のレイアウトへと継承させていく感じをよくしています。ソースコード
_layouts
ディレクトリにdefault.html
を作成し、以下の通りにします。_layouts/default.html<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title> {% if page.title %}{{ page.title }} - {{ site.title }}{% else %}{{ site.title }}{% endif %} </title> <meta name="description" content="{% if page.desc %}{{ page.desc }}{% else %}{{ page.content | strip_html | truncate: 130 }}{% endif %}"> <!-- OGP の設定 --> <meta property="og:title" content="{% if page.title %}{{ page.title }} - {{ site.title }}{% else %}{{ site.title }}{% endif %}" /> <meta property="og:type" content="article" /> <meta property="og:url" content="{{ site.url }}{{ page.url }}" /> <meta property="og:site_name" content="{{ site.title }}" /> <meta property="og:description" content="{% if page.desc %}{{ page.desc }}{% else %}{{ page.content | strip_html | truncate: 130 }}{% endif %}" /> <meta property="og:image" content="{% if page.thumbnail %}{{ page.thumbnail }}{% else %}{{ "/assets/images/ogp-image.png" | absolute_url }}{% endif %}" /> <!-- RSSフィード の指定 --> <link rel="alternate" type="application/rss+xml" title="{{ site.title }} RSS" href="{{ "/feed.xml" | absolute_url }}" /> <!-- mvp.css https://andybrewer.github.io/mvp/ を利用します。 MITライセンスで公開されています。 --> <link href="{{ "/mvp.css" | absolute_url }}" rel="stylesheet"> </head> <body> {% include _header.html %} {{ content }} {% include _footer.html %} </body> </html>解説
<title> {% if page.title %}{{ page.title }} - {{ site.title }}{% else %}{{ site.title }}{% endif %} </title>先ほどの説明の通りです。
これは、もし page.title に値が存在したら{{ page.title }} - {{ site.title }}
を出力、
そうでければ{{ site.title }}
を出力します。<meta name="description" content="{% if page.desc %}{{ page.desc }}{% else %}{{ page.content | strip_html | truncate: 130 }}{% endif %}">これも同様に、 page.desc に値が存在したら page.desc を、そうでなければ page.content (ページの内容が入った変数)の最初の 130字 を出力します。
次に、
{% include _header.html %} ... {% include _footer.html %}liquid タグの include です。これはレイアウトを分割するのに非常に便利です。
_include
配下のファイルを読み込むことができます。_include/_header.html<header> <nav> <a href="/">{{ site.title }}</a> <ul> <li><a href="/">ホーム</a></li> </ul> </nav> <h1>{{ page.title }}</h1> </header>最後に、
{{ content }}これは読み込み元のソースを参照します。
post レイアウト
ソースコード
_layouts/post.html--- layout: default --- <main> <article> <p>公開日: {{ page.date | date: "%Y年 %m月%d日" }}</p> {% if page.desc %} <h2>{{ page.desc }}</h2> {% endif %} <div> {{ content }} </div> </article> </main>解説
--- layout: default ---Front Matter です。
基幹となる default レイアウトを指定しています。<p>公開日: {{ page.date | date: "%Y年 %m月%d日" }}</p>日付のフォーマットです。
page.date で返ってくる値はあまりきれいなものでないので、
date フィルターを利用してきれいな形にしました。他は特に特筆する点はないと思います。 if 文なども先ほど説明しました。
page レイアウト
ソースコード
_layouts/page.html--- layout: default --- <main> <div> {% if page.desc %} <h2>{{ page.desc }}</h2> {% endif %} <div> {{ content }} </div> </div> </main>解説
特筆すべき事項は特にないですね。 post とだいたい一緒です。
front matter に layout を指定するのを忘れないようにしましょう。その他のページ作ってみる
index.html
index.html がないと、ドメインのルートにアクセスした時に不格好なインデックスページが出てきてしまうので、
index.html を作りましょう。index.html--- layout: default --- <main> <section> <header> <h2>投稿一覧</h2> </header> {% for post in site.posts %} <aside> <a href="{{ post.url }}"><h3>{{ post.title }}</h3></a> <p>{% if post.desc %}{{ post.desc }}{% else %}{{ post.content | strip_html | truncate: 130 }}{% endif %}</p> </aside> {% endfor %} </section> </main>解説
liquid タグの for を使っています。これは、
for <変数名> in 配列
の形で、各要素を変数に取り出してくれます。最終的なディレクトリ構成
. ├── 404.html ├── Gemfile ├── Gemfile.lock ├── _config.yml ├── _includes │ ├── _footer.html │ └── _header.html ├── _layouts │ ├── default.html │ ├── page.html │ └── post.html ├── _posts │ ├── 2020-05-19-hello-world.md │ └── 2020-05-19-welcome-to-jekyll.markdown ├── index.html └── mvp.cssソースコード全部
ソースコードを GithHub に公開しています。
https://github.com/yu-san-19/zero-starts-jektyll-nyumon/tree/7c75428c880be369a552bf72bf6a2f87d7a5293e実行してみる
さて、一通りできたと思うので実行してみましょう。
$ bundle exec jekyll slistening 的な感じのやつが出てきたら、以下にアクセスしてみましょう。
以下のように表示されたら成功です。http://localhost:4000/yyyy/mm/dd/hello-world.html (yyyy, dd, mm に適宜 mdファイル名の数字を入れる)
さいごに
今回はテーマ作成ということで、レイアウトや liquid タグについて解説しました。
少々省略したり説明不足だったりしますが、日本語のドキュメントが結構しっかりと説明が書いてあったりしますので、是非読んでみてください。ここがわからない、とか、ここは間違ってる、とか、こういう説明の方がわかりやすい、などありましたら気軽にコメントいただけたらありがたいです。
ここまでで(デザインなどは無視していますが)サイトは一応できています。
ですが、これでは誰でも見れる状態ではありません。
次回は、サイトの公開について、解説していこうと思います。それではまた今度!
- 投稿日:2020-05-29T17:29:30+09:00
ruby メソッドの省略記法
hoge = ['a', 'b']
hoge.map(&:upcase)=> ['A', 'B']
- 投稿日:2020-05-29T16:09:30+09:00
Ruby ビンゴカード作成問題 解いてみた(解答例あり)
はじめに
『プロを目指す人のためのRuby入門』通称チェリー本を学習後のプログラミング初心者です。
インプットしたものを手を動かして実践してみたいなと思ったら、作者の記事を見つけました。
「アウトプットのネタに困ったらこれ!?Ruby初心者向けのプログラミング問題を集めてみた(全10問)」この3つ目の問題を解いてみました。
他の問題はこちら
一問目:カレンダー作成問題(たのしいRuby 練習問題)
二問目:カラオケマシン作成問題問題
詳細はここから
B:1~15のどれか
I:16~30のどれか
N:31~45のどれか
G:46~60のどれか
O:61~75のどれか
というルールで次のようなビンゴカードを作ります。B | I | N | G | O 13 | 22 | 32 | 48 | 61 3 | 23 | 43 | 53 | 63 4 | 19 | | 60 | 65 12 | 16 | 44 | 50 | 75 2 | 28 | 33 | 56 | 68Bingo.generate_card メソッドの出力結果は上で述べた数字のルールのほかに、以下の仕様を満たす必要があります。
- 毎回異なるカードを生成します。
- 各列はパイプ(|)で区切ります。
- 数字や"BINGO"の文字は右寄せで出力します。
- 真ん中(FREEになる場所)はスペースを出力します。解答例
こんな感じになりました。
class Bingo def self.generate_card title = "BINGO".split("").map{|bingo| sprintf("%2s",bingo)}.join(" | ") numbers = [*1..75].each_slice(15).to_a.map{|b| b.sample(5)} numbers[2][2] = " " body = [] for i in 0..4 body << numbers.map{|number| sprintf("%2s",number[i])}.join(' | ') end [title,body].join("\n") end endさいごに
下記リンクに他の方の解答も掲載されていました。
Ruby -「ビンゴカード作成問題」の優秀作品ベスト3を発表します!
transposeメソッド
など、知らなかったので使いませんでしたが他の方の解答をみるに、まだまだリファクタリングの余地はありそうです。ご意見等あればコメントで教えてください。
よろしくお願いします。
- 投稿日:2020-05-29T13:44:32+09:00
【NoMethodError】newアクションで発生した際の解決法
概要
登録画面に遷移したいのに、エラーになって全然表示できない
状況に陥り、何時間も悪戦苦闘した結果、解決しましたので備忘録として残します。事象
routes.rbRails.application.routes.draw do devise_for :users root "groups#index" resources :users, only: [:edit, :update] resources :groups, only: [:index, :new, :create, :edit, :update] do resources :tasks, only: [:index, :new, :create, :edit, :update, :destroy] end endcontroller.html.hamldef new @task = Task.new endmain_view.html.haml#省略 .new_display = form_for @task do |f| #省略エラーを見た感想
おかしなとこない
何言ってんねん
newアクションだからデータないやんかーーー
と一人で悶々としてました。。。原因
原因は【form_forが自動的に生成してくれるパスは複数形のみ】ということ!!
エラー文にも記載ありました!undefind method 'tasks_path' for ~
私は、その箇所を読んでも何言ってんの??そんなパスないよ
と思ってましたが解決策
viewを下記のように変更しました!
main_view.html.haml#省略 .new_display = form_for @task, url: group_tasks_path do |f| #省略画面
似たようなことでお悩みの方の足掛かりになれば幸いです
参考
- 投稿日:2020-05-29T13:18:43+09:00
【Rails】deviseのバリデーションをカスタマイズ
最初に
deviseを使用して、ユーザーの新規登録機能を実装している際に、
バリデーションを付与しているのにバリデーションが適用されず、実装に苦戦したため備忘録として残します。deviseのデフォルトのバリデーション
deviseは認証機能を簡単に実装できる様にするためのgemですが、勝手にgemが動いてくれるがゆえに見えないところで何をしているかが、初学者の私には理解が難しかったです。これがバリデーションを付与しているのに適応されない原因でした。
new.html.haml= form_for(@user, url: user_registration_path) do |f| = f.password_field :password, class: "main__box__bottom__content__group2__form", placeholder: "7文字以上の半角英数字",結論はフォームでpassword_fieldを使用していたため、Railsはこの入力フォームをpasswordと認識して、簡単なバリデーションを自動でかけるようになっているようです。
deviseのカスタマイズ手順
1.フォームを修正(適切なフォームに)
password_fileldからtext_fieldに修正。
new.html.haml= form_for(@user, url: user_user_registration_path) do |f| = f.text_field :password endこれでpasswordカラムと認識。
2.deviseのバリデーションの設定
ただモデルのpasswordカラムを追加に対してバリデーションをかけると2重でバリデーションがかかってしまうので、
deviseのバリデーションを追加している:validatableを削除する。user.rbclass User < ApplicationRecord devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable #↑削除する。 endこれでdeviseのバリデーションは掛からなくなり、passwordに自分でカスタマイズしたバリデーションが付与されるようになりました。
まとめ
・バリデーションには参照する優先順位があり、
テキストフィールドに与えられているバリデーション>devise>自作のバリデーション
となっている。
・カスタムバリデーションのみ適用させる場合は、text_fieldを使い、deviseの:validatableを削除する。参考文献
- 投稿日:2020-05-29T12:14:50+09:00
【Rails】ヘルパーメソッド、confimartionの使い方
最初に
パスワード入力時に確認用で2回パスワードを打ち込む仕様になっているサービスをよく見かける。
同じことを実装しようとした際にconfimartionというRailsにおけるバリデーションのヘルパーメソッドを使用すれば簡単に実装できたため、備忘録として残します。connfimartionとは
2つのテキストフィールドで受け取る内容が完全に一致する必要がある場合に使います。たとえば、メールアドレスやパスワードで、確認フィールドを使うとします。このバリデーションヘルパーは仮想の属性を作成します。属性の名前は、確認したい属性名に「confirmation」を追加したものになります。
https://railsguides.jp/activerecord_validations.html
上記より引用。要は2つのフォームで同じデータでなければ登録できないバリデーションをかけるメソッドという認識で問題ないかと思います。
実装手順
1.モデルの修正
user.rbclass User < ApplicationRecord validates :password, confirmation: true endモデルにconfimartionメソッドを適用したいカラムに対して、上記の様な記述を追加。
これでバリデーション完了。2.ビューに確認用のフォームを追加
new.html.haml= form_for(@user, url: user_registration_path) do |f| = f.text_field :password = f.text_field :password_confirmation endテキストフィールドの第二引数の属性名は、confimartionで確認したい属性名に_confimartionを追加します。
実際に記述したモデルとビューについては下記の様な感じになります。
以上で2つのフォームに入力された値が同じでなければバリデートされる様になります!
バリデーションを追加したい場合は、確認したい属性にバリデーションを付与すれば良いだけです。最初実装する際に、
・確認用の属性を作成して、
・二つのフォームに入力がされた値が同じかどうかを確認する様な条件分岐の作成
・同じであれば保存
の様なコードを書かないといけないと思っていたので、confirmationを使うことにより非常に簡単に実装が完了しました。参考文献
- 投稿日:2020-05-29T08:21:26+09:00
.bashrcと.bash_profileの違い
.bashrc/.bash_profile/.profile それぞれに何を書くのか
~/.bash_profile
-> 余計なものを書かない。
ログイン時に一回だけ実行する設定を書く
.profileと.bashrcを読み込む設定だけ書く~/.bashrc
シェルを起動するたびに実行したい設定を書く
ex.) EDITOR変数, プロンプト設定~/.profile
bashに依存しないものを書く
ex.) 環境変数, GUI設定上記を踏まえての設定
.bash_profile # .profileを読み込む if [ -f ~/.profile ] ; then . ~/.profile fi # .bashrcを読み込む if [ -f ~/.bashrc ] ; then . ~/.bashrc fi.bashrc # gitのブランチ名と改行 source /usr/local/etc/bash_completion.d/git-prompt.sh source /usr/local/etc/bash_completion.d/git-completion.bash # default:cyan / root:red if [ $UID -eq 0 ]; then PS1='\[\033[31m\]\[\033[00m\]\[\033[01m\]\w\[\033[31m\]$(__git_ps1)\[\033[00m\]\n\$ ' else PS1='\[\033[36m\]\[\033[00m\]\[\033[01m\]\w\[\033[31m\]$(__git_ps1)\[\033[00m\]\n\$ ' fi.profile # rubyの環境変数設定 export PATH=$HOME/.nodebrew/current/bin:$PATH eval "$(rbenv init -)"参照
- 投稿日:2020-05-29T04:27:50+09:00
type: :mailer以外のrspecだとActionMailer::Base.deliveriesが空っぽ
困った
request specなどでも
ActionMailer::Base.deliveries
を使ってテストがしたいのに
type: :mailer
以外のrspecだとActionMailer::Base.deliveries
が []のままで困ったきっとtype: :mailerの時は何かが裏で勝手にincludeとかしてるんだろうなーと思って
調べてみる
rspec の type: :model とかについて
という記事を見つけた
どうやらそういうことらしいtypeに応じてここにあるものが読み込まれるのかな?
https://github.com/rspec/rspec-rails/blob/master/lib/rspec/rails/example/ってことで試してみる
解決
rails_helper.rbRSpec.configure do |config| . . . config.include RSpec::Rails::MailerExampleGroup . . .ってやると
お、ActionMailer::Base.deliveries
に積まれるようになった〜〜
よかったよかった常に読み込ませたくないなら
ActionMailer::Base.deliveries
を使いたいrspecの中でのみinclude RSpec::Rails::MailerExampleGroup`でおk
- 投稿日:2020-05-29T02:31:52+09:00
ssh接続をする際に「WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!」
同じIPアドレスでサーバを用意した場合、SSHで接続するときに下記のようなエラーがせることがある
$ ssh root@123.123.123.123 @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY! Someone could be eavesdropping on you right now (man-in-the-middle attack)! It is also possible that a host key has just been changed. The fingerprint for the RSA key sent by the remote host is 9a:33:31:63:13:85:d3:32:c1:bb:d9:0f:cc:d9:c5:f6. Please contact your system administrator. Add correct host key in /Users/hideaki/.ssh/known_hosts to get rid of this message. Offending RSA key in /Users/hideaki/.ssh/known_hosts:51 Password authentication is disabled to avoid man-in-the-middle attacks. Keyboard-interactive authentication is disabled to avoid man-in-the-middle attacks. Permission denied (publickey,password,keyboard-interactive).解決方法
$ ssh-keygen -R 123.123.123.123↓
再度接続!
- 投稿日:2020-05-29T02:31:00+09:00
herokuデプロイ
データベースをpostgreSQL以外を使っている場合はそのデータベースのgemをコメントアウトして、
Gemfileの一番下に本番環境ではpostgreSQLを使うことを宣言してbundle installする。Gemfile# gem 'mysql2', '>= 0.4.4', '< 0.6.0' group :production do gem "pg" endjQueryにてテンプレートリテラルを使用した場合は下記の文章をコメントアウトする。
environments/production.rb# config.assets.js_compressor = :uglifier
あとはherokuで登録が完了していればログインする。
ターミナル$ heroku loginその後、GitHubで管理しているアプリをheroku上に作成する。
ターミナル$ heroku create アプリ名GitHubでcommitとPushをしてmasterにマージを済ました状態で下記のコードを実行する。
ターミナル$ git push heroku masterheroku上にデータベースを作成する。
ターミナル$ heroku run rails db:migrateそして最後にアプリを開く
ターミナル$ heroku open
- 投稿日:2020-05-29T00:07:16+09:00
Railsで大量データを削除する方法と懸念点
はじめに
皆様、こんにちは!
佐久間まゆちゃんのプロデューサーの@hiroki_tanakaです。私はRailsアプリケーションの保守に関わっているのですが先日、本番環境に大量の不要データが存在していることが判明しました。
それがキッカケでRailsでの大量データの削除方法を検討したので、調べたことをまとめました。Railsにおけるdestroyとdeleteの違い
まず、Railsには2つのデータ削除メソッドのdestroyとdeleteがあります。
それぞれの違いを簡単にまとめたいと思います。destroy/destroy!
ActiveRecordを介して指定した1レコードを削除します。
ActiveRecordを介するためcallbackメソッド(before_destroy
やafter_destroy
など)やvalidationが機能します。
また、削除対象のModelにdependent: :destroy
が設定されている関連Modelが存在している場合は、設定されているModelも一緒に削除します。
destroyは実行時にエラーが発生し削除出来なかった場合はfalseを返すだけで例外を返却しません。
対して、destroy!は例外を返却します。
そのため、削除時のエラーを明示的にキャッチしたいという場合はdestroy!を使用するのが良いと思います。delete
ActiveRecordを介さずにDBに対して直接SQL(DELETE文)を発行して対象の1レコードを削除します。
ActiveRecordを介さないため、callbackメソッドやvalidationは機能しません。
また、削除対象のModelにdependent: :destroy
の関連付けされているModelがあったとしても削除しません。
失敗時の挙動はdestroy同様にfalseを返すだけで例外を返却しません。
deleteにはdelete!が存在しないので、「データを削除して、失敗した場合はエラーを返却したい」という場合はdestroy!を素直に使用するのが良いと思います。destory_all
destroy/destroy!は1レコードしか削除対象に取れませんが、destory_allは複数レコードが指定可能で指定されたレコードを全て削除します。
destroyと同様にdestory_allもActiveRecordを介するためcallbackメソッドやvalidation・dependent: :destroy
が機能します。
destory_allはdestroyと同様に実行時にエラーが発生し、途中で削除処理が失敗した場合はfalseを返すだけで例外を返却しません。
ただし、destory_all!というメソッドは存在しないため、「大量データを削除したい。でも、失敗した場合はちゃんとエラーを返して欲しい」という場合は方法を考える必要があります。(その方法は後述します。)delete_all
指定された複数レコードをActiveRecordを介さずに削除します。
delete同様にcallbackメソッドやvalidation・dependent: :destroy
は機能しません。
delete_allもdeleteと同様に実行時にエラーが発生し、途中で削除処理が失敗した場合はfalseを返すだけで例外を返却しません。
個人的にはそんなに使用する場面がないと思うのですが、ActiveRecordを介さない分処理がdestroyやdestory_allより速いです。
そのため、大量データを例外やコールバック・関連を気にせず一気に削除したいという場面では使用できるかと思います。大量データを削除する方法
本題の大量データを削除する方法について見ていきたいと思います。
前提
- Ruby 2.6.6
- Rails 6.0.2
- Model:Animal
- 削除対象データ:10万件(Modelの全量ではない。)
- callbackメソッド:あり
dependent: :destroy
の関連付けModel:あり今回削除対象のModelにはcallbackも
dependent: :destroy
で関連付いたModelもあります。
要件として関連Modelも削除する必要があり、処理途中にエラーがあった場合明示的にキャッチしたいです。
この時点でdelete/delete_all/destroy_allは使用できません。
そして、本番稼働中のアプリケーションが動いている裏側で削除処理を実行する必要があります。
なので、極端にDBに負荷の掛かる方法はあまり取りたくありません。やり方①:トランザクションを張らずに1件1件にdestroy!を行う
animals = Animal.where(type: 'dog') # 削除対象のデータ抽出 animals.each do |animal| animal.destroy! end最もシンプルに考えるとこのようなコードになると思います。
ただし、2点問題点があります。
- 10万回destroyが走り続けるので負荷が大きい。
- 削除処理の途中でエラーが発生し処理が落ちた場合、それまでに削除されたデータはロールバックせずに削除されたままとなる。(やり直しが効かない。)
そのため、DBに余力がある場合やアプリケーションに明確な閉局時間が決まっている場合かつ、削除が途中で失敗際にロールバックしなくても問題ない・データ整合性を問わない場合はこの方法で良いかと思います。
やり方②:トランザクションを張った上で1件1件にdestroy!を行う
animals = Animal.where(type: 'dog') # 削除対象のデータ抽出 ActiveRecord::Base.transaction do animals.each do |animal| animal.destroy! end end①のやり方の削除処理を1トランザクションとしました。
1トランザクションとすることで削除処理の途中でエラーが発生した場合、削除処理は全てロールバックしやり直しが効きます。
そのため、この方法を使えばデータ整合性が求められるデータを安全に一括削除することが出来ます。1点注意点として、トランザクションを明示的に張る場合は必ずdestroy!を使用しなければなりません。
destroyはエラーが発生しても例外を発生させずにfalseを返すだけなので、処理が止まらずトランザクションから抜けないためです。やり方③:トランザクションを張った上で1000件毎にデータを抽出し、destroy!を行う。削除処理の合間に0.1秒のsleepを入れる。
animals = Animal.where(type: 'dog') # 削除対象のデータ抽出 ActiveRecord::Base.transaction do animals.in_batches.each do |delete_target_animals| delete_target_animals.map(&:destroy!) sleep(0.1) end end上記のやり方が今回採用した方法です。
ActiveRecord::Relation#in_batchesメソッドを使用して、10万件のレコードを1000件単位でまとめて取得し、その塊の1件ずつに対してdestroy!を行います。
そして、1000件毎の削除処理が完了するとsleep(0.1)
で0.1秒処理を停止し、DBへの負荷を軽減させます。
また、外側に大きなトランザクションを張っているので、削除処理の途中でエラーが発生しても全てロールバックされやり直しが効くので安全です。※補足:in_batchesメソッドの件数変更方法
in_batchesメソッドはof
オプションを指定することで、取得する件数を変更できます。
デフォルトでは1000件です。
下記のように記載すれば10000件毎に処理を行います。animals = Animal.where(type: 'dog') # 削除対象のデータ抽出 ActiveRecord::Base.transaction do animals.in_batches(of: 10000).each do |delete_target_animals| delete_target_animals.map(&:destroy!) sleep(0.1) end endおわりに
大量データの削除に関しては、要件次第で様々な方法があると思います。
そのため、ベストプラクティスがあれば是非伺いたいです(o。_。)oペコッ