20200529のRubyに関する記事は28件です。

[JavaScript]入力フォーム漏れがあった時のアラート表示方法

はじめに

RailsでECサイトを作成しています。
カートに商品を追加する時に、数量選択がなかった場合に以下のようなポップアップを表示させたいと思いました。今回はその備忘録です。
スクリーンショット 2020-05-29 23.12.14.png

補足情報

  • 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;
    }
  })
});

参考サイト

JavaScriptでフォームの入力チェックをする方法
【JavaScript】 空文字のチェック方法【勉強中】

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初心者】この記事を見ながら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
=> 1

getsは文字列で出力されます。

数字にした配列を出力する方法

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?
=> false

even?は偶数か判別して
odd?は奇数か判別します。

変数の長さを確認する

a = "aaaa"
=> "aaaa"

a.size
=> 4

a.length
=> 4

a = [1,2]
=> [1, 2]

a.size
=> 2

a.length
=> 2

sizeと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(:+)
=> 9

sumと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
end

trueまでループし続ける方法

x = 2
yen = 100
count = 0

while yen > x do
  yen += yen / 100
  count = count + 1
end

puts count

whileは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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初心者】記事を見ながら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
=> 1

getsは文字列で出力されます。

数字にした配列を出力する方法

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?
=> false

even?は偶数か判別して
odd?は奇数か判別します。

変数の長さを確認する

a = "aaaa"
=> "aaaa"

a.size
=> 4

a.length
=> 4

a = [1,2]
=> [1, 2]

a.size
=> 2

a.length
=> 2

sizeと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(:+)
=> 9

sumと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
end

trueまでループし続ける方法

x = 2
yen = 100
count = 0

while yen > x do
  yen += yen / 100
  count = count + 1
end

puts count

whileは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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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.js

2. application.jsに下記コード追加

app/javascript/packs/application.js
import "bootstrap"

3. webpack/environment.jsを修正

config/webpack/environment.js
const { environment } = require('@rails/webpacker')
const webpack = require("webpack")
environment.plugins.append("Provide", new webpack.ProvidePlugin({
    $: 'jquery',
    jQuery: 'jquery',
    Popper: ['popper.js', 'default']
}))

module.exports = environment

4. 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/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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.js

2. application.jsに下記コード追加

app/javascript/packs/application.js
import "bootstrap"

3. webpack/environment.jsを修正

config/webpack/environment.js
const { environment } = require('@rails/webpacker')
const webpack = require("webpack")
environment.plugins.append("Provide", new webpack.ProvidePlugin({
    $: 'jquery',
    jQuery: 'jquery',
    Popper: ['popper.js', 'default']
}))

module.exports = environment

4. 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/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】セレクトボックスを活用した多階層構造データの表示について

経緯

とあるプログラミングスクール受講生です。
存知の方も多いでしょう「フリマクローンサイト」作成にあたり、出品カテゴリーをajaxで実装しましたので、自分の頭の整理を兼ねてまとめていきたいと思います。

なお、カテゴリーの設定は「ancestry」というGemを使用しております。
今回の記事につきましては、カテゴリー設定後を想定しております。

※説明が必要ない方はコードのみ追ってもらえれば実装できると思います。

完成イメージ

category.gif

コード

ビュー(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.rb
 resources :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.jbuilder
json.array! @category_children do |child|
  json.id child.id
  json.name child.name
end
get_category_grandchildren.json.jbuilder
json.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様(とてもわかりやすい記事でした。ありがとうございました!!)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby on rails】rails db:migrate:resetコマンドでFATAL: Listen error: unable to monitor directories for changes.と表示される。【Rails tutorial】

エラーについて

以下コマンド実行時にエラーが発生。

rails db:migrate:reset
FATAL: 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_instances 
128

128から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

無事にコマンドが通りました!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby ハッシュについて

ハッシュとは

ハッシュとは、キーと値の組み合わせでデータを管理するオブジェクトです。
配列は複数の値を並べて管理するのに対して、ハッシュはそれぞれの値にキーと呼ばれる名前をつけて管理します。

ハッシュの作成

ハッシュを作成する場合は、以下のような構文(ハッシュリテラル)を使用します。 

index.rb
#キーと値の組み合わせを2つ格納するハッシュ
{キー1 => 1, キー2 => 2}

(例)
キーと値の間は、=>で繋ぎます。要素と要素はコンマ(,)で区切ります。

index.rb
{"fruit" => "apple", "color" => "red}

ハッシュは、Hashクラスのオブジェクトです。
コンソール上で空のハッシュ{}を作成し、そのクラス名を確認すると、Hashとなっていることがわかります。

{}.class

結果

=> Hash

ハッシュを変数に代入

ハッシュを変数に代入できます。
(例)

index.rb
fruit = {"apple" => "red", "lemon" => "yellow", "melon" => "green"}
puts fruit

結果

{"apple" => "red", "lemon" => "yellow", "melon" => "green"}

要素の出力

ハッシュの各要素の値は、対応するキーを使って出力することができます。

(例)

index.rb
fruit = {"apple" => "red", "lemon" => "yellow", "melon" => "green"}
puts fruit["apple"]

結果

red
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

マイグレーション入門

マイグレーション入門

マイグレーションとは

text
マイグレーションは、データベーススキーマの継続的な変更を、簡単に行なうための便利な手法です。マイグレーションではRubyのDSLを使っているので、生のSQLを作成する必要がなく、スキーマへの変更をデータベースの種類に依存せずに済みます。

なるほど本来であればテーブルを追加したときや、テーブルに対して属性を追加した時などは
SQLを作成する必要がありますよね。

CREATE TABLE HOGE

こんな感じで生のSQLを書いてデータベースへの変更をするがそれをする必要がなくなります。
またデータベースの種類に依存しません。
なんとなく使ってたけどすごいよねー。

マイグレーションの例

productsというテーブルを追加する例です。

nameというstringカラムと、descriptionというtextカラムが含まれています。主キーはidという名前で暗黙に追加されます。idはActive Recordモデルにおけるデフォルトの主キーです。

ruby.rb
class 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.rb
class AddPartNumberToProducts < ActiveRecord::Migration[5.0]
  def change
    add_column :products, :part_number, :string
  end
end

DBに反映される

これまでのマイグレーションファイルをDBに反映させます。

#実行
rails db:migrate
#ロールバック
rails db:rollback

以上のようにしてマイグレーションを実行、またはロールバックができるというわけですね。

ざっくりとですがマイグレーションについてまとめてみました。
今までなんとなく使っていた私ですが、
どういったものなのか少しずつ理解できるようになってきました。

本日は以上です
1人前のエンジニアになるまであと92日

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】TwitterのOGPを設定する

Gemなしです。

TwitterのOGPを表示させるために必要なmetaタグについては以下の記事を参考にしました。
【2020年版】Twitterカードとは?使い方と設定方法まとめ

実装

app/helper/application_helper.rb
def 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}"
end
app/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")) %>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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. 単純な繰り返しの場合

DraperActive 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>

まとめ

という感じで、いくつも書き方は思い浮かんでしまい、逡巡することになります。
この内、どれがいいかは場況を見るしかありません。
自分ではあまり書いたことのない書き方があれば、まずはぜひ手を動かして書いてみてください。
とりあえず書いてみて、数週間後の仕様変更や、同僚からのコメントで書き方が適切だったかどうかが試されます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】whenever による定時バッチ処理

【Rails】whenever による定時バッチ処理

Wheneverを使ってRailsでのバッチ処理を実装したのでまとめます。

目次


動作環境

OS : macOS Mojave 10.14.6
ruby : 2.6.3p62
rails : 5.2.4

やりたいこと

決まった時間に定期的に実行するタスクを作成したい

手順

wheneverのインストール

Gemfile
gem '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_cleaner
class 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
test

schedule.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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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

今後実装される方のヒントになれば幸いです。久々にハマった....

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

阿笠博士の秘密

あがせ博士はワインずき

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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.coffee

coffee scriptの影響を疑い、coffeeファイルを削除すると、イベント発火するようになった。

もう一回、railsを再起動するとエラー。

LoadError: cannot load such file -- coffee_script 

cofee-railsをuninstall

Gemfile
# gem 'coffee-rails', '~> 4.2'
terminal
bundle install
terminal
rails tmp:cache:clear

解決!

参考

https://qiita.com/yakimeron/items/7945a1350bd4b8c2438b

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

awdkma

dwaoda

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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]]

さいごに

配列を配列のまま結合する方法をまとめた記事を見つけられなかったので、自分でまとめてみました。
間違い等ありましたら教えてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.markdown

index.markdown と about.markdown はとりあえず不要なので削除しましょう。

設定ファイルを編集する

_config.yml を開き以下の通りに上書きします。

_congig.yml
title: 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 s

listening 的な感じのやつが出てきたら、以下にアクセスしてみましょう。
以下のように表示されたら成功です。

http://localhost:4000/
image.png

http://localhost:4000/yyyy/mm/dd/hello-world.html (yyyy, dd, mm に適宜 mdファイル名の数字を入れる)
image.png

さいごに

今回はテーマ作成ということで、レイアウトや liquid タグについて解説しました。
少々省略したり説明不足だったりしますが、日本語のドキュメントが結構しっかりと説明が書いてあったりしますので、是非読んでみてください。

ここがわからない、とか、ここは間違ってる、とか、こういう説明の方がわかりやすい、などありましたら気軽にコメントいただけたらありがたいです。

ここまでで(デザインなどは無視していますが)サイトは一応できています。
ですが、これでは誰でも見れる状態ではありません。
次回は、サイトの公開について、解説していこうと思います。

それではまた今度!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ruby メソッドの省略記法

hoge = ['a', 'b']
hoge.map(&:upcase)=> ['A', 'B']

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 | 68

Bingo.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メソッドなど、知らなかったので使いませんでしたが他の方の解答をみるに、まだまだリファクタリングの余地はありそうです。

ご意見等あればコメントで教えてください。
よろしくお願いします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【NoMethodError】newアクションで発生した際の解決法

概要

登録画面に遷移したいのに、エラーになって全然表示できない:sob:
状況に陥り、何時間も悪戦苦闘した結果、解決しましたので備忘録として残します。

事象

下記のようなエラーが発生:zap:
スクリーンショット 2020-05-29 11.45.21.png

routes.rb
Rails.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
end
controller.html.haml
  def new
    @task = Task.new
  end
main_view.html.haml
#省略
.new_display
  = form_for @task do |f|
#省略

エラーを見た感想

おかしなとこない:disappointed_relieved:
何言ってんねん:persevere:
newアクションだからデータないやんかーーー
と一人で悶々としてました。。。

原因

原因は【form_forが自動的に生成してくれるパスは複数形のみ】ということ!!
エラー文にも記載ありました!

undefind method 'tasks_path' for ~

私は、その箇所を読んでも何言ってんの??そんなパスないよ:triumph:
と思ってましたが:sweat:

解決策

viewを下記のように変更しました!

main_view.html.haml
#省略
.new_display
  = form_for @task, url: group_tasks_path do |f|
#省略

画面

スクリーンショット 2020-05-29 13.40.42.png

似たようなことでお悩みの方の足掛かりになれば幸いです:relaxed:

参考

https://qiita.com/annaaida/items/ae38de82526bf5b4aa73

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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.rb
class User < ApplicationRecord
 devise :database_authenticatable, :registerable,
  :recoverable, :rememberable, :validatable 
                                  #↑削除する。
end

これでdeviseのバリデーションは掛からなくなり、passwordに自分でカスタマイズしたバリデーションが付与されるようになりました。

まとめ

・バリデーションには参照する優先順位があり、
テキストフィールドに与えられているバリデーション>devise>自作のバリデーション
となっている。
・カスタムバリデーションのみ適用させる場合は、text_fieldを使い、deviseの:validatableを削除する。

参考文献

https://qiita.com/kouheiszk/items/215afa01eeaadbd99340

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】ヘルパーメソッド、confimartionの使い方

 最初に

パスワード入力時に確認用で2回パスワードを打ち込む仕様になっているサービスをよく見かける。
同じことを実装しようとした際にconfimartionというRailsにおけるバリデーションのヘルパーメソッドを使用すれば簡単に実装できたため、備忘録として残します。

connfimartionとは

2つのテキストフィールドで受け取る内容が完全に一致する必要がある場合に使います。たとえば、メールアドレスやパスワードで、確認フィールドを使うとします。このバリデーションヘルパーは仮想の属性を作成します。属性の名前は、確認したい属性名に「confirmation」を追加したものになります。
https://railsguides.jp/active
record_validations.html
上記より引用。

要は2つのフォームで同じデータでなければ登録できないバリデーションをかけるメソッドという認識で問題ないかと思います。

実装手順

1.モデルの修正

user.rb
class 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を追加します。
実際に記述したモデルとビューについては下記の様な感じになります。
スクリーンショット 2020-05-29 12.05.37.png

スクリーンショット 2020-05-29 12.03.40.png

以上で2つのフォームに入力された値が同じでなければバリデートされる様になります!
バリデーションを追加したい場合は、確認したい属性にバリデーションを付与すれば良いだけです。

最初実装する際に、
・確認用の属性を作成して、
・二つのフォームに入力がされた値が同じかどうかを確認する様な条件分岐の作成
・同じであれば保存
の様なコードを書かないといけないと思っていたので、confirmationを使うことにより非常に簡単に実装が完了しました。

参考文献

https://railsguides.jp/active_record_validations.html

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

.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 -)"

参照

https://techracho.bpsinc.jp/hachi8833/2019_06_06/66396

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.rb
RSpec.configure do |config|
.
.
.
  config.include RSpec::Rails::MailerExampleGroup
.
.
.

ってやると
お、 ActionMailer::Base.deliveries に積まれるようになった〜〜
よかったよかった

常に読み込ませたくないなら

ActionMailer::Base.deliveries を使いたいrspecの中でのみ

include RSpec::Rails::MailerExampleGroup`

でおk

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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


再度接続!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

herokuデプロイ

データベースをpostgreSQL以外を使っている場合はそのデータベースのgemをコメントアウトして、
Gemfileの一番下に本番環境ではpostgreSQLを使うことを宣言してbundle installする。

Gemfile
# gem 'mysql2', '>= 0.4.4', '< 0.6.0'

group :production do
  gem "pg"
end

jQueryにてテンプレートリテラルを使用した場合は下記の文章をコメントアウトする。

environments/production.rb
# config.assets.js_compressor = :uglifier

あとはherokuで登録が完了していればログインする。

ターミナル
$ heroku login

その後、GitHubで管理しているアプリをheroku上に作成する。

ターミナル
$ heroku create アプリ名

GitHubでcommitとPushをしてmasterにマージを済ました状態で下記のコードを実行する。

ターミナル
$ git push heroku master

heroku上にデータベースを作成する。

ターミナル
$ heroku run rails db:migrate

そして最後にアプリを開く

ターミナル
$ heroku open
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsで大量データを削除する方法と懸念点

はじめに

皆様、こんにちは!
佐久間まゆちゃんのプロデューサーの@hiroki_tanakaです。

私はRailsアプリケーションの保守に関わっているのですが先日、本番環境に大量の不要データが存在していることが判明しました。
それがキッカケでRailsでの大量データの削除方法を検討したので、調べたことをまとめました。

Railsにおけるdestroyとdeleteの違い

まず、Railsには2つのデータ削除メソッドのdestroyとdeleteがあります。
それぞれの違いを簡単にまとめたいと思います。

destroy/destroy!

ActiveRecordを介して指定した1レコードを削除します。
ActiveRecordを介するためcallbackメソッド(before_destroyafter_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ペコッ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む