20191002のRubyに関する記事は30件です。

Rubyの `OpenSSL::BN` で手軽に合同算術(余りの計算)

Rubyの数値クラスは基本的に Numeric のサブクラスだが、そうなっていなく隠れているものがある。それがopensslライブラリ内の OpenSSL::BN 。(他にもあるかもしれないけど)

OpenSSL内で利用される多倍長整数クラスです。

通常多倍長整数を利用するには Bignum を用いてください。

暗号化に利用する計算が目的のため、通常の整数クラスには無いメソッドがいくつか用意されている。そのひとつに剰余をとりながらの演算合同算術)が挙げられる。

合同算術メソッドの一覧

通常の演算子 +, -, *, / (divmod), ** もあり Integer と混ぜて使えるが、それに加えて #mod_xxx というメソッドが用意されている。これらは引数に法(modulus)を指定する。

  • 加算 : #mod_add
  • 減算 : #mod_sub
  • 乗算 : #mod_mul
  • 累乗(冪剰余 : #mod_exp
  • 自乗(平方剰余) : #mod_sqr
  • 逆元(モジュラー逆数 : #mod_inverse

このうち冪剰余とモジュラー逆数は、自力で効率よく計算しようとするとアルゴリズムの知識がそれなりに必要になる。これを標準ライブラリに任せられるのは嬉しい。

なお、冪剰余についてはRuby2.5から Integer#pow でもできるようになった。モジュラー逆数は以前に自作してみたことがあるが、このクラスを知っていたらわざわざやらなかったかも。

冪剰余とフェルマーの小定理
a = 12345   # any integer
n = 6700417 # prime number

# OpenSSL::BN
require 'openssl'

a.to_bn.mod_exp(n, n).to_i #=> a % n

# Integer (built-in class)
a ** n % n  #=> NaN (warning: in a**b, b may be too big)
a.pow(n, n) #=> a % n
モジュラー逆数
63 * 27 % 100 == 1

# OpenSSL::BN
require 'openssl'

63.to_bn.mod_inverse(100).to_i #=> 27
64.to_bn.mod_inverse(100).to_i #=> OpenSSL::BNError: no inverse

# https://www.rubydoc.info/gems/numeric_inverse
require 'numeric_inverse'
using NumericInverse

63.inv(100) #=> 27
64.inv(100) #=> ArgumentError: modulus 100 is not coprime to 64

利用例:RSA暗号の復号の計算

最近計算する機会があったので、 OpenSSL::BN でもやってみる。主な変数は以下の通り。

  • 公開鍵 (n, e) は誰でも手に入る
    • n は2つの素数の積
    • e は適当な数1(※満たすべき条件はある)
  • 秘密鍵 (n, d) は鍵ペアの作成者しか知らない
    • n は公開鍵と同じ2
    • d は e と対になる数
  • m と c はそれぞれ平文と暗号文(を数値化したもの)
    • m から c を求めるのが暗号化で、公開鍵によって誰でも可能
    • c から m を求めるのが復号で、秘密鍵によって鍵ペアの作成者のみ可能
      • 秘密鍵の d を知らずに復号できたら暗号解読成功

Wikipedia英語版にある例で復号を試す。公開鍵しか知らない状態でも、 n を素因数分解した2つの素数がわかれば、秘密鍵を求められる。

require 'openssl'

n, e = 3233, 17 # public key (n, e)
c = 2790 # ciphertext

# discover that 3233 == 61 * 53
# --> get a private key (n, d)
l = [61 - 1, 53 - 1].inject(:lcm)
d = e.to_bn.mod_inverse(l).to_i #=> 413

# decrypt
m = c.to_bn.mod_exp(d, n).to_i #=> 65 (plaintext)

# encrypt again
c = m.to_bn.mod_exp(e, n).to_i #=> 2790 (ciphertext)

  1. 固定値 65537 (0x10001) になっていることが多い 

  2. 理論上は n だけでいいが、実際の秘密鍵には元となった2つの素数も記録されている。 

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

【Rspec】attributes_forってなんだ...

はじめに

ポートフォリオの機能を一通り実装し終えたので、Everyday Railsで勉強をしながらRspecでテストを書き始めました。

モデルスペックを書き終え、コントローラスペックを書いている際に「attributes_for」に出会い、ちょっと詰まったので記事にします。

attributes_forとは

Everyday Railsによると、「プロジェクトファクトリからテスト用の属性値をハッシュとして作成します。」、だそうです。

最初の感想としては、「なんのために???buildでよくない???」でした。

まずは見てみよう

buildとattrubutes_forの違いを以下に示します。

まずはFactoryの中身です。

users.rb
FactoryBot.define do
  factory :user do
    name "test"
    sequence(:email) { |n| "test#{n}@test.com"}
    password "password"
  end
end
posts.rb
FactoryBot.define do
  factory :post do
    content 'test content'
    association :user
  end
end

これらを用いたbuildとattributes_forで取得するデータの違いが以下の通りです。

build
> FactoryBot.build(:post)
=> #<Post id: nil, content: "test content", user_id: 8, created_at: nil, updated_at: nil> 
attributes_for
> FactoryBot.attributes_for(:post)
=> {:name=>"test", :email=>"test2@test.com", :password=>"password"}  

buildはモデルオブジェクト、attributes_forはハッシュとなっていますね。(間違えてたら教えてください)

つまり

パラメータとして値を渡す場合、ハッシュを使って渡すことができます。複数のパラメータを送信する場合に、個々にパラメータ名を付けるのではなく、キーと値を組み合わせたハッシュとして渡せます。

これは実際に見てもらったほうがわかりやすいと思います。

posts_controller_spec.rb
#省略

describe "#create" do
    context "ログインユーザーとして" do
      before do
        @user = FactoryBot.create(:user)
      end

      it "投稿ができる" do
          post_params = FactoryBot.attributes_for(:post)
          sign_in @user
          expect{
            post :create, params: { post: post_params}
          }.to change(@user.posts, :count).by(1)
      end
    end
  end

このプログラムの、post :create, params: { post: post_params}に注目してください。
これは、

post :create, params: { post: {:name=>"test", :email=>"test2@test.com", :password=>"password"} }

このプログラムと同義となります。
つまり、POSTメソッドで渡すparamsにpostキーを渡し、そのpostキーのValueをattributes_forの返り値であるハッシュを渡すことで、このように簡潔に書くことができます。(説明下手ですみません...)

上記より、buildではなくattributes_forを使用している意味がわかりました。

最後に

私はまだ業界未経験で、なおかつRspecを書き始めて2日目です。
間違えている部分などありましたら、教えてもらえると非常に助かります。

以上です。

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

rbenvでのruby2.6.1インストール時「Agreeing to the Xcode/iOS license requires admin privileges」で失敗

ruby 2.6.1をrbenvを用いてインストールしようとしたときになぜか落ちたので共有。

落ちた箇所

$ rbenv install 2.6.1                                                                                                                              
Wed Oct  2 15:02:22 2019
Downloading openssl-1.1.0j.tar.gz...
-> https://dqw8nmjcqpjn7.cloudfront.net/31bec6c203ce1a8e93d5994f4ed304c63ccf07676118b6634edded12ad1b3246
Installing openssl-1.1.0j...

BUILD FAILED (OS X 10.14.6 using ruby-build 20190423)

Inspect or clean up the working tree at /var/folders/y4/jzf9wps144x1w4hp2kwp359xrk7xs7/T/ruby-build.20191002150224.4456
Results logged to /var/folders/y4/jzf9wps144x1w4hp2kwp359xrk7xs7/T/ruby-build.20191002150224.4456.log

SIXTY_FOUR_BIT_LONG mode

Configured for darwin64-x86_64-cc.

Agreeing to the Xcode/iOS license requires admin privileges, please run “sudo xcodebuild -license” and then retry this command.

こいつが怪しい。

Agreeing to the Xcode/iOS license requires admin privileges, please run “sudo xcodebuild -license” and then retry this command.

Xcodeのライセンスがどーのと言われているっぽい...

素直にコマンドを実行

エラー文章通りコマンドを実行する。

$ sudo xcodebuild -license

You have not agreed to the Xcode license agreements. You must agree to both license agreements below in order to use Xcode.

すると、ずらーーーーーーーーっとライセンスの情報が出ます(文章を共有するのはやめときます)

q で抜けると、「agree」, 「print」, 「cancel」の3つから操作を求められるので、ターミナル上で「agree」と入力しEnterを押す。

By typing 'agree' you are agreeing to the terms of the software license agreements. Type 'print' to print them or anything else to cancel, [agree, print, cancel]  agree

# agreeを入力してEnter

You can view the license agreements in Xcode's About Box, or at /Applications/Xcode.app/Contents/Resources/English.lproj/License.rtf

すると、インストールできる。

rbenv install 2.6.1                                                                                                                      28.2s  Wed Oct  2 15:03:58 2019
ruby-build: use openssl from homebrew
Downloading ruby-2.6.1.tar.bz2...
-> https://cache.ruby-lang.org/pub/ruby/2.6/ruby-2.6.1.tar.bz2
Installing ruby-2.6.1...
ruby-build: use readline from homebrew
Installed ruby-2.6.1 to /Users/xxxxxx/.rbenv/versions/2.6.1

なぜライセンス同意をいまさら求められたのかはよくわかりませんが、インストールできたのでOK。

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

【Rails】ページ内で読み込みたいCSSを�制御する

ソースコードを見ると/app/assets/stylesheets/以下にあるファイルを読み込む記述が自動で書き出されている。
その記述に関してあれこれ変更したかったのでやってみました。

一応環境情報を載せておきます。
ruby:2.6.4
rails:5.2.3

目的

1.ページ内に自動記述されるCSSの読み込み順番を変更したい。
2.SASS使用のためのimportさせるファイルは読み込ませたくない。

方法

自動記述の制御はある程度/app/assets/stylesheets/application.scss内で調整ができる。

application.scss(初期状態)
 /*
  * 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_tree .
  *= require_self
  */

1.ページ内に自動記述されるCSSの読み込み順番を変更したい。

下部にある*= require_tree .から*= require_selfまでを変更する。
アセットパイプラインの仕組みにより、ここで読み込みに関して調整可能。
この仕組みはディレクティブと呼ばれている。
(※デフォルトだとアルファベット順?)

今回は最下部に記述されるapplication.cssを最上部にしたいので以下のように記述しました。

application.scss(変更後)
 *= require_self
 *= require_tree .

変更点は、単純に記述の順番を入れ替えただけ。

*= require_tree .はインクルードさせる記述。
.とあるので/app/assets/stylesheets/を指定していることになる?)

*= require_selfは正直よくわからなかった・・・。勉強します。

例えばhoge01.csshoge02.css...の順で絶対並べたい!という場合は、

application.scss
 *= require_self
 *= require hoge01.css
 *= require hoge02.css
 *= require_tree .

のように書いてあげればOK。

ただし、今の状態だと/app/assets/stylesheets/以下全てのファイルが自動記述されてしまうので、次で調整する。

2.SASS使用のためのimportさせるファイルは読み込ませたくない。

自分はSASSで使用する変数ファイルなどを/app/assets/stylesheets/partial/以下に格納しました。
ひとまずサブディレクトリは読み込まれないようにします。

application.scss(変更後)
 *= require_self
 *= require_directory .

*= require_tree . → *= require_directory .
に変更するだけ。

*= require_directory ./app/assets/stylesheets/pretial/直下にあるファイルだけ読み込むよ、というもの。

自動でやってくれるのはとても便利ですので、なおさら制御方法は知っておかなきゃいけないなと痛感。
まだまだRails開発を始めたばかりなので、今後深掘りしていこうと思います。

参考サイト

RailsでCSSの読み込む順番を制御する方法 - Qiita
Rails 使用するCSSを指定する | | KeruuWeb
Railsのマニフェストファイルを使ったJsとStylesheetの呼び出し - Qiita

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

[質問です。]rubyのapiの叩き方について

rubyでapiを叩きたい

現在プログラミング歴1ヶ月でlinebotを作成しながら勉強しています。

ぐるなびAPIをlinebotに使用したい

ぐるなびAPIをlinebotに使用する所でつまづいており、皆様にご質問があります。

下記エラーが解決出来ません
NoMethodError (undefined method `sample' for nil:NilClass):

該当コードは

linebot_controller.rb
events.each { |event|
      if event.message['text'] != nil
        place = event.message['text'] #ここでLINEで送った文章を取得
        result = `curl -X GET https://api.gnavi.co.jp/RestSearchAPI/v3/?keyid=ffb92f0f997a628153ecfa407099fe9b&category_s=RSFST08008&category_s=RSFST08009&#{place}`
      else
        latitude = event.message['latitude']
        longitude = event.message['longitude']

        result = `curl -X GET https://api.gnavi.co.jp/RestSearchAPI/v3/?keyid=ffb92f0f997a628153ecfa407099fe9b&category_s=RSFST08008category_s=RSFST08009&latitude=#{latitude}longitude=#{longitude}`#ここでぐるなびAPIを叩く
      end

      hash_result = JSON.parse result  #レスポンスが文字列なのでhashにパースする
      shops = hash_result["rest"] #ここでお店情報が入った配列となる
      shop = shops.sample #任意のものを一個選ぶ

hash_result = JSON.parse result 部分まではしっかり値が入っているのですが、
shops = hash_result["rest"]   ←この部分がnilで返ってきてしまいエラーを吐き出してしまいます。
文法のミス等自分なりにapiを叩く記述を参考にしながら行なったのですが解決出来ません。
皆様のお力をお貸しいただけると幸いです。

以下参考記事
https://qiita.com/NoharaMasato/items/6fb1ac277c965905e019

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

(質問です)rubyのapiの叩き方について

rubyでapiを叩きたい

現在プログラミング歴1ヶ月でlinebotを作成しながら勉強しています。

ぐるなびAPIをlinebotに使用したい

ぐるなびAPIをlinebotに使用する所でつまづいており、皆様にご質問があります。

下記エラーが解決出来ません
NoMethodError (undefined method `sample' for nil:NilClass):

該当コードは

linebot_controller.rb
events.each { |event|
      if event.message['text'] != nil
        place = event.message['text'] #ここでLINEで送った文章を取得
        result = `curl -X GET https://api.gnavi.co.jp/RestSearchAPI/v3/?keyid=ffb92f0f997a628153ecfa407099fe9b&category_s=RSFST08008&category_s=RSFST08009&#{place}`
      else
        latitude = event.message['latitude']
        longitude = event.message['longitude']

        result = `curl -X GET https://api.gnavi.co.jp/RestSearchAPI/v3/?keyid=ffb92f0f997a628153ecfa407099fe9b&category_s=RSFST08008category_s=RSFST08009&latitude=#{latitude}longitude=#{longitude}`#ここでぐるなびAPIを叩く
      end

      hash_result = JSON.parse result  #レスポンスが文字列なのでhashにパースする
      shops = hash_result["rest"] #ここでお店情報が入った配列となる
      shop = shops.sample #任意のものを一個選ぶ

hash_result = JSON.parse result 部分まではしっかり値が入っているのですが、
shops = hash_result["rest"]   ←この部分がnilで返ってきてしまいエラーを吐き出してしまいます。
文法のミス等自分なりにapiを叩く記述を参考にしながら行なったのですが解決出来ません。
皆様のお力をお貸しいただけると幸いです。

以下参考記事
https://qiita.com/NoharaMasato/items/6fb1ac277c965905e019

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

【小学生でもわかる】Ruby 戻り値 return 

【Ruby】戻り値 returnとは

今流行りのキャッシュレス決済になぞらえてわかりやすく解説

sample.rb
def discount(price)
  return price * 0.95
end

cashless = discount(5000)

puts "キャッシュレス決済で5%還元セール実施中!"
puts "#{price}円の買い物が実質#{cashless}円に!"

解説

「もともと5000円のものがキャッシュレス決済を利用すると5%オフになって実質4750円になる」という文

「このひみつ道具を使うとpriceが自動的に5%オフになるんだよ〜」みたいな状況
(discountという名前のメソッド)
(どらえもんのガリバートンネル的な)

pirce:元の値段
discount:自動で5%オフしてくれるシステムみたいなもの(メソッド)
cashless:計算結果

まとめ

cashless = priceにdiscountメソッドを適用した結果

【番外編】複数の戻り値をネットショッピングを例に解説

「〇〇円以上のお買い物だと送料無料」みたいなことよくありますよね

sample.rb
def online_shopping(price)
  if price >= 2000
    return price
  end
  return price + 220
end

puts "合計金額は1500円です"
puts "金額は、送料込みで#{online_shopping(1500)}円です"
puts "-----------"
puts "商品の合計金額は10000円です"
puts "金額は、送料込みで#{price_with_shipping(10000)}円です"

結果

合計金額は1500円です"
お支払い金額は、送料込みで1720円です"

あるいは

合計金額は10000円です"
お支払い金額は、送料込みで10000円です"

と表示される

解説

これは2000円以上のお買い物で送料が無料になる文。

2000円以上の時はそのままpriceの値が表示されるようにする、
それ以下の時はpriceに送料である220円を足した値が表示されるようにする

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

presence メソッド

presenceメソッドは

def presence
  self if present?
end

で定義されている

使い所

例えば、site_titleというWebページのタイトルを返すメソッドの場合、このような実装になるかと思います。

def site_title(title)
  title.present? ? title : 'サイト名'
end

これをpresenceを使うと下記のようになります。

def site_title(title)
  title.presence || 'サイト名'
end

REFERENCE

http://www.monokoto.xyz/rails-presence/

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

binding.pryの使い方

デバッグツールのbinding.pryの基本的な使い方の備忘録

  1. gemfileにbinding.pryを記述
  2. ターミナルでbundle insatall を実施する
  3. 動作を止めたいところ(変数の中身を確認したいところ)に挿入する。
user_contorller.rb
def login
    @user = User.find_by(
      email:params[:email],
      password:params[:password]
    )
    binding.pry
    if @user
      flash[:notice] = "ログインしました"
      redirect_to action: :index
    else
      @error_message = "メールアドレスまたはパスワードが間違っています"
      @email = params[:email]
      @password = params[:password]
      render action: :login_form
    end  
  end

アクションに該当する動作を実施するとターミナルに以下のように表示される。

    40: def login
    41:   @user = User.find_by(
    42:     email:params[:email],
    43:     password:params[:password]
    44:   )
    45:   binding.pry
 => 46:   if @user
    47:     flash[:notice] = "ログインしました"
    48:     redirect_to action: :index
    49:   else
    50:     @error_message = "メールアドレスまたはパスワードが間違っています"
    51:     @email = params[:email]
    52:     @password = params[:password]
    53:     render action: :login_form
    54:   end  
    55: end

あとはterminalで確認したい中身の変数をたたけば確認できる!


[1] pry(#<UsersController>)> @user
=> nil
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【小学生でもわかる】Ruby メソッド

【Ruby】でメソッドを使用する

sample.rb
def introduction(hobby)
  puts "はじめましてよろしくお願いします"
  puts "趣味は#{hobby}です"
end

「自己紹介の場面が来たらこうやって言おう!」というフォーマット
「でも趣味は場面に応じて言い換えよう」と考えているみたいですね

sample.rb
# 当たり障りないこと言っておこう...って時
introduction("映画鑑賞")

# 女の子ウケが良さそうなこと言おう...って時
introduction ("タピオカ旅")

結果

はじめましてよろしくお願いします
趣味は映画鑑賞です

あるいは

はじめましてよろしくお願いします
趣味はタピオカ旅です

などと言い換えることができました。

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

【小学生でもわかる】Ruby メソッド 引数

【Ruby】でメソッドを使用する

sample.rb
def introduction(hobby)
  puts "はじめましてよろしくお願いします"
  puts "趣味は#{hobby}です"
end

「自己紹介の場面が来たらこうやって言おう!」というテンプレート
「でも趣味は場面に応じて言い換えよう」と考えているみたいですね

sample.rb
# 当たり障りないこと言っておこう...って時
introduction("映画鑑賞")

# 女の子ウケが良さそうなこと言おう...って時
introduction ("タピオカ旅")

結果

はじめましてよろしくお願いします
趣味は映画鑑賞です

あるいは

はじめましてよろしくお願いします
趣味はタピオカ旅です

などと言い換えることができました。

まとめ

引数とは
「ある程度用意したテンプレートに場合に応じていろんなもの入れるぜ!」
というもの

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

クラス、インスタンスのまとめ

今勉強しているrubyのクラス、インスタンスについてまとめていきます。間違いなどございましたら教えてください!

クラス

メソッドなどのいろんな処理を入れておく箱のようなものです。
このクラスを定義して、rubyに新しい定義を追加することができます。
定義方法は

work.rb
class Name
#処理
end

このような形です。クラス名は大文字で始めることに注意します。

インスタンス

まずクラスに処理を書いていきます。

work.rb
class Name
  def initialize
    @name = "ヤマモト"
  end
  def a
    @name + "タロウ"
  end
end

initializeメソッドとはインスタンスを作った際に自動で呼び出されるメソッドです。
aで定義している部分をインスタンスメソッドと呼びます。
@から始まっているのはインスタンス変数と呼び、ローカル変数とは違ってクラス内の全メソッドで共通して使うことができます。
上記の処理を実際に行うためにはインスタンスを生成して、クラスを呼び出す必要があります。
インスタンスの生成にはnewメソッドを用います。

work.rb
class Name
  def initialize
    @name = "ヤマモト"
  end
  def a
    @name + "タロウ"
  end
end

pon = Name.new
puts pon.a # => ヤマモトタロウ

上記のようにnewを使ってインスタンスを生成した後、変数ponにインスタンスを結んでます。
そして最後に出力される処理を書きました。

以上がクラスとインスタンスについてまとめたものです。

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

【Ruby】Slack APIを使用してメッセージを送信する

前提

この記事は以下の前提で書きます。
まだの方はこちらの記事を参考にして準備を進めてください。

  • Slack APIで使用するトークンを取得済みであること
  • APIにメッセージ送信の権限を与えていること

準備

slack-ruby-clientをインストールします。
下記コマンドを実行するか、Gemfileに追記します。

$ gem install slack-ruby-client

or

Gemfile
gem 'slack-ruby-client'

ソースコード

slack.rb
require 'slack-ruby-client'

Slack.configure do |config|
  # APIトークンを設定
  config.token = 'xoxp-xxxxxxxxxxxx-xxxxxxxxxxxx-xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
end

# APIクライアントを生成
client = Slack::Web::Client.new

# #チャンネル名 of @ユーザー名
channel = '#slack-test'

# メッセージ
text = 'Hello World'

response = client.chat_postMessage(channel: channel, text: text, as_user: false)

pp response

実行してみる

$ ruby slack.rb
{"ok"=>true,
...

送信できた。
スクリーンショット 2019-09-13 0.08.59.png

参考

slack-ruby/slack-ruby-client

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

[初心者]Rails6にbootstrap4を導入する

Ruby on railsを学び始めて約2ヶ月、
初めてbootstrapを導入したので、記事を作ってみました。


環境

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.14.6
BuildVersion:   18G95

$ ruby -v
ruby 2.6.1p33 (2019-01-30 revision 66950) [x86_64-darwin18]
$ rails -v
Rails 6.0.0

目次

  1. bootstrapgemを追加
  2. applicationファイルを編集
  3. index.html.erbを編集
  4. bootstrapが導入されているかを確認

1.bootstrap gemを追加

Gemfileに以下を追加

Gemfile
~
gem 'bootstrap', '~> 4.1.1'
gem 'jquery-rails'
~

terminalbundle installを実行
(ローカルでgemを管理している場合は、bundle install --path=vendor/bundleで実行)

これで、プロジェクト内にbootstrapのgemが追加されました。

2.applicationファイルを編集

2.1 application.cssの編集

app>assets>stylesheets>application.cssをエディタで開き、@import "bootstrap";を追加

application.css
~
@import "bootstrap";
~

次に拡張子を.cssから.scssへ変更
application.css → application.scss

2.2 application.jsの編集

app>assets>javascript>application.jsをエディタで開き、//= require bootstrapを追加
(フォルダ、ファイルがなければ作成)
bootstrapはjqueryとpopperに依存しているらしいので導入

application.js
//= require jquery3
//= require popper
//= require bootstrap-sprockets

3.index.html.erb を編集

適当なcontrollerに以下のコードを追加
app>assets>views>コントローラ名>index.html.erb
このコードはbootstrapのサイトにあるテストコードです。

index.html.erb
<a class="btn btn-primary" href="#" role="button">Link</a>
<button class="btn btn-primary" type="submit">Button</button>
<input class="btn btn-primary" type="button" value="Input">
<input class="btn btn-primary" type="submit" value="Submit">
<input class="btn btn-primary" type="reset" value="Reset">

4.動作確認

下準備は整いました。

bundle exec rails sでローカルにサーバーを立ち上げます。
ブラウザにlocalhost:3000/コントローラ名/indexを入力し、画面を確認してください。
以下の表示になっていれば完了です。
スクリーンショット 2019-10-02 19.11.36.png

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

ISUCON9予選感想戦

はじめに

本記事では、ISUCON9の予選問題をローカル環境で実行しながら振り返りを行います。言語はrubyで行います。

環境構築

環境構築には以下のリポジトリのVagrantfileを使用します。
https://github.com/matsuu/vagrant-isucon

本番との違いはREADME.mdにも記載のある通り、スペックと台数です。

初期実装のスコア

実装をrubyに切り替えてベンチマークを実行します。

  • 実装の切り替え
$ sudo systemctl stop    isucari.golang.service
$ sudo systemctl disable isucari.golang.service
$ sudo systemctl start   isucari.ruby.service
$ sudo systemctl enable  isucari.ruby.service
  • ベンチマークの実行

ベンチマークは本番と同様にNginxを通して実行したいので、target_urlを指定して実行します。

$ ./bin/benchmarker -target-url https://127.0.0.1

実行結果は以下の通りです。

{"pass":true,"score":2010,"campaign":0,"language":"ruby","messages":[]}

アクセスログを解析する

alp v0.4.0を使用してアクセスログの解析をします。
https://github.com/tkuchiki/alp

v1.0.0もありますが、事前準備の段階でv1.0.0が上手く動かなかったため、v0.4.0を使用しました。

$ sudo wget https://github.com/tkuchiki/alp/releases/download/v0.4.0/alp_linux_amd64.zip
$ sudo unzip alp_linux_amd64.zip
$ sudo mv alp /usr/bin/

Nginxのログをalpで解析できる形式にします。
nginx.confを修正したらNginxを再起動する必要があります。

nginx.conf
log_format ltsv "time:$time_local"
            "\thost:$remote_addr"
            "\tforwardedfor:$http_x_forwarded_for"
            "\treq:$request"
            "\tstatus:$status"
            "\tmethod:$request_method"
            "\turi:$request_uri"
            "\tsize:$body_bytes_sent"
            "\treferer:$http_referer"
            "\tua:$http_user_agent"
            "\treqtime:$request_time"
            "\tcache:$upstream_http_x_cache"
            "\truntime:$upstream_http_x_runtime"
            "\tapptime:$upstream_response_time"
            "\tvhost:$host";
access_log /var/log/nginx/access.log ltsv;

この状態で改めてベンチマークを実行し、アクセスログを解析します。
集計の正規表現はalpの結果を見ながら適当に足して行きました。

$ sudo cat /var/log/nginx/access.log | alp --aggregates="/users/\d+.json","/items/\d+.json","/new_items/\d+.json","/upload/.+.jpg","/transactions/\d+.png"

結果は以下のように表示されます。
大きな時間を取っているGET /users/transactions.jsonGET /new_items/\d+.jsonから対応していく方針になります。

+-------+--------+-------------------------------------+-----+------+-----+-----+-----+-------+-------+---------+-------+-------+-------+-------+--------+------------+------------+--------------+------------+
| COUNT | METHOD |                 URI                 | 1XX | 2XX  | 3XX | 4XX | 5XX |  MIN  |  MAX  |   SUM   |  AVG  |  P1   |  P50  |  P99  | STDDEV | MIN(BODY)  | MAX(BODY)  |  SUM(BODY)   | AVG(BODY)  |
+-------+--------+-------------------------------------+-----+------+-----+-----+-----+-------+-------+---------+-------+-------+-------+-------+--------+------------+------------+--------------+------------+
|     1 | GET    | /static/js/runtime~main.a8a9905a.js |   0 |    1 |   0 |   0 |   0 | 0.008 | 0.008 |   0.008 | 0.008 | 0.008 | 0.008 | 0.008 |  0.000 |   1502.000 |   1502.000 |     1502.000 |   1502.000 |
|    34 | GET    | /transactions/\d+.png               |   0 |   22 |   0 |  12 |   0 | 0.004 | 0.012 |   0.124 | 0.004 | 0.000 | 0.004 | 0.000 |  0.004 |     32.000 |    626.000 |    13945.000 |    410.147 |
|     1 | GET    | /reports.json                       |   0 |    1 |   0 |   0 |   0 | 0.012 | 0.012 |   0.012 | 0.012 | 0.012 | 0.012 | 0.012 |  0.000 |  94019.000 |  94019.000 |    94019.000 |  94019.000 |
|     1 | GET    | /static/js/2.ff6e1067.chunk.js      |   0 |    1 |   0 |   0 |   0 | 0.032 | 0.032 |   0.032 | 0.032 | 0.032 | 0.032 | 0.032 |  0.000 | 508459.000 | 508459.000 |   508459.000 | 508459.000 |
|     9 | POST   | /items/edit                         |   0 |    3 |   0 |   6 |   0 | 0.004 | 0.052 |   0.092 | 0.010 | 0.004 | 0.008 | 0.012 |  0.015 |     57.000 |     92.000 |      615.000 |     68.333 |
|     1 | GET    | /static/css/main.19393e92.chunk.css |   0 |    1 |   0 |   0 |   0 | 0.052 | 0.052 |   0.052 | 0.052 | 0.052 | 0.052 | 0.052 |  0.000 |    994.000 |    994.000 |      994.000 |    994.000 |
|    57 | GET    | /upload/.+.jpg                      |   0 |   57 |   0 |   0 |   0 | 0.004 | 0.128 |   1.644 | 0.029 | 0.008 | 0.024 | 0.004 |  0.025 |  51229.000 | 130127.000 |  4362729.000 |  76539.105 |
|    13 | POST   | /bump                               |   0 |   13 |   0 |   0 |   0 | 0.004 | 0.352 |   0.756 | 0.058 | 0.076 | 0.068 | 0.008 |  0.092 |     90.000 |     91.000 |     1171.000 |     90.077 |
|     1 | GET    | /static/js/main.babc3d4d.chunk.js   |   0 |    1 |   0 |   0 |   0 | 0.452 | 0.452 |   0.452 | 0.452 | 0.452 | 0.452 | 0.452 |  0.000 |  90365.000 |  90365.000 |    90365.000 |  90365.000 |
|    53 | GET    | /settings                           |   0 |   53 |   0 |   0 |   0 | 0.012 | 0.488 |   6.173 | 0.116 | 0.048 | 0.092 | 0.000 |  0.125 |   3086.000 |   3100.000 |   163919.000 |   3092.811 |
|  2207 | GET    | /items/\d+.json                     |   0 | 2206 |   0 |   1 |   0 | 0.005 | 0.517 |  16.024 | 0.007 | 0.008 | 0.004 | 0.012 |  0.019 |      0.000 |   4041.000 |  4840278.000 |   2193.148 |
|    61 | POST   | /login                              |   0 |   53 |   0 |   8 |   0 | 0.060 | 0.781 |  18.730 | 0.307 | 0.124 | 0.389 | 0.060 |  0.183 |     72.000 |    266.000 |    14293.000 |    234.311 |
|    52 | POST   | /sell                               |   0 |   34 |   0 |  18 |   0 | 0.004 | 0.820 |   5.281 | 0.102 | 0.080 | 0.000 | 0.008 |  0.204 |     12.000 |    105.000 |     1836.000 |     35.308 |
|    40 | POST   | /ship_done                          |   0 |   22 |   0 |  18 |   0 | 0.816 | 0.824 |  19.747 | 0.494 | 0.020 | 0.000 | 0.000 |  0.392 |     28.000 |     82.000 |     1602.000 |     40.050 |
|    21 | POST   | /complete                           |   0 |   20 |   0 |   1 |   0 | 0.004 | 0.876 |  14.621 | 0.696 | 0.008 | 0.828 | 0.832 |  0.287 |      0.000 |     33.000 |      660.000 |     31.429 |
|    38 | POST   | /ship                               |   0 |   26 |   0 |  12 |   0 | 0.808 | 0.937 |  19.301 | 0.508 | 0.020 | 0.820 | 0.828 |  0.398 |     28.000 |     60.000 |     1944.000 |     51.158 |
|   267 | GET    | /users/\d+.json                     |   0 |  267 |   0 |   0 |   0 | 0.008 | 1.325 |  53.977 | 0.202 | 0.168 | 0.072 | 0.120 |  0.199 |     97.000 |  23995.000 |  3711121.000 |  13899.330 |
|    51 | POST   | /buy                                |   0 |   27 |   0 |  24 |   0 | 0.000 | 2.029 |  48.036 | 0.942 | 0.004 | 1.613 | 1.640 |  0.809 |     28.000 |     48.000 |     1809.000 |     35.471 |
|   130 | GET    | /new_items.json                     |   0 |  130 |   0 |   0 |   0 | 0.048 | 2.045 |  51.348 | 0.395 | 0.508 | 0.097 | 0.188 |  0.320 |  23011.000 |  23940.000 |  3043699.000 |  23413.069 |
|   700 | GET    | /new_items/\d+.json                 |   0 |  698 |   0 |   2 |   0 | 0.028 | 2.061 | 167.766 | 0.240 | 0.088 | 0.088 | 0.148 |  0.217 |      0.000 |  24044.000 | 16387888.000 |  23411.269 |
|     1 | POST   | /initialize                         |   0 |    1 |   0 |   0 |   0 | 7.156 | 7.156 |   7.156 | 7.156 | 7.156 | 7.156 | 7.156 |  0.000 |     32.000 |     32.000 |       32.000 |     32.000 |
|   160 | GET    | /users/transactions.json            |   0 |  151 |   0 |   9 |   0 | 0.028 | 7.467 | 450.698 | 2.817 | 0.384 | 2.613 | 3.492 |  1.551 |      0.000 |  30441.000 |  3087209.000 |  19295.056 |
+-------+--------+-------------------------------------+-----+------+-----+-----+-----+-------+-------+---------+-------+-------+-------+-------+--------+------------+------------+--------------+------------+

categoriesのオンメモリ化

GET /users/transactions.jsonGET /new_items/\d+.jsonの実装を確認すると、get_user_simple_by_id()get_category_by_id()が非常に気軽に呼ばれていることに気がつきます。
このうち、get_category_by_id()についてはcategoriesテーブルの更新が無いため、メモリに乗せることができます。

def get_category_by_id(category_id)
  category = categories.find{|item| item[:id] == category_id}

  return if category.nil?

  parent_category_name = if category[:parent_id] != 0
    parent_category = get_category_by_id(category[:parent_id])

    return if parent_category.nil?

    parent_category['category_name']
  end

  {
    'id' => category[:id],
    'parent_id' => category[:parent_id],
    'category_name' => category[:category_name],
    'parent_category_name' => parent_category_name
  }
end

def categories
  [
    {:id => 1, :parent_id => 0, :category_name => "ソファー"},
    {:id => 2, :parent_id => 1, :category_name => "一人掛けソファー"},
    {:id => 3, :parent_id => 1, :category_name => "二人掛けソファー"},
    {:id => 4, :parent_id => 1, :category_name => "コーナーソファー"},
    {:id => 5, :parent_id => 1, :category_name => "二段ソファー"},
    {:id => 6, :parent_id => 1, :category_name => "ソファーベッド"},
    {:id => 10, :parent_id => 0, :category_name => "家庭用チェア"},
    {:id => 11, :parent_id => 10, :category_name => "スツール"},
    {:id => 12, :parent_id => 10, :category_name => "クッションスツール"},
    {:id => 13, :parent_id => 10, :category_name => "ダイニングチェア"},
    {:id => 14, :parent_id => 10, :category_name => "リビングチェア"},
    {:id => 15, :parent_id => 10, :category_name => "カウンターチェア"},
    {:id => 20, :parent_id => 0, :category_name => "キッズチェア"},
    {:id => 21, :parent_id => 20, :category_name => "学習チェア"},
    {:id => 22, :parent_id => 20, :category_name => "ベビーソファ"},
    {:id => 23, :parent_id => 20, :category_name => "キッズハイチェア"},
    {:id => 24, :parent_id => 20, :category_name => "テーブルチェア"},
    {:id => 30, :parent_id => 0, :category_name => "オフィスチェア"},
    {:id => 31, :parent_id => 30, :category_name => "デスクチェア"},
    {:id => 32, :parent_id => 30, :category_name => "ビジネスチェア"},
    {:id => 33, :parent_id => 30, :category_name => "回転チェア"},
    {:id => 34, :parent_id => 30, :category_name => "リクライニングチェア"},
    {:id => 35, :parent_id => 30, :category_name => "投擲用椅子"},
    {:id => 40, :parent_id => 0, :category_name => "折りたたみ椅子"},
    {:id => 41, :parent_id => 40, :category_name => "パイプ椅子"},
    {:id => 42, :parent_id => 40, :category_name => "木製折りたたみ椅子"},
    {:id => 43, :parent_id => 40, :category_name => "キッチンチェア"},
    {:id => 44, :parent_id => 40, :category_name => "アウトドアチェア"},
    {:id => 45, :parent_id => 40, :category_name => "作業椅子"},
    {:id => 50, :parent_id => 0, :category_name => "ベンチ"},
    {:id => 51, :parent_id => 50, :category_name => "一人掛けベンチ"},
    {:id => 52, :parent_id => 50, :category_name => "二人掛けベンチ"},
    {:id => 53, :parent_id => 50, :category_name => "アウトドア用ベンチ"},
    {:id => 54, :parent_id => 50, :category_name => "収納付きベンチ"},
    {:id => 55, :parent_id => 50, :category_name => "背もたれ付きベンチ"},
    {:id => 56, :parent_id => 50, :category_name => "ベンチマーク"},
    {:id => 60, :parent_id => 0, :category_name => "座椅子"},
    {:id => 61, :parent_id => 60, :category_name => "和風座椅子"},
    {:id => 62, :parent_id => 60, :category_name => "高座椅子"},
    {:id => 63, :parent_id => 60, :category_name => "ゲーミング座椅子"},
    {:id => 64, :parent_id => 60, :category_name => "ロッキングチェア"},
    {:id => 65, :parent_id => 60, :category_name => "座布団"},
    {:id => 66, :parent_id => 60, :category_name => "空気椅子"}
  ]
end

get_user_simple_by_idを削除

get_user_simple_by_id()get_category_by_id()と同様に非常に多く呼ばれています。
こちらはオンメモリ化は難しいので、JOINして取得するようにします。
以下に、一例としてgetNewItemsのクエリを掲載します。

SELECT
  t1.*
  ,t2.`account_name`
  ,t2.`num_sell_items`
FROM
  `items` AS t1
  INNER JOIN `users` AS t2 ON
    t1.`seller_id` = t2.`id`
WHERE
  t1.`status` IN (?, ?)
ORDER BY
  t1.`created_at` DESC, t1.`id` DESC
LIMIT
  #{ITEMS_PER_PAGE + 1}

getTransactionsのAPI呼び出し

ここからは当日できなかった修正です。
api_client.shipment_statusはtransaction_evidenceのstatusがdoneの時には呼び出す必要が無いそうです。
修正後のスコアは以下の通りです。

{"pass":true,"score":3740,"campaign":0,"language":"ruby","messages":[]}

この時点でのalpの結果は以下のようになります。

+-------+--------+-------------------------------------+-----+------+-----+-----+-----+-------+-------+---------+-------+-------+-------+-------+--------+------------+------------+--------------+------------+
| COUNT | METHOD |                 URI                 | 1XX | 2XX  | 3XX | 4XX | 5XX |  MIN  |  MAX  |   SUM   |  AVG  |  P1   |  P50  |  P99  | STDDEV | MIN(BODY)  | MAX(BODY)  |  SUM(BODY)   | AVG(BODY)  |
+-------+--------+-------------------------------------+-----+------+-----+-----+-----+-------+-------+---------+-------+-------+-------+-------+--------+------------+------------+--------------+------------+
|     1 | GET    | /static/js/2.ff6e1067.chunk.js      |   0 |    1 |   0 |   0 |   0 | 0.016 | 0.016 |   0.016 | 0.016 | 0.016 | 0.016 | 0.016 |  0.000 | 508459.000 | 508459.000 |   508459.000 | 508459.000 |
|     1 | GET    | /static/js/runtime~main.a8a9905a.js |   0 |    1 |   0 |   0 |   0 | 0.024 | 0.024 |   0.024 | 0.024 | 0.024 | 0.024 | 0.024 |  0.000 |   1502.000 |   1502.000 |     1502.000 |   1502.000 |
|     1 | GET    | /reports.json                       |   0 |    1 |   0 |   0 |   0 | 0.032 | 0.032 |   0.032 | 0.032 | 0.032 | 0.032 | 0.032 |  0.000 | 143390.000 | 143390.000 |   143390.000 | 143390.000 |
|    50 | GET    | /transactions/\d+.png               |   0 |   40 |   0 |  10 |   0 | 0.008 | 0.080 |   0.544 | 0.011 | 0.008 | 0.012 | 0.012 |  0.015 |     32.000 |    623.000 |    24890.000 |    497.800 |
|    11 | POST   | /items/edit                         |   0 |    6 |   0 |   5 |   0 | 0.004 | 0.116 |   0.304 | 0.028 | 0.028 | 0.000 | 0.048 |  0.033 |     57.000 |     92.000 |      835.000 |     75.909 |
|    56 | GET    | /upload/.+.jpg                      |   0 |   56 |   0 |   0 |   0 | 0.004 | 0.180 |   2.994 | 0.053 | 0.084 | 0.068 | 0.008 |  0.043 |  51574.000 | 146187.000 |  4706042.000 |  84036.464 |
|     1 | GET    | /static/css/main.19393e92.chunk.css |   0 |    1 |   0 |   0 |   0 | 0.192 | 0.192 |   0.192 | 0.192 | 0.192 | 0.192 | 0.192 |  0.000 |    994.000 |    994.000 |      994.000 |    994.000 |
|     1 | GET    | /static/js/main.babc3d4d.chunk.js   |   0 |    1 |   0 |   0 |   0 | 0.520 | 0.520 |   0.520 | 0.520 | 0.520 | 0.520 | 0.520 |  0.000 |  90365.000 |  90365.000 |    90365.000 |  90365.000 |
|    13 | POST   | /bump                               |   0 |   13 |   0 |   0 |   0 | 0.004 | 0.529 |   1.821 | 0.140 | 0.156 | 0.028 | 0.080 |  0.164 |     89.000 |     91.000 |     1168.000 |     89.846 |
|    52 | GET    | /settings                           |   0 |   52 |   0 |   0 |   0 | 0.008 | 0.568 |   7.472 | 0.144 | 0.044 | 0.320 | 0.040 |  0.153 |   3086.000 |   3103.000 |   160855.000 |   3093.365 |
|    60 | POST   | /login                              |   0 |   52 |   0 |   8 |   0 | 0.100 | 0.740 |  22.886 | 0.381 | 0.576 | 0.452 | 0.184 |  0.190 |     72.000 |    269.000 |    14063.000 |    234.383 |
|    55 | POST   | /ship_done                          |   0 |   39 |   0 |  16 |   0 | 0.004 | 0.896 |  33.709 | 0.613 | 0.016 | 0.004 | 0.853 |  0.360 |      0.000 |     82.000 |     2017.000 |     36.673 |
|    65 | POST   | /sell                               |   0 |   50 |   0 |  15 |   0 | 0.004 | 0.936 |   8.002 | 0.123 | 0.068 | 0.016 | 0.080 |  0.208 |     12.000 |    105.000 |     1790.000 |     27.538 |
|   390 | GET    | /users/\d+.json                     |   0 |  386 |   0 |   4 |   0 | 0.007 | 0.944 |  94.877 | 0.243 | 0.144 | 0.280 | 0.225 |  0.153 |      0.000 |  23864.000 |  5174034.000 |  13266.754 |
|    50 | POST   | /ship                               |   0 |   40 |   0 |  10 |   0 | 0.004 | 0.948 |  31.514 | 0.630 | 0.016 | 0.829 | 0.849 |  0.361 |     28.000 |     60.000 |     2720.000 |     54.400 |
|    38 | POST   | /complete                           |   0 |   37 |   0 |   1 |   0 | 0.008 | 1.117 |  30.578 | 0.805 | 0.192 | 0.844 | 0.888 |  0.228 |      0.000 |     33.000 |     1221.000 |     32.132 |
|   128 | GET    | /new_items.json                     |   0 |  128 |   0 |   0 |   0 | 0.212 | 1.509 |  69.956 | 0.547 | 0.212 | 0.372 | 0.369 |  0.185 |  22988.000 |  23744.000 |  2994285.000 |  23392.852 |
|   788 | GET    | /new_items/\d+.json                 |   0 |  785 |   0 |   3 |   0 | 0.044 | 1.857 | 214.928 | 0.273 | 0.044 | 0.112 | 0.296 |  0.148 |      0.000 |  24021.000 | 18454159.000 |  23418.984 |
|  2932 | GET    | /items/\d+.json                     |   0 | 2932 |   0 |   0 |   0 | 0.008 | 1.857 |  37.309 | 0.013 | 0.080 | 0.004 | 0.012 |  0.040 |   1851.000 |   4052.000 |  6539101.000 |   2230.253 |
|    61 | POST   | /buy                                |   0 |   41 |   0 |  20 |   0 | 0.076 | 1.865 |  70.834 | 1.161 | 0.040 | 1.725 | 1.657 |  0.754 |     28.000 |     48.000 |     2118.000 |     34.721 |
|   414 | GET    | /users/transactions.json            |   0 |  409 |   0 |   5 |   0 | 0.024 | 2.341 | 256.159 | 0.619 | 0.216 | 1.160 | 0.340 |  0.411 |      0.000 |  27197.000 |  8797561.000 |  21250.147 |
|     1 | POST   | /initialize                         |   0 |    1 |   0 |   0 |   0 | 8.213 | 8.213 |   8.213 | 8.213 | 8.213 | 8.213 | 8.213 |  0.000 |     32.000 |     32.000 |       32.000 |     32.000 |
+-------+--------+-------------------------------------+-----+------+-----+-----+-----+-------+-------+---------+-------+-------+-------+-------+--------+------------+------------+--------------+------------+

INDEXを張る

itemsテーブルのseller_idbuyer_idcreated_atにINDEXを張ってみます。

還元率を変更してみる

当日はルールへの理解が足りず着手しなかったのですが、postInitializeのレスポンスにあるcampaignの数値を変更することで負荷が上がるようです。
試しに0 → 1に変更してベンチマークを実行した結果が以下のものです。

{
  "pass": true,
  "score": 7240,
  "campaign": 1,
  "language": "ruby",
  "messages": [
    "GET /new_items.json: リクエストに失敗しました(タイムアウトしました)",
    "GET /users/transactions.json リクエストに失敗しました (user_id: 3197)(タイムアウトしました)",
    "POST /login: リクエストに失敗しました(タイムアウトしました)",
    "POST /ship: リクエストに失敗しました (item_id: 50041)(タイムアウトしました)"
  ]
}

タイムアウトが増えますが、スコアは上がっています。

まとめ

1台構成ですが、予選通過ラインに肉薄するスコアが出るようになってきました。
本番の8時間でこれらを実施するのは大変ですが、来年は予選通過できるよう精進したいと思います。

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

【小学生でもわかる】Ruby のeach文 繰り返し処理

【Ruby】each文 繰り返し処理

sample.rb
subjects = ["こくご","さんすう","りか","しゃかい"]

subjects.each do |subject|
 puts "すきな科目は #{subject}です。"
end

結果

「すきな科目はこくごです」
「すきな科目はさんすうです」
「すきな科目はりかです」
「すきな科目はしゃかいです」

と表示される

解説

1.「subjectsっていう箱の中に4つぶちこむぜ!」
2.「subjectsっていう箱の中から今度は一個ずつ取り出してならべていくぜ!」
3.「そんで取り出すときにはsubjectっていう名前つけるぜ!」

というイメージ

【応用編】

sample.rb
samples = [
  {subject: "こくご", score: 80, grade: "A"},
  {subject: "さんすう", score: 45, grade: "D"},
  {subject: "えいご", score: 100, grade: "S", comment: "よくできました!"}
]

samples.each do |sample|
  puts "#{sample[:subject]}のてんすうは#{sample[:score]}点で、ひょうかは#{sample[:grade]}でした"
  if sample[:comment]
    puts "コメント: #{sample[:comment]}"
  end
end

コメントがあるときだけコメントを表示できるようにするif文を組み込むことができる

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

【小学生でもわかる】Rubyでの[ ]と{ }の違い 配列とハッシュの違い

【Ruby】 [ ]配列と { }ハッシュの違い

同じ種類の情報をひとまとめにする時(配列)

「教科」という共通の情報を併記

sample.rb
sample = ["こくご", "さんすう", "えいご"]

1つのことに対して複数の情報を加える時(ハッシュ)

対してこちらは共通の情報を併記しているわけではない。
例えるならa,1,アを一括りにしているイメージ

sample.rb
sample = {"subject" => "こくご", "score" => 80, "grade" => "A"}

シンボルを用いる方法

sample.rb
sample = {:subject => "こくご", :score => 80, :grade => "A"}

この方がキーの関係がわかりやすくて良いと思う。

省略形

sample.rb
sample = {:subject => "こくご", :score => 80, :grade => "A"}

sample = {subject: "こくご", score: 80, grade: "A"}

この方がさらにわかりやすいと思う

[ ]と{ } 配列とハッシュの応用

sample.rb
sample = {subject: "こくご", score: 80, grade: "A"}



samples = [
  {subject: "こくご", score: 80, grade: "A"},
  {subject: "さんすう", score: 45, grade: "D"},
  {subject: "えいご", score: 100, grade: "S"}
]

配列の中にハッシュがある状態

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

TECH::EXPERT_基礎メンター試験①

a

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

cloud9 環境 で rails 5.2.3を初めて設定してみた。

概要

Rubyを学び始めた初学者です。clou9でruby開発環境を整えるまで2日かかってしまいました。rails6.0.0をインストールしてもrailsサーバーが起動できなかった(yarnインストールができず、諦めました。)ため、rails5.2.3で構築してみましたよ。初投稿。
間違っている所などありましたら、ご指摘頂けると嬉しいです。

前提条件

amazon web serviceアカウントを持っている方
cloud9環境での開発を考えている方

環境

ruby 2.6.5
rails 5.2.3
sqlite3 3.7.17

ruby 2.6.5 のインストール

environmentディレクトリにて、コードの実行をする。

インストール可能なruby一覧情報

rvm list know

rubyのインストール

rvm install 2.6.5

複数インストールしたrubyから、選択する

rvm use 2.6.5

インストールされているrubyの内、どれが使われているか確認

rvm list

インストールされているrubyを、選択して削除

rvmsudo rvm remove 2.6.5

インストールした複数のrubyから、デフォルトとして設定

rvm --default use 2.6.5

rails 5.2.3のインストール

environmentディレクトリにて、コードの実行をする。
```gem install rails --version="5.2.3"

railsプロジェクトの作成

『プロジェクト名』のフォルダを作成。
以下、コマンドでディレクトリ移動。

cd 『プロジェクト名』

rails _5.2.3_ new フォルダ名 を入力。
『フォルダ名』ディレクトリへ移動。
cd 『フォルダ名』

bundleのインストール

bundle install

sqlite3の更新設定

gemfile→の中→9行目あたりの
sqlite3の後に,'~>1.3.6'と入力

ターミナルにて、
bundle update
rails db:createと入力

サーバーの起動

ターミナルにて、
rails sと入力

上部タブ『preview』から『preview runnning applecation』を選択
URLを確認。

rails スタートの画面になっていれば成功。

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

Sinatra サーバーを Rakefile とかで起動する

背景

Sinatraでサーバー起動したかったんだが、
rackupや、bundle exec ruby ~~~ で起動する方法しかググれなかった、、、

結論

Rakefileのタスクの中などで、Sinatra::Baseのサブクラスのrun!を呼ぶ

option = {port: 12345}
ClassOfSinatraServer.run!(option)

経緯と詳細

Sinatraのドキュメントをざっと読んだんだが、

http://sinatrarb.com/intro-ja.html

「モジュラーアプリケーションの提供」あたりにいくつか起動方法が書いてあるんだが、
rubyスクリプトで起動する方法がずばり書いてない、、、(と思う、、)
書いてあったが、わからんかった。(run!メソッドがprivateなんかと思った)


その他ググったりして、
以下の感じで一応起動したので、メモ作成

Gemfile
source "https://rubygems.org"
gem 'sinatra'
the_server.rb
require 'sinatra/base'

class TheServer < Sinatra::Base
  get '/' do
    'Hello, Sinatra'
  end
end
Rakefile
require './the_server.rb'

task default: :sinatra_server

desc 'start Sinatra server'
task :sinatra_server do
  option = {
    port: 12345
  }
  TheServer.run!(option)
end

上記3ファイルを同じフォルダにおいて、そこのフォルダをカレントにして

> bundle
> rake

で起動する!

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

Rails スクレイピングでデータを取得(getメソッド・searchメソッド編)備忘録

スクレイピングとは

  • 例えば、以下のようなHTMLのサイトがあった場合
index.html
<ul>
  <li>TEST1</li>
  <li>TEST2</li>
  <li>TEST3</li>
</ul>

<ul><li>の中にある「TEST1, TEST2, TEST3」等の値を取り出すことを言う。(ウェブサイト上のHTMLからある特定のデータを抜き出す処理のこと)

Mechanize

  • Mechanizeはスクレイピングを行うためのGemである。
  • Mechanizeクラスが使えるようになる。
  • Mechanizeクラスにはスクレイピングするための様々なメソッドが用意されている。

GemfileにMechanizeを追記(Railsの場合)

Gemfile
# 省略

gem 'mechanize'

bundle installすればOK

Mechanizeクラスで使うメソッドの紹介

HTML情報から指定のタグ要素の情報を検索する

searchメソッド

  • getメソッドで取得したページの情報が入ったオブジェクトに対して使用する。
  • 該当するHTMLのタグ要素が1つでも、返り値は配列の形式で返ってくる。

使い方

elements = Mechanize::Pageオブジェクト.search('セレクタ')

あるウェブページからh2要素のHTMLの情報を取得する場合

scraping.rb
agent = Mechanize.new
page = agent.get("http://sample.com/")
elements = page.search('h2') # h2要素を検索
puts elements
  • 出力結果(例)
<h2 class="entry-title index-entry-title">
<a href="/products/1" title="Single Post">sample1</a>
            </h2>
<h2 class="entry-title index-entry-title">
<a href="/products/2" title="Single Post">sample2</a>
            </h2>
<h2 class="entry-title index-entry-title">
<a href="/products/3" title="Single Post">sample3</a>
            </h2>
#以下略

h2要素の下のa要素のHTML情報を取得してみる

scraping.rb
agent = Mechanize.new
page = agent.get("http://sample.com/")
elements = page.search('h2 a') # h2要素の下のa要素を検索
puts elements
  • 出力結果(例)
<a href="/products/1" title="Single Post">sample1</a>
<a href="/products/2" title="Single Post">sample2</a>
<a href="/products/3" title="Single Post">sample3</a>
<a href="/products/4" title="Single Post">sample4</a>
# 以下略

このように、該当するHTMLのタグ要素全てが取得される。

  • 以下のようにするとsearchメソッドの返り値が配列の形式になっていることが確認できる。
test_scraping.rb
require 'mechanize'

agent = Mechanize.new
page = agent.get("http://sample.com/")
elements = page.search('h2 a') # h2要素の下のa要素を検索
puts elements[0]
  • 出力結果
ターミナル
<a href="/products/1" title="Single Post">Sample1</a>

以上のように返ってきた値の1番目の要素を取り出すことができるので配列の形式で返ってきていることがわかる。

まとめ

以上でmechanizeを使用したメソッドのうちgetメソッドsearchメソッドの2つを紹介しましたが今日は時間切れなので後日、他の使える便利なメソッドを紹介したいと思う。

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

attr_accessorについて

attr_accessorについて

attr_accesssorは外部からインスタンス変数を参照できるようにするために使用するメソッド。(厳密には、参照と書き込みの両方が行うことができる。)initializeで定義したインスタンス変数を外部から参照または、変更ができるようになる。
ex: attr_accessorの使用例

class Product
  attr_accessor :id

  def initialize(name)
    @id = name
  end

  def name
    "#{@id}-Product."
  end
end

# => コンソール
a=Product.new("sample")
=> #<Product:0x000056145a7f3d10 @id="sample">
a.name
=> "sample-Product."

#インスタンス変数を参照可能
a.id
=> "sample"
# インスタンス変数を変更可能
a.id = "red"
=> "red"
a.name
=> "red-Product."

#initializeとは? 

インスタンスを初期化したいときに実装したい処理を定義する場所。外部から呼び出せない。(デフォがprivateメソッド)引数をつければ、newする時に引数が必要となる。initialize内でインスタンス変数を作成すれば、クラス内でそのまま参照できる。しかし、インスタンス変数を外部から参照はできない。

class Product
  def initialize(name)
    @id = name
  end

  def name
    "#{@id}-Product."
  end
end

# => コンソール
a=Product.new('aa')
=> #<Product:0x0000558c6c72a508 @id="aa">
a.name
=> "aa-Product."
# 外部からの参照はできない
a.id
=> NoMethodError: undefined method 'id'

initializeを使用せずにクラスを作成してみた。

initializeがないため引数をつけるとエラーが出る。初期化時の実行が定義されていないため!
引数なしで作成し、インスタンス変数に値を代入すれば、メソッドでもインスタンス変数をして出力できた。

class Product
  attr_accessor :id

  def name
    "#{@id}-Product."
  end
end

# => コンソール
# 引数をつけるとエラーが出る
a=Product.new("sample")
ArgumentError: wrong number of arguments (given 1, expected 0)
# 引数なしなら作成できる
a=Product.new          
=> #<Product:0x000055eca644b478>
# 値を代入していないためnil
a.id
=> nil
a.name
=> "-Product."
# attr_accessorがあるため、代入は可能
a.id = "sample"
=> "sample"
# 代入後はメソッドなども問題なく使用できる
a.id
=> "sample"
a.name
=> "sample-Product."
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

再帰してディレクトリ内のファイルの行数を個別に表示する

末尾が _spec のファイルは除外している

# 全ファイルの行数を吐き出すlinuxコマンド (rubyはバッククォートにするとシェルを直接実行できる)
`for file in $(find . -name "*.rb"); do wc -l $file; done > ./tmp/lines.txt`
  open('./tmp/lines.txt', 'r') { |f| f.inject([]) { |r, l| r << l.split(" ") } }.sort{|a, b| a[0].to_i <=> b[0].to_i }.each {|i| puts i.join(" : ") unless i[1] =~ /_spec/  }

よくよく考えたらシェル使わずに Dir.glob でパス取ってきてやるでもよかったな...

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

RubyでNokogiriを使ってスクレイピングを行う

Nokogiriを使って、スクレイピングしてcsvに吐き出す方法

cssを使って指定

row.css('#normalResultsBox')        #ID
row.css('.normalResultsBox')        #クラス
row.css('h4>a').text,            #階層
row.css('p')[1].text            #2番目要素


row.css('a').attribute('href').value    #タグ内の要素を取得
row.css('a').gsub('aaaa','')       #置換する
row.css('a').split(".")[0]        #区切って1番目を取得


Rubyプログラミング

test.rb
#住所という文字列が存在するかどうか
if row.css('p')[0].text.include?("住所")
 #存在する
else
 #存在しない
end

urlからスクレイピング

noko.rb
require 'nokogiri'
require 'open-uri'
require 'csv'

#ヘッダーの書き込み
CSV.open('noko.csv', 'w') do |csv|
  csv << ["title","url"] 
end


#スクレイピングするurlを列挙する
urls = [
    'url1',
    'url2'
    ]

#urlをループ処理  
urls.each {
  |url|
  charset = nil

  html = open(url) do |f|
      f.read
  end

  doc = Nokogiri::HTML.parse(html, nil, "CP932")

  #2時配列を宣言する
  arr = Array.new

  doc.css('.normalResultsBox').each do |row|
      arr << [
        row.css('h4>a').text,                 #タイトル
        row.css("a").attribute('href').value  #url 
      ]
  end

  # csvファイル形式で生成
  string_csv_format = CSV.generate do |data|
    arr.each do |line|
      data << line
    end
  end

  # csvファイル書き出し方法1
  File.open("noko.csv", "a") do |f| 
    f.puts string_csv_format
  end
}

textファイルを読み込んで、取り出す

noko2.rb
require 'nokogiri'
require 'csv'

#ヘッダーの書き込み
CSV.open('itown.csv', 'w') do |csv|
  csv << ["url","comment"] 
end


html = File.open("test1.txt") do |f|
    f.read
end

doc = Nokogiri::HTML.parse(html, nil, "CP932")

#2時配列を宣言する
arr = Array.new

 doc.css('.normalResultsBox').each do |row|
    arr << [
      row.css('h4>a').text,                                                       #社名
      row.css('p')[1].text.gsub('住所 ','').gsub('〒','').split(" ")[0],         #郵便番号
      row.css('p')[1].text.gsub(' 地図・ナビ','').split(" ")[1],                 #住所
      row.css('p')[2].css('b').text                                               #電話
    ]
end

# csvファイル形式で生成
string_csv_format = CSV.generate do |data|
  arr.each do |line|
    data << line
  end
end

# csvファイル書き出し方法1
File.open("itown.csv", "a") do |f| 
  f.puts string_csv_format
end

実行する

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

Rails6 のちょい足しな新機能を試す87(Time#advance編)

はじめに

Rails 6 に追加された新機能を試す第87段。 今回は、 Time#advance 編です。
Rails 6 では、 1001/03/07 以前の Time#advance の計算が正確になりました。

Ruby 2.6.4, Rails 6.0.0 Rails 5.2.3 で確認しました。

$ rails --version
Rails 6.0.0

今回は、ちょっとわざとらしいですが、2009年4月1日の1200年前に即位した天皇を検索してみましょう。

プロジェクトを作る

rails new rails_sandbox
cd rails_sandbox

Emperor モデルを作る

nameenthroned_at (即位した年) をもつ Emperor モデルを作ります。

bin/rails g model Emperor name enthroned_at:datetime

seed データを作る

seed データを作ります。

db/seeds.rb
Emperor.create(
  [
    { name: '桓武天皇', enthroned_at: Time.utc(781, 4, 3) },
    { name: '平城天皇', enthroned_at: Time.utc(806, 3, 17) },
    { name: '嵯峨天皇', enthroned_at: Time.utc(809, 4, 1) },
    { name: '淳和天皇', enthroned_at: Time.utc(823, 4, 16) },
    { name: '仁明天皇', enthroned_at: Time.utc(833, 2, 28) }
  ]
)

マイグレーションを行い seed データを登録する

$ bin/rails db:create db:migrate db:seed

rails console で確認する

enthroned_at が 2009年4月1日の 1200年前に即位した天皇を検索してみます。

「2009年4月1日の 1200年前」は、 Time.utc(2009, 4, 1).advance(years: 1200) で求めることができます。

irb(main):001:0> Emperor.where(enthroned_at: Time.utc(2009, 4, 1).advance(years: -1200))
  Emperor Load (0.3ms)  SELECT "emperors".* FROM "emperors" WHERE "emperors"."enthroned_at" = $1 LIMIT $2  [["enthroned_at", "0809-04-01 00:00:00"], ["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Emperor id: 3, name: "嵯峨天皇", enthroned_at: "0809-04-01 00:00:00", created_at: "2019-09-21 21:49:24", updated_at: "2019-09-21 21:49:24">]>

嵯峨天皇が検索できました。

Rails 5.2.3 では

「2009年4月1日の 1200年前」が 809年4月5日になってしまうため、期待した結果が得られません

irb(main):001:0> Emperor.where(enthroned_at: Time.utc(2009, 4, 1).advance(years: -1200))
  Emperor Load (0.5ms)  SELECT  "emperors".* FROM "emperors" WHERE "emperors"."enthroned_at" = $1 LIMIT $2  [["enthroned_at", "0809-04-05 00:00:00"], ["LIMIT", 11]]
=> #<ActiveRecord::Relation []>

試したソース

試したソースは以下にあります。
https://github.com/suketa/rails_sandbox/tree/try087_time_advance

参考情報

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

Railsアプリ〜デプロイへの道〜その②Ruby・MySQLインストール編【ConoHa VPS・CentOS・Capistrano3・Nginx・Unicorn】

Railsアプリケーションをデプロイした時の手順をまとめた記事の第2弾です。

今回はRubyおよびMySQLのインストール・設定の手順をまとめました。

前回同様こちらの記事がベースとなっています。
https://qiita.com/ryo2132/items/f62690f0b16ec11270fe

〈前回記事〉
Railsアプリ〜デプロイへの道〜その①リモートサーバーへのSSH接続編

開発環境

・Mac OS
・Rails 5.2.3
・Ruby 2.5.1
・ConoHa VPS
・CentOS 7.6
・Capistrano3
・Nginx
・Unicorn

Rubyのインストール

ConoHa VPSにRubyをインストールしていきます。

1.Gitインストール

まずGitをインストールします。

$ sudo yum -y install git

$ git --version
# version名出力されればOK

2.rbenvインストール

続いてrbenvをインストール。
rbenvはRubyのバージョン管理ツールです。

$ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv

ruby-burildインストール

こちらもRubyをインストールするために必要になります。

$ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build

3.rbenv設定

rbenvコマンドを使えるようにするためにbash_profileに以下の内容を追加します。

# PATHにrbenvコマンドを追加
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
# rbenvの初期化
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
# bashの再読込
$ source ~/.bash_profile
# rbenvのバージョン確認 バージョンが出ればOK
$ rbenv -v

これでRubyインストールの準備は完了です。
いよいよRubyをインストールします。

4.Rubyインストール

ローカルで使用中のRubyバージョンを確認
サーバーにインストールする前に、開発で使用したRubyのバージョンを確認します。

ローカル

$ ruby -v

ローカルと同じバージョンのRubyをインストール

サーバー

$ rbenv install 2.5.1 
# globalで使用するrubyの設定
$ rbenv global 2.5.1
# rbenvの再起動?
$ rbenv rehash
# rubyのバージョン確認
$ ruby -v

5.bundleインストール

Gemの管理ツールであるbundlerをインストールします。

bundlerのバージョンを開発したRailsアプリのGemfile.lockのものと合わせる必要があります。
バージョンが合っていないとデプロイ時にエラーが発生します。
参考:https://qiita.com/MotohiroSiobara/items/c0d343a160cffc2902ef

RailsアプリのGemfile.lockを確認

BUNDLED WITH 2.0.1

サーバー
同じバージョンのbundlerをインストール。

$ gem install bundle -v 2.0.1

6.関連パッケージインストール

必要なパッケージを諸々インストール
これらがないとデプロイ時にエラー起こします。

<参考>
https://teratail.com/questions/13430
https://qiita.com/jaxx2104/items/2277cec77850f2d83c7a

サーバー

#gcc-c++インストール
$ sudo yum install gcc-c++

#node.jsインストール
$ sudo yum install nodejs npm --enablerepo=epel 

#yarnインストール
$ sudo npm install -g yarn

MySQL

1.事前準備:MariaDBの削除

CentOS7系では標準でMariaDB(MySQL互換のDB)がインストールされている場合があるので、MySQLと競合しないようMariaDBを削除します。

参考:https://qiita.com/miqpim/items/5c519a9979d9b269d47e

サーバー

$ rpm -qa | grep maria     # mariaDBが存在するか確認
$ sudo yum remove mariadb-libs  # 本体削除
$ sudo rm -rf /var/lib/mysql  # データ削除

rmコマンドのオプションについてはこちらをご覧ください。
参考:https://eng-entrance.com/linux_command_rm

2.MySQLのリポジトリを登録

Mysql公式のYumリポジトリをインストールします。
最新版はMysql公式で確認できます。

サーバー

〈参考〉
https://qiita.com/rutko/items/56a33d1ecd70c0480202
https://qiita.com/nooboolean/items/7efc5c35b2e95637d8c1

CentOSのバージョンの確認

$ cat /etc/redhat-release

リポジトリのインストール

yumコマンドのlocalinstallオプションを使うことでリモートにあるrpmファイルをインストールすることができます。
rpmコマンドを使ってのインストールも可能ですが、依存関係とかを考慮したインストールまではしてくれないので、yumでインストールしましょう。
URLはリモートにあるrpmファイルをインストールするためのもので、公式サイトのリポジトリを、 http://dev.mysql.com/get/の後に付け加えて完成です。(お使いのOSに合わせたものを選んでください)

詳しくはこちらの記事をご覧ください。
https://qiita.com/nooboolean/items/7efc5c35b2e95637d8c1

$ sudo yum localinstall http://dev.mysql.com/get/mysql57-community-release-el7-7.noarch.rpm

リポジトリの確認

/etc/yum.repos.d配下に、mysql-community-source.repoとmysql-community.repoというリポジトリが作成されてるはずです。

$ cd /etc/yum.repos.d/
$ ls
mysql-community-source.repo  mysql-community.repo

このリポジトリが作成されて入れば、リポジトリの追加の確認は完了

インストールできたか確認

$ yum repolist all | grep mysql

mysql-connectors-community/x86_64 MySQL Connectors Community      有効:
mysql-connectors-community-source MySQL Connectors Community - So 無効
mysql-tools-community/x86_64      MySQL Tools Community           有効:
mysql-tools-community-source      MySQL Tools Community - Source  無効
mysql55-community/x86_64          MySQL 5.5 Community Server      無効
mysql55-community-source          MySQL 5.5 Community Server - So 無効
mysql56-community/x86_64          MySQL 5.6 Community Server      無効  <-ここ注目
mysql56-community-source          MySQL 5.6 Community Server - So 無効
mysql57-community/x86_64          MySQL 5.7 Community Server      有効: <-ここ注目
mysql57-community-source          MySQL 5.7 Community Server - So 無効

(環境によっては有効がenabled、無効がdisabledと表示される)
上記の例では、5.7が有効、5.6が無効になっています。
このままインストールすると、有効になっている5.7がインストールされてしまうので、5.6をインストールするには有効無効の切り替えをします。
この切り替えをするためにはyumの設定変更用のyum-utilsパッケージが必要なので、インストールされていない場合はインストールします。

$ yum list installed | grep yum-utils  #yum-utilsがインストールされているか確認
$ yum -y install yum-utils #入ってなければyum-utilsをインストールする
$ yum-config-manager --disable mysql57-community     #5.7を無効に設定
$ yum-config-manager --enable mysql56-community       #5.6を有効に設定

設定ができているか再度確認。

$ yum repolist all | grep mysql

mysql-connectors-community/x86_64  MySQL Connectors Community       有効:    
mysql-connectors-community-source  MySQL Connectors Community - Sou 無効
mysql-tools-community/x86_64       MySQL Tools Community            有効:     
mysql-tools-community-source       MySQL Tools Community - Source   無効
mysql55-community/x86_64           MySQL 5.5 Community Server       無効
mysql55-community-source           MySQL 5.5 Community Server - Sou 無効
mysql56-community/x86_64           MySQL 5.6 Community Server       有効:    
mysql56-community-source           MySQL 5.6 Community Server - Sou 無効
mysql57-community/x86_64           MySQL 5.7 Community Server       無効
mysql57-community-source           MySQL 5.7 Community Server - Sou 無効

5.6が有効になっていればOK。

#バージョン確認
$ yum info mysql-community-server
# インストール
$ sudo yum install mysql-community-server mysql-devel

mysql-develは必要です。
ないとデプロイ時にエラーが出ます。
参考:https://teratail.com/questions/181707

# 確認。バージョン出ればOK
$ mysqld --version

Mysqlの起動/自動起動設定

起動

$ sudo systemctl start mysqld.service   #起動

自動起動設定

$ sudo systemctl enable mysqld.service   #自動起動 ON
$ systemctl disble mysqld   #自動起動 OFF

自動起動になっているか確認

$ systemctl is-enabled mysqld

mysqld.service is not a native service, redirecting to /sbin/chkconfig.
Executing /sbin/chkconfig mysqld --level=5
enabled

enabledになっていれば自動起動 ONに設定されています。

基本操作

$ sudo systemctl start mysqld.service   #起動
$ systemctl stop mysqld    #停止
$ systemctl status mysqld  #ステータス確認
$ systemctl restart mysqld #再起動

ちなみに、mysqldのdはデーモン (daemon)のdらしいです。
デーモンについてはこちらの記事をご覧ください。
https://wa3.i-3-i.info/word11000.html

簡単に言うと「いつでも動けるようにスタンバイしてる常駐プログラム」だそうです。

3.パスワード、セキュリティ設定

続いてMySQLの設定をしていきます。
今回の山場です。
参考:https://weblabo.oscasierra.net/mysql-57-init-setup/

ログインするために、まず自動生成された初期パスワードを確認します。

$ sudo cat /var/log/mysqld.log | grep password
2017-12-31T11:31:07.946947Z 1 [Note] A temporary password is generated for root@localhost: .7ogGO4yokDh
# 「.7ogGO4yokDh」の部分が初期パスワードです

確認できたらmysql_secure_installationで初期設定を行います。
以下対話形式で進むので適宜コマンド入力してください。

$ mysql_secure_installation


Securing the MySQL server deployment.

Enter password for user root: # ログファイルから取得した初期パスワードを入力します

The existing password for the user account root has expired. Please set a new password.

New password: # root ユーザの新規パスワードを入力します

Re-enter new password: # 確認用にもう一度入力します
The 'validate_password' plugin is installed on the server.
The subsequent steps will run with the existing configuration
of the plugin.
Using existing password for root.

Estimated strength of the password: 100
Change the password for root ? ((Press y|Y for Yes, any other key for No) : y

By default, a MySQL installation has an anonymous user,
allowing anyone to log into MySQL without having to have
a user account created for them. This is intended only for
testing, and to make the installation go a bit smoother.
You should remove them before moving into a production
environment.

Remove anonymous users? (Press y|Y for Yes, any other key for No) : y # 匿名ユーザーアカウントを削除
Success.


Normally, root should only be allowed to connect from
'localhost'. This ensures that someone cannot guess at
the root password from the network.

Disallow root login remotely? (Press y|Y for Yes, any other key for No) : y # ローカルホスト以外からアクセス可能な root アカウントを削除
Success.

By default, MySQL comes with a database named 'test' that
anyone can access. This is also intended only for testing,
and should be removed before moving into a production
environment.


Remove test database and access to it? (Press y|Y for Yes, any other key for No) : y # test データベースの削除
 - Dropping test database...
Success.

 - Removing privileges on test database...
Success.

Reloading the privilege tables will ensure that all changes
made so far will take effect immediately.

Reload privilege tables now? (Press y|Y for Yes, any other key for No) : y
Success.

All done!

4.日本語化

日本語を扱うために文字コードを設定します。
日本語の文字コードはutf8に設定すると思いますが、utf8mb4は絵文字に対応した文字コードなので、僕はutf8ではなくutf8mb4で設定しています。

現状、問題なく使えています。

〈参考〉
https://qiita.com/okamu_/items/5eb81688849fbe351350
https://qiita.com/jkr_2255/items/74fc79e764378b59355a

$ sudo vi /etc/my.cnf

#ファイル内の末尾に以下を追記
[mysqld]
character-set-server=utf8mb4

[client]
default-character-set=utf8mb4

#再起動
$ sudo systemctl restart mysqld.service

#文字コード確認
$ mysql -u root -p

$ show variables like "chara%";

utf8mb4になってたらOK!

5.Time-zone設定

〈参考〉
https://dev.mysql.com/doc/refman/8.0/en/time-zone-support.html
https://agohack.com/mysql-change-timezone/

現在のタイムゾーンを確認

$ show variables like '%time_zone%';

system_time_zone : UTC
time_zone : SYSTEM

タイムゾーンデータの確認

$ select * from mysql.time_zone;

空の場合は、データをインポートする。

タイムゾーンのインポート

$ /usr/bin/mysql_tzinfo_to_sql /usr/share/zoneinfo > ~/timezone.sql
# passwordを聞かれるのでmysqlのパスワードを入力
$ mysql -u root -p -Dmysql < ~/timezone.sql

設定の追加

$ sudo vi /etc/my.cnf

# 末尾に以下を追加
[mysqld]
default-time-zone = 'Asia/Tokyo'

再起動

$ sudo systemctl restart mysqld.service

確認

mysql > show variables like '%time_zone%';

# 以下表示が出ればOK
+------------------+------------+
| Variable_name    | Value      |
+------------------+------------+
| system_time_zone | JST        |
| time_zone        | Asia/Tokyo |
+------------------+------------+
2 rows in set (0.00 sec)

MySQLの自動起動設定
サーバーの再起動の際にも、mysqlが自動的に起動するよう設定します。

chkconfig mysqld on

まとめ

以上、Railsアプリ〜デプロイへの道〜その②Ruby・MySQLインストール編でした。

次回はいよいよCapistrano3・Nginx・Unicornの設定です。(予定)

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

brewで入れているruby-buildをgitのHEADに更新する

はじめに

久々のQiitaです。
Rubyの2.4以降のバージョンに対してセキュリティリリースが出たそうで。
https://www.ruby-lang.org/ja/news/2019/10/01/ruby-2-6-5-released/

使っているrubyのバージョンを更新しようとしたところ、brew upgrade ruby-buildしたのに2.6.5がない。

$ brew update && brew upgrade ruby-build
$ rbenv install --list

...(中略)...
  2.6.3
  2.6.4
  2.7.0-dev
  2.7.0-preview1
...(中略)...

どうやら brew upgrade ruby-build でインストールできるのは 2019/08/28 リリースのバージョンまでらしい(2019/10/02現在)。
https://formulae.brew.sh/formula/ruby-build

というわけで、gitのHEADから拾ってきてruby-buildを更新します。

やり方

インストールコマンドにオプションを付けるだけ

$ brew install ruby-build --HEAD

が、既に ruby-build が入った状態でこれをやると怒られます。

Error: ruby-build 20190828 is already installed
To install HEAD, first run `brew unlink ruby-build`.
Warning: Skipping (old) /usr/local/Cellar/ruby-build/20190828 due to it being linked

まあそりゃそうだ、ということで、一旦 unlink して外してあげてから再インストールします。

$ brew unlink ruby-build
$ brew install ruby-build --HEAD

Rubyのインストールをしたらバージョン切り替えて確認

$ rbenv install 2.6.5
$ rbenv local 2.6.5
$ ruby -v
ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-darwin18]

大丈夫そうっすね。
というわけで以上です〜〜

参考

https://qiita.com/chezou/items/658a058ae22eb5bf6c5e

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

Rails6でのRubyOnRails 環境構築

はじめに

RubyOnRailsの導入方法です。
エラーが起こってわかりにくかったところをまとめます。

開発環境等

  • macOS Mojave version 10.14.6
  • Ruby 2.6.4
  • Rails 6.0.0
  • rbenv local 2.6.4
  • sqlite3 3.29.0

Homebrewのインストール

入っていない人は次のコマンドをコピペしてインストールしてください。

Homebrewのインストール
$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

macOS用パッケージマネジャー — homebrew

次にHomebrewをupdateします

Homebrewのアップデート
$ brew update

rbenvのインストール

rbenvを使うと複数のバージョンのrubyを管理できるようになります。
ruby-buildはRubyをバージョンごとに異なるディレクトリにビルドするためのツール

rvenvのインストール
 $ brew install rbenv ruby-build

環境変数のPATHにディレクトリを登録しておくと登録したディレクトリ直下のスクリプトが絶対パスで指定しなくても実行できるようになります。

.bash_profileの設定
$ echo 'export PATH="~/.rbenv/shims:/usr/local/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
$ source ~/.bash_profile

Ruby2.6.4のinstall

インストールできるrubyのバージョンを確認する

バージョンの確認
$ rbenv install -l

僕はruby2.6.4が最新だったのでそれをインストールしました。

ruby2.6.4のinstall
$ rbenv install 2.6.4

システム全体でつかうRubyとして登録して置きたいのでそれも設定

$ rbenv global 2.6.4

インストールしたRubyを使用可能にするためにrehash

$ rbenv rehash

複数のバージョンをインストールする場合は繰り返しコマンドの実行を行う

Railsのinstall

RailsはWebアプリケーションフレームワークです。これもインストールしていきます
(注:2.5.0以降のVersionでしかRailsは動きません!!!)

1.bundlerのインストール

まずbundlerをインストールします。gemコマンドではなくbundlerコマンドでパッケージをインストールすることでバージョンの互換性の問題を解決できます。

PermissionError言われたのでsudoさん使って実行

bundlerのインストール
$ sudo gem install bundler

2.sqlite3のアップグレード

Railsを利用するためにはsqlite3をインストールしている必要があります。macにはデフォルトで入っているのでアップグレードします。あとはシンボリックリンクを作成してパスを通す。
RailsのデフォルトのDBはsqliteですのでmysqlを使いたい人は切り替えて使う。

//現在のバージョンの確認
$ sqlite3 --version
3.24.0

//sqlite3のアップグレード
$ brew upgrade sqlite3

//最新バージョンの確認
$ ls /usr/local/Cellar/sqlite/
3.29.0

//シンボリックリンクの作成
$ cd /usr/local/bin/
$ ln -sf ../Cellar/sqlite/3.29.0/bin/sqlite3 ./

//PATHを通す
$ echo 'export PATH=/usr/local/bin:"$PATH"' >> ~/.bash_profile
$ source ~/.bash_profile

//変更できたか確認
$ sqlite3 --version
3.29.0

3.作業用ディレクトリへのgemのインストール

作業用ディレクトリの作成を行う

作業用ディレクトリの作成
$ mkdir ~/workspace
$ cd ~/workspace

Rubyのバージョンの指定

$ rbenv local 2.6.4

ruby '~> 2.6.4'を追加し、Gemfileのgem "rails"の コメントアウトを外す(バージョンは自分が入れたバージョンに変える)

GemFileの作成
$ bundle init

Writing new Gemfile to /Users/<ユーザ名>/workspace/Gemfile
Gemfileの編集
$ vim /Users/<ユーザ名>/workspace/Gemfile

ruby '~> 2.6.4'

# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem "rails"

railsをインストールする

Railsのインストール
$ bundle install --path=vendor/bundle
$ bundle exec rails -v
Rails 5.2.3

bundle install --path=vendor/bundleでバージョンエラーが起こる場合の対処方法

試しにブログアプリケーションを作成して動作確認を行ったところbundleがsystemのrubyを参照していたため失敗。雑だがファイルを無理やり移動させてパスを変更したらうまくいった。

bundleとbundlerのPATH
$ which bundle bundler
/usr/local/bin/bundle
/usr/local/bin/bundler 

変更
$ mv /usr/local/bin/bundle /Users/<ユーザ名>/.rbenv/shims/bundle 
$ mv /usr/local/bin/bundler /Users/<ユーザ名>/.rbenv/shims/bundler

$ which bundle bundler
/Users/<ユーザ名>/.rbenv/shims/bundle
/Users/<ユーザ名>/.rbenv/shims/bundler

4.サーバーの起動

Rails6を使う場合は# environment is not defined in config/webpacker.ymlが出る時を行う。場合によってはcan't find gem railtiesが出る時を行う。

サーバーの起動
$ bundle exec rails new blog
$ cd ~/workspace/blog
$ rails server<img width="752" alt="スクリーンショット 2019-10-02 2.10.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505843/cdbc5838-e114-0e7a-ea42-a258ae3cc5fc.png">
<img width="752" alt="スクリーンショット 2019-10-02 2.10.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505843/47e9ad5d-4e44-f551-6387-49bbf56b0f5b.png">

=> Booting Puma
=> Rails 6.0.0 application starting in development 
=> Run `rails server --help` for more startup options
Puma starting in single mode...
* Version 3.12.1 (ruby 2.6.4-p104), codename: Llamas in Pajamas
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://localhost:3000
Use Ctrl-C to stop

can't find gem railtiesが出る時

パスを変えて実行したところ can't find gem railtiesと言われたのでreailtiesのインストールを行った。

一覧表示
$ gem list

reailtiesがなければgemでrailsをインストールする。

railsのインストール
$ gem install rails

environment is not defined in config/webpacker.ymlが出る時(Rails6を使う場合必須)

Rails6.0.0ではwebpackerのインストールが必要。インストールしていない場合はenvironment is not defined in config/webpacker.ymlとエラーが出るはず。webpackerのインストールにはyawnのインストールが必要となるのでインストールしていない場合はこちらも。

yawnのインストール
$ brew install yawn
webpackerのインストール
$ bundle exec rails webpacker:install

http://localhost:3000/ にアクセスしてYay! You’re on Rails!の画面が表示されRuby versionが2.6.4かどうか確認
スクリーンショット 2019-10-02 10.43.35.png

参考サイト

Ruby初学者のRuby On Rails 環境構築【Mac】
Rails をはじめよう - Railsガイド
Bundlerの使い方
MacのpyenvでHomebrewからインストールしたsqlite3を利用する

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

Rails6でのRubyonRails環境構築

はじめに

RubyOnRailsの導入方法です。
エラーが起こってわかりにくかったところをまとめます。

開発環境等

  • macOS Mojave version 10.14.6
  • Ruby 2.6.4
  • Rails 6.0.0
  • rbenv local 2.6.4
  • sqlite3 3.29.0

Homebrewのインストール

入っていない人は次のコマンドをコピペしてインストールしてください。

Homebrewのインストール
$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

macOS用パッケージマネジャー — homebrew

次にHomebrewをupdateします

Homebrewのアップデート
$ brew update

rbenvのインストール

rbenvを使うと複数のバージョンのrubyを管理できるようになります。
ruby-buildはRubyをバージョンごとに異なるディレクトリにビルドするためのツール

rvenvのインストール
 $ brew install rbenv ruby-build

環境変数のPATHにディレクトリを登録しておくと登録したディレクトリ直下のスクリプトが絶対パスで指定しなくても実行できるようになります。

.bash_profileの設定
$ echo 'export PATH="~/.rbenv/shims:/usr/local/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
$ source ~/.bash_profile

Ruby2.6.4のinstall

インストールできるrubyのバージョンを確認する

バージョンの確認
$ rbenv install -l

僕はruby2.6.4が最新だったのでそれをインストールしました。

ruby2.6.4のinstall
$ rbenv install 2.6.4

システム全体でつかうRubyとして登録して置きたいのでそれも設定

$ rbenv global 2.6.4

インストールしたRubyを使用可能にするためにrehash

$ rbenv rehash

複数のバージョンをインストールする場合は繰り返しコマンドの実行を行う

Railsのinstall

RailsはWebアプリケーションフレームワークです。これもインストールしていきます
(注:2.5.0以降のVersionでしかRailsは動きません!!!)

1.bundlerのインストール

まずbundlerをインストールします。gemコマンドではなくbundlerコマンドでパッケージをインストールすることでバージョンの互換性の問題を解決できます。

PermissionError言われたのでsudoさん使って実行

bundlerのインストール
$ sudo gem install bundler

2.sqlite3のアップグレード

Railsを利用するためにはsqlite3をインストールしている必要があります。macにはデフォルトで入っているのでアップグレードします。あとはシンボリックリンクを作成してパスを通す。
RailsのデフォルトのDBはsqliteですのでmysqlを使いたい人は切り替えて使う。

//現在のバージョンの確認
$ sqlite3 --version
3.24.0

//sqlite3のアップグレード
$ brew upgrade sqlite3

//最新バージョンの確認
$ ls /usr/local/Cellar/sqlite/
3.29.0

//シンボリックリンクの作成
$ cd /usr/local/bin/
$ ln -sf ../Cellar/sqlite/3.29.0/bin/sqlite3 ./

//PATHを通す
$ echo 'export PATH=/usr/local/bin:"$PATH"' >> ~/.bash_profile
$ source ~/.bash_profile

//変更できたか確認
$ sqlite3 --version
3.29.0

3.作業用ディレクトリへのgemのインストール

作業用ディレクトリの作成を行う

作業用ディレクトリの作成
$ mkdir ~/workspace
$ cd ~/workspace

Rubyのバージョンの指定

$ rbenv local 2.6.4

ruby '~> 2.6.4'を追加し、Gemfileのgem "rails"の コメントアウトを外す(バージョンは自分が入れたバージョンに変える)

GemFileの作成
$ bundle init

Writing new Gemfile to /Users/<ユーザ名>/workspace/Gemfile
Gemfileの編集
$ vim /Users/<ユーザ名>/workspace/Gemfile

ruby '~> 2.6.4'

# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem "rails"

railsをインストールする

Railsのインストール
$ bundle install --path=vendor/bundle
$ bundle exec rails -v
Rails 5.2.3

bundle install --path=vendor/bundleでバージョンエラーが起こる場合の対処方法

試しにブログアプリケーションを作成して動作確認を行ったところbundleがsystemのrubyを参照していたため失敗。雑だがファイルを無理やり移動させてパスを変更したらうまくいった。

bundleとbundlerのPATH
$ which bundle bundler
/usr/local/bin/bundle
/usr/local/bin/bundler 

変更
$ mv /usr/local/bin/bundle /Users/<ユーザ名>/.rbenv/shims/bundle 
$ mv /usr/local/bin/bundler /Users/<ユーザ名>/.rbenv/shims/bundler

$ which bundle bundler
/Users/<ユーザ名>/.rbenv/shims/bundle
/Users/<ユーザ名>/.rbenv/shims/bundler

4.サーバーの起動

Rails6を使う場合は# environment is not defined in config/webpacker.ymlが出る時を行う。場合によってはcan't find gem railtiesが出る時を行う。

サーバーの起動
$ bundle exec rails new blog
$ cd ~/workspace/blog
$ rails server<img width="752" alt="スクリーンショット 2019-10-02 2.10.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505843/cdbc5838-e114-0e7a-ea42-a258ae3cc5fc.png">
<img width="752" alt="スクリーンショット 2019-10-02 2.10.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505843/47e9ad5d-4e44-f551-6387-49bbf56b0f5b.png">

=> Booting Puma
=> Rails 6.0.0 application starting in development 
=> Run `rails server --help` for more startup options
Puma starting in single mode...
* Version 3.12.1 (ruby 2.6.4-p104), codename: Llamas in Pajamas
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://localhost:3000
Use Ctrl-C to stop

can't find gem railtiesが出る時

パスを変えて実行したところ can't find gem railtiesと言われたのでreailtiesのインストールを行った。

一覧表示
$ gem list

reailtiesがなければgemでrailsをインストールする。

railsのインストール
$ gem install rails

environment is not defined in config/webpacker.ymlが出る時(Rails6を使う場合必須)

Rails6.0.0ではwebpackerのインストールが必要。インストールしていない場合はenvironment is not defined in config/webpacker.ymlとエラーが出るはず。webpackerのインストールにはyawnのインストールが必要となるのでインストールしていない場合はこちらも。

yawnのインストール
$ brew install yawn
webpackerのインストール
$ bundle exec rails webpacker:install

http://localhost:3000/ にアクセスしてYay! You’re on Rails!の画面が表示されRuby versionが2.6.4かどうか確認
スクリーンショット 2019-10-02 10.43.35.png

参考サイト

Ruby初学者のRuby On Rails 環境構築【Mac】
Rails をはじめよう - Railsガイド
Bundlerの使い方
MacのpyenvでHomebrewからインストールしたsqlite3を利用する

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

canvasで描いた絵のURLをDBに保存する

概要

私は9月ごろから独学でRuby on Railsを始めたのですが、題名の通りcanvasで描いた絵をURLに変換してDBに保存する際に数日詰まったので自分への戒めのために今回の記事を書く運びとなりました。

詰まったところ

ArgumentError (When assigning attributes, you must pass a hash as an argument.):

ハッシュにしてから受け渡してください的なエラーが出る。

受け渡したデータ

JavaScriptでcanvasのデータをURL化しているため、そのままJavaScriptでPOSTしました。
こんなかんじ

//変数imageにはcanvasのURLが入っています
function send_url(image){
                var form = document.createElement('form');
                var request = document.createElement('input');

                form.method = 'POST';
                form.action = 'save';

                request.type = 'hidden';
                request.name = 'text';
                request.value = image;

                form.appendChild(request);
                document.body.appendChild(form);

                form.submit();
}

受け取り側はこのようになっています

private
    def post_params
          params.require(:text)
    end
end

この形式で受け取るとpost_paramsは送信されてきた文字列だけをデータに持ちます。

修正後

先ほどの関数post_paramsを次のように書き換えました

def post_params
      img = params.require(:text)
      hash_url = {"image_url" => img}
      return hash_url
end

力技です。

最後に

セキュリティなどの観点で見てこれが大丈夫なやり方なのかはわかりませんが、とりあえずこのハッシュにしたデータを渡すことで無事URLをDBに渡すことができました。

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