20190622のRubyに関する記事は17件です。

2019年にRails + Ajaxを整理してみる(サンプルアプリ&コード付き)

Rails上でAjaxを動かす、という良くありそうな話。

ただ調べてみるとやり方が色々あって、
Rails歴半年ちょいの私には何が正しいのかさっぱり分からなかった。

という訳で整理してみる。同じ境遇にある人の助けになれば嬉しい。

jQueryとかを用いた、古式ゆかしい(らしい)やり方ですので、
Vue/React等をお使いの方々はおかえりください(涙)

間違ってる部分とかあったら、コメントいただければ幸いです。
(特に図の部分)

この記事がよく刺さりそうな人

  • Railsの基礎はわかる
  • Ajaxの雰囲気はわかる
  • JavaScript & jQueryも本気出せばちょっと書ける
    (決してチョットデキルではない)
  • RailsでAjaxはあまりやった事がない
    もしくは「良く分からんけどまぁ動いてるからヨシ!」で乗り切った

とりあえず結論

Rails + Ajax の実現方法は、
ざっくり以下の3パターン&その組み合わせっぽい。

1. Rails推奨方式
2. フロントはJavaScriptだけでやる方式
3. AjaxのリクエストはRails & レスポンス以降はJavaScriptでやる方式

方式別サンプルアプリ&コード

作ったのは、フォームに文字を入力&ボタンを押すと、Ajaxを使って文字が書き換わるアプリ。
(アプリを名乗るのはおこがましいかもしれない)

これを上の3方式のそれぞれでやってみた。
Ajaxでやる意味ある?というツッコミは無しで...

ajax-succeed.gif

コードは本当に必要最低限なので、色々細かいツッコミはご容赦くださいm(_ _)m
逆に手元で再現する分にはやりやすいはず。。。

あとjQueryを使ってます。入れ方は以下参照。
Rails 5.2 jQuery 動かし方 - Qiita

共通処理

サーバ(Rails)側の処理は、3つの方式でほぼ共通。

routes.rb
Rails.application.routes.draw do
  get 'static/top'
  post 'static/ajax_update', to: 'static#ajax_update'
  post 'static/ajax_update2', to: 'static#ajax_update2'
end
static_controller.rb
class StaticController < ApplicationController
  def top
  end

  # 1. Rails推奨方式 で使用
  def ajax_update
    @text = params[:data]
    render
  end

  # 2. フロント側はJavaScriptだけでやる方式 
  # 3. AjaxのリクエストはRails & レスポンス以降はJavaScriptでやる方式
  # で使用
  def ajax_update2
    @text = params[:data]
    render plain: @text
  end
end

1. Rails推奨方式

Railsガイドに書かれたやり方
Rails で JavaScript を使用する - Rails ガイド

ajax_update.js.erb
var user = '<%= "#{ @text }" %>'
$('#ajax-test1').text(user);
top.html.slim
h1 Static#top
p Find me in app/views/static/top.html.slim

#ajax-test1 Ajax: Rails依存

#ajax-request1
  = form_with url: static_ajax_update_path do |f|
    = f.text_field :data
    = f.submit 'Post Ajax'

図に表すと多分以下の感じ。
もうガッツリRailsに乗っかっている状態。

rails-ajax.001.jpeg

肝は以下2点

  • xxx.js.erbからJavaScriptをレンダリングしてフロントに返す
  • フロント側で受け取ったJavaScriptを実行

個人的にはRails側で、JavaScriptをレンダリングしている辺り、
少し気持ち悪い。。。

ただコード量は必要最低限で済むし、
Rails推奨であることからトラブルも起きにくそう。
基本はこれでいいのではないだろうか。

なお細かい処理がしたい場合には不便になることもある様子で、
何だかんだ使わないと言う話もあるらしい。
参考:https://qiita.com/ka215/items/dfa602f1ccc652cf2888

2. フロント側はJavaScriptだけでやる方式

Railsにあえて叛逆していくやり方。

top.html.slim
#ajax-test4 Ajax: ほぼJS(jQuery)
= text_field_tag 'static[ajax_data2]'
= button_tag 'Post Ajax', id: 'btn2'
ajax_request_response.js
$(document).ready( () => {
  $('#btn2').on('click', (e) => {
    e.preventDefault();

    const param = $('#static_ajax_data2').val();

    // CSRFトークンを取得&セット
    $.ajaxPrefilter( (options, originalOptions, jqXHR) => {
        if (!options.crossDomain) {
          const token = $('meta[name="csrf-token"]').attr('content');
          if (token) {
               return jqXHR.setRequestHeader('X-CSRF-Token', token);
           }
        }
    });

   $.ajax({
      url: `/static/ajax_update2`,
      type: 'POST',
      data: {
        data: param
      }
    })
    .done( (data, textStatus, jqXHR) => {
      var result = $('#ajax-test4');
      result.text(data);
    });
  });
});

図に表すと多分以下の感じ。
Ajaxに関しては、Railsには頼らないという強い意思が見える。

rails-ajax.002.jpeg

肝は、
RailsのCSRF対策のために、
CSRFトークンの取得&セットを行なっている所。

具体的なやり方は以下の記事を完全リスペクトしましたm(_ _)m
https://qiita.com/a_ishidaaa/items/7c3fa339d3bea25a9ba8

ざっくり言うと、RailsではCSRFという脆弱性への対策として、
Postのリクエスト時にトークン(身分証明みたいなもの)を使っている。
ここをカバーしてあげないと、JavaScriptからPostは出来ない。

そう、Railsからの叛逆に成功したと思いきや、
実はその呪縛から逃れきれていなかったのだ。
なんかエモい。

なお、RailsのCSRFについての詳細は以下の記事等をご参照ください。
外部からPOSTできない?RailsのCSRF対策をまとめてみた - Qiita

3. AjaxのリクエストはRails & レスポンス以降はJavaScriptでやる方式

Railsへの依存を減らしつつ、CSRF対策はRailsによろしくできるやり方。

top.html.slim
= form_with url: static_ajax_update2_path, id: 'ajax-request-3' do |f|
  = f.text_field :data
  = f.submit 'Post Ajax'
ajax_response.js
// Ajax: form送信はRails、受信以降はJS
$( () => {
  $('#ajax-request-3').on('ajax:success', (e) => {
    const result = $('#ajax-test3');
    result.text(e.detail[0]);
  });
});

図に表すと多分以下の感じ。
折衷案な雰囲気。

rails-ajax.003.jpeg

肝は、
RailsとJavaScriptの間で、
どのようにデータをやり取りされるかの理解が必要な所。

適当にやってると変なハマり方をしそう。。。

ただそこさえクリアすれば、
Railsっぽさと自由度をある程度両立できる?気がする(よく分かってない)

まとめ

  • 基本的には大人しく「1. Rails推奨方式」を使った方がいい気がする。
    (特に経験浅めの人)
  • ただ不便な場合もある(らしい)ので、
    その際は「3. AjaxのリクエストはRails...」を採用、
    もしくは「1. Rails推奨方式」と組み合わせて使えば良さそう。
  • Railsで開発するけどなるべく依存したくないというワガママな人は、
    「2. フロント側はJavaScriptだけでやる方式」を使えばいい...のか?

参考サイト

以下本記事作成に際しお世話になったサイト。見ると理解がすごく深まる。。。
Ruby on RailsのAjax処理のおさらい - Qiita
Rails 5.1+jQueryでajaxを試す (罠にハマる) - Qiita
Rails 5.2 jQuery 動かし方 - Qiita
RailsでのAjax - Qiita
jQuery.ajax()のまとめ: 小粋空間
Rails 5.2 jQuery 動かし方 - Qiita

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

railsで複数ワードでの検索機能(end)とマイナス検索機能(-)を実装してみる

はじめに

 半年前にrailsで複数ワードでの検索機能(or)とマイナス検索機能(-)を実装してみるという記事を投稿させてもらったのですが、沢山の方に見てもらい良質なフィードバックまで頂きました。本当にありがとうございます!
 そこで記事の続きという訳では無いですが、複数ワードでの検索時にorでは無くendで検索ができるようなコードをフィードバックを踏まえて書いてみました。また今回も完成品のコードを最後に置いておきます。

環境

Ruby 2.3.3
Rails 5.2.3
MySQL 8.0.13

前提として

まず前提としてこんな感じの検索フォームからコントローラの方に検索ワードを送る。

index.html.erb
<%= form_tag('/items/search', method: :get) do %>
   <input id="page_name" name='keyword' size="30" type="text" />
<% end %>

その後にコントローラで入力された検索ワードに合わせてデータを引っ張ってきて表示する。

search.html.erb
<% @items.each do |item| %>
  <%= item.name %>
<% end %>

ちなみにDBにはこんなデータが入っているとします。

mysql> select name from items;
+------------------+
| name             |
+------------------+
| 水筒A            |
| 水筒B            |
| 水筒C            |
| 大きい水筒       |
| 大きい水筒A      |
| 小さい水筒       |
| 小さい水筒A      |
+------------------+
7 rows in set (0.00 sec)

前提のコード(orとマイナス検索)

# キーワード分割
keywords = params[:keyword].split(/[[:blank:]]+/).select(&:present?)

# 普通のキーワードとマイナスのキーワードを分ける
negative_keywords, positive_keywords = 
  keywords.partition {|keyword| keyword.start_with?("-") }

# 空のモデルオブジェクト作成(何も入っていない空配列のようなもの)
@items = Item.none

# 検索ワードの数だけor検索を行う
positive_keywords.each do |keyword|
  @items = @items.or(Item.where("name LIKE ?", "%#{keyword}%"))
end

# -(マイナス)がついた検索ワードの数だけnot検索を行う
negative_keywords.each do |keyword|
  @items.where!("name NOT LIKE ?", "%#{keyword.delete_prefix('-')}%")
end

前回の記事でkg8mさんから教えて頂いた複数ワードでの検索機能(or)とマイナス検索機能(-)のコードです。短くて素敵。これをorからandに変えていく形で解説を入れながらコードを書いていきます。

実際に書いてみる

必要な作業は
1,送られてきたキーワードを空白で区切る(キーワード分割)
2,普通のキーワードと-(マイナス)のついたキーワードを分ける
3,普通のキーワード群でAND検索を行う
4,-(マイナス)のキーワードでNOT検索を行う

早速1から行きます。

1、送られてきたキーワードを空白で区切る(キーワード分割)

検索フォームから送られてきた「水筒 A -小さい」のようなキーワードの文字列を「"水筒","A","-小さい"」と空白で区切って複数のワードに分ける作業です。この時はまだマイナスは気にしません。

items_controller.rb
def search
  # キーワード分割
  keywords = params[:keyword].split(/[[:blank:]]+/).select(&:present?)
end

params[:keyword]に検索フォームで入力された検索ワードが入っています。
それをsplitメソッドで分割を行います。[:blank:]は簡単に言ったら空白やタブという意味です(この記事を参考にしました)。つまり空白で区切って配列にするぜ!という意味になります。

その後にselectメソッド配列から何も入っていない要素を削除します。このメソッドの使い方はリファレンスを見るとわかりやすい。あ、present?はちょっと説明が難しいのですが何か値があるか?で真と偽を返すようです。
そもそも空白で区切ってなんで配列の要素にそんなのがあんだよ!ってなるんですが、前回の記事のここに理由を書いてあるので気になった方は読んでみてください。

2、普通のキーワードと-(マイナス)のついたキーワードを分ける

「"水筒","A","-小さい"」のように配列になったキーワード群を普通のキーワードの配列とマイナスのキーワードの配列に分けます。

items_controller.rb
def search
  # キーワード分割
  keywords = params[:keyword].split(/[[:blank:]]+/).select(&:present?)

  # 普通のキーワードとマイナスのキーワードを分ける
  negative_keywords, positive_keywords = 
    keywords.partition {|keyword| keyword.start_with?("-") }

end

keywords に配列で検索ワードが入っています。これをpartitionメソッドで普通の検索ワードが入った配列とマイナスの検索ワードが入った配列に分けます。
partitionメソッドは配列の要素1つ1つを調べて真になったら1つめの配列型の変数に(今回の場合はnegative_keywords)、偽だったら2つめの配列型の変数に(今回の場合はpositive_keywords)に入れ直してくれます。
今回は1つ1つのキーワードに対してstart_with?("-")としているので要素の先頭が「-」だったらnegative_keywordsに要素を入れるといった動きになります。

3,普通のキーワード群でAND検索を行う

ここが一番大切なポイントです!この部分はor検索の時は以下のようになっていました。

items_controller.rb
# 検索ワードの数だけor検索を行う
positive_keywords.each do |keyword|
  @items = @items.or(Item.where("name LIKE ?", "%#{keyword}%"))
end

キーワードを1つづつwhereで検索をかけてそれをorで繋げる感じ。ただ今回はandなので普通にwhere句を重ねていく。

items_controller.rb
def search
  # キーワード分割
  keywords = params[:keyword].split(/[[:blank:]]+/).select(&:present?)

  # 普通のキーワードとマイナスのキーワードを分ける
  negative_keywords, positive_keywords = 
    keywords.partition {|keyword| keyword.start_with?("-") }

  # Itemモデルオブジェクト作成
  @items = Item

  # 検索ワードの数だけand検索を行う
  positive_keywords.each do |keyword|
    @items = @items.where("name LIKE ?", "%#{keyword}%")
  end
end

これでいけるはず。
ただし@items = Itemはどうなんでしょうかね?これOKなんですかね?

4,-(マイナス)のキーワードでNOT検索を行う

ここは元の所と変える必要が無かった。はずだった。
とりあえず元のコードを解説。

items_controller.rb
# -(マイナス)がついた検索ワードの数だけnot検索を行う
negative_keywords.each do |keyword|
  @items.where!("name NOT LIKE ?", "%#{keyword.delete_prefix('-')}%")
end

NOT LIKEでキーワードに引っかかったデータを除外している。delete_prefixメソッドは文字列の先頭に引数の文字があれば削除するというもの。negative_keywordsにはマイナスキーワードが ["-小さい","-コンパクト"] といった感じで入っていますが、これをこのまま「-小さい」で検索しても「小さい」にはヒットしません。
なのでdelete_prefixを使い先頭の-を削除してから検索をしている訳です。

で、問題はここから。このdelete_prefixメソッドはRubyのバージョンが2.5で実装されたメソッドなので自分の開発環境の2.3では使えない。このメソッドを教えて頂いた前回の記事ではあろうことか開発環境の項目にrailsとMysqlだけでRubyのバージョンを書いていないというアホみたいな事をやらかしているという・・・すまねぇすまねぇ。

しょうがないのでdelete_prefixを使わない方向で実装する。

items_controller.rb
def search
  # キーワード分割
  keywords = params[:keyword].split(/[[:blank:]]+/).select(&:present?)

  # 普通のキーワードとマイナスのキーワードを分ける
  negative_keywords, positive_keywords = 
    keywords.partition {|keyword| keyword.start_with?("-") }

  # Itemモデルオブジェクト作成
  @items = Item

  # 検索ワードの数だけand検索を行う
  positive_keywords.each do |keyword|
    @items = @items.where("name LIKE ?", "%#{keyword}%")
  end

  # マイナスキーワードの先頭から-を取り除く
  negative_keywords.each {|word| word.slice!(/^-/) }

  # マイナスキーワードでnot検索
  negative_keywords.each do |keyword|
    next if keyword.blank?
    @items = @items.where.not("name LIKE ?", "%#{keyword}%")
  end
end

まずslice!メソッドでマイナスキーワードから先頭の-を取り除く。そして後はnot検索を行います。keyword.blank?は「""」みたいな要素が来た時の対策です。詳しくは前回の記事のここを見てみてください。

これで完成です!

改善点

@items = Itemがちょっと気になる。
元々のor検索では@items = Item.noneだったけど、and検索だと常に検索結果が0件になる。推測だけどnoneは空のモデルを取得するって意味らしいが、アクションレコードとしてSQLが発行されて何もヒットしなかった(だから空のモデル)扱いになってるのかな?
だとすると「"水筒","大きい"」で検索した場合条件は
1.none(ヒット無し)
2.水筒
3.大きい
になるから、orだったら1~3のどれかに合致すればデータを引っ張ってこれたけど、andだと全ての条件に合致しなければいけないから何のデータにも引っかからないnoneがあると検索結果が常に0件になるんだと思う。なので@items = Item.allで取り敢えず全データを入れてたけれど、all無しでも動いたので無しで動かしてる。アクションレコードに関しては勉強不足だなー。

完成品(コード)

items_controller.rb
  def search
    keywords = params[:keyword].split(/[[:blank:]]+/).select(&:present?)
    negative_keywords, positive_keywords =
    keywords.partition {|keyword| keyword.start_with?("-") }

    @items = Item

    positive_keywords.each do |keyword|
      @items = @items.where("name LIKE ?", "%#{keyword}%")
    end

    negative_keywords.each {|word| word.slice!(/^-/) }

    negative_keywords.each do |keyword|
      next if keyword.blank?
      @items = @items.where.not("name LIKE ?", "%#{keyword}%")
    end

  end

あと今回のコードは検索フォームにキーワードが入力されていなかった場合の処理を書いていないので必要に応じて付け足してください。

おわりに

Ruby(Rails)は面白いメソッドが多いと思う。自分でガリガリ書かなくても良いようになっていて便利だと感じています。あと久しぶりに記事を書いたからマトモに書けてるか心配です・・・

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

Qiitaを始めてみました。

初めまして、ユウキと言います。
今35歳の労働ワーカーですが、以前から興味があったプログラミング学習を
2019年から始めました:relaxed:

最初はHTMLとCSSの学習をドットインストールやprogateなどを使って行い、
サンプルサイトを作ったりしていました。

そして今週からRUbyの勉強を開始。まだ条件分岐などの基礎な部分しかしてないですが、
とても楽しいです:grinning:

近い将来、WEBサービスの開発やプログラマーとしてお仕事したいんですが、今は毎日コツコツ
手を動かし、思考してステップアップに励んでいます。

身近にプログラマーや学習している方々がいないので、こちらのサイトでは色々な方々と交流できれば
幸いです!

どうぞよろしくお願いします。

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

現場で使うコントローラーの作成方法

概要

プログラミングスクールなどで習うrails g controller コントローラー名というコマンドでのコントローラー作成方法は、実際の現場では使用する頻度が少ないという。では実際の現場で使うコントローラー作成方法をどのようなものなのか。簡単にまとめてみた。

なぜ現場ではrails g controller コントローラー名というコマンドで作成しないのか?

結論!!余計なファイルができると管理しづらい!

これに尽きるということ!

では実際はどうやるのか…

現場で使うコントローラー作成方法とは

1.app/controllers/の中に新しいファイルを作成。

app/controllers/ディレクトリの中にコントローラー名_controller.rbという新しいファイルを作成する。

2.コントローラー名_controller.rbに、コントローラークラスを作成していく。

class コントローラー名Controller 

end

これだけだと、クラスを定義しただけなので、コントローラーとして機能しない。

なので、app/controllers/application_controllerの機能を引き継いであげる。(< ApplicationControllerを追加)

class コントローラー名Controller < ApplicationController

end

これで、コントローラー名Controllerが、コントローラーとしての役割をしてくれるため、

結果 →  コントローラー作成!!(ApplicationControllerの機能をすべて引き継いだ)

まとめ

以上、現場で使うコントローラー作成方法をまとめてみた。今回は実際に現場で働いた実績のあるフリーランスエンジニアの方の方法を自分なりに噛み砕いて、アウトプット。初学者ないし、これからエンジニアとして現場に入る方の参考になれば幸いです。

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

【初学者向け】プログラミングで処理を実装する時は流れを考えよう!

はじめに

初学者向けに書いています。
一連の処理を作るときの考え方を書きました。

意識して欲しいこと

実装の要素を分解し、流れを考える

何かを実装したいと思ったら、その実装に必要な要素やフロー(流れ)を考えて書き出してみましょう。

料理で例えましょう。
あなたは「カレーを作りたい」と思った時、魔法使いでも無い限りいきなりカレーを空間に出現することはできないと思います。
きっと手順として頭の中で以下と似た様なフローを思い描くと思います。
(※食材の調達や細かい工程は割愛しています。カレールゥじゃ無くてスパイスからとかレトルトをチンするとか言わない。)

  1. 具材を切る
  2. 具材を炒める
  3. カレールゥを入れて煮込む

プログラミングも同じです。
何かを実装したいと思ったら上記の様にフローを思い描いてから実装に取り組んでみましょう。
そうすればどんな処理が必要か、何の値が必要かと行ったことが明確になり、実装中に迷子にならずに済みます。

具体例

あなたは唐突に「ユーザーに入力させた数字を二乗にして表示したい」という気持ちに駆られたとします。
この処理をフローで書き出しましょう。大体以下の様になるかと思います。

  1. ユーザーに数字を入力させる
  2. 数字を二乗する
  3. 答えを表示する

というわけで早速実装しましょう。
慣れるまでは作業ファイルにコメントアウトでフローを書いておくのもありですね。

作業ファイル
# 好きな数字を入力させる
# 二乗する
# 表示する

丁寧に描くとこんな感じ。

作業ファイル
# 好きな数字を入力させる
input = gets.to_i
# 二乗する
answer = input ** 2
# 表示する
puts "二乗した結果#{answer}になりました。"

もちろんプログラミングは自由に書いて良いので上記が絶対では無いです。
3行も書くのは勿体無いなというストイックマンは1行で実装するかもしれないし

ストイックマン
puts "二乗した結果#{gets.to_i ** 2}になりました。"

丁寧な人は、入力させる前にユーザーに分かりやすいようアナウンスを入れて、尚且つ二乗処理を使い回せる様にメソッドにするかもしれません。

丁寧な人
def multi(number)
  number ** 2
end

puts "好きな数字を入力してください。"
puts "二乗した結果#{multi(gets.to_i)}になりました。"

どの様に書いても結果良ければ全てよし!ではありますが、ゆくゆくは他人と一緒に開発することを想定して読みやすく、無駄なく書く様心がけましょう。

まとめ

今回の具体例はフローを書くまでもなくとても簡単な処理ですが、複雑な処理になると初学者は何を書いているか見失いがちです。
実装したいことに向けてしっかりと必要な要素、フローを書いて落ち着いてして一つ一つ実装して行きましょう。

最後に

「ここが分かりにくかった」「これは何?」などの質問や、レイアウト等の書き方にアドバイスがある方はお気軽に(優しく)コメントを頂けると助かります。

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

rails g ~ で指定のディレクトリ下にフォルダを格納したい場合

超基本ですが、一応メモってことで書いときます。

apiモードで作成する時、app/controllers/api/v1の下にuserコントローラーを置きたい時は、

terminal
$ rails g controller api::v1::users index show new create edit update destroy

これで、

app/controllers/api/v1/user_controller.rb # これが作成される

ルーティングは、こうですね。

config/routes.rb
namespace 'api' do
 namespace 'v1' do
  resources :users
 end
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rails g ~ でapp/controllers/api/v1みたいな感じでファイルを格納したい場合

超基本ですが、一応メモってことで書いときます。

apiモードで作成する時、app/controllers/api/v1の下にuserコントローラーを置きたい時は、

terminal
$ rails g controller api::v1::users index show new create edit update destroy

これで、

app/controllers/api/v1/user_controller.rb # これが作成される

ルーティングは、こうですね。

config/routes.rb
namespace 'api' do
 namespace 'v1' do
  resources :users
 end
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RubyのバージョンアップをしたらHerokuにデプロイできなくなった

tl;dr

個人でつくってるプロダクトで

ふと思い立って、Rubyのバージョンアップを行ったら

Herokuにデプロイできなくなった

それの解決方法というか、、

原因

Rubyのバージョンアップの際に、gem install bundler をして
Bundlerのバージョンが2.0.2 となったからっぽい

$ bundle -v
Bundler version 2.0.2
Gemfile.lock
BUNDLED WITH
   2.0.2

Bundlerを2.0.1で入れ直して、 Gemfile.lock もつくりなおす

$ gem uninstall bundler
$ gem install bundler -v 2.0.1

$ bundle -v  
Bundler version 2.0.1
$ rm Gemfile.lock
$ bundle --without production

これでできる

$ git push heroku master

おまけ

バージョンアップしたらRailsコマンドが叩けなくなった

rbenv: rails: command not found

The `rails' command exists in these Ruby versions:
$ rbenv exec gem install bundler

$ rbenv rehash

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

【Mac】まだ初期のrubyバージョン使用しているの?rbenvを使ってバージョンをコントロールしよう!

想定読者

  • 初期のRubyのバージョンを使用している人
  • rbenvを知らない人
  • 最新のRubyを触りたい人。

得られるもの

  • 最新のRubyを使うことができる。(今回はRuby2.6.3を使用。)
  • バージョンコントロールの方法がなんとなくわかる。

準備するもの

  • Homebrew
  • 学ぶ気持ち

筆者のPCスペック

-macOS Mojave 10.14.5

rbenvって何?

簡単に言うとRubyのバージョンを簡単に変えることができるツールです。
ただし、バージョンを変えるだけで実際にはRubyをインストールはしません。
Rubyもインストールする際はruby-buildもインストールする必要があります。

初めに

最初にHomebrewがインストールされているか確認しましょう。
Homebrewの確認は下記のコマンドです。

$ brew -v
Homebrew 2.1.3
Homebrew/homebrew-core (git revision 66a23; last commit 2019-05-22)

※$マークはコピーしなくていいです。
上記が出なかったらこちらからインストールしましょう。

手順

rbenvとruby-buildのインストール

下記のコマンドを打ちましょう。

$ brew install rbenv ruby-build

実際に入っているか確認しましょう、

$ brew list
autoconf    openssl     pkg-config  rbenv       ruby-build
$ rbenv -v
rbenv 1.1.2
$ ruby-build --version
ruby-build 20190423

先ほどインストールした以外にもautoconfopensslpkg-config等入っていますが、今は気にしないでください。

実際にインストールはできているので、次はrbenvの実行後を反映させる為の準備を行なっていきます。

rbenvを初期設定する。

現在rbenvでrubyをインストールしても実行結果は反映されません。
シェルにrubyはrbenvでインストールされたものを使う事を読み込ませる必要があります。

下記のコマンドを打って初期設定を済ませてください。

$ rbenv init
# Load rbenv automatically by appending
# the following to ~/.bash_profile:

eval "$(rbenv init -)"

$ echo eval "$(rbenv init -)" > ~/.bash_profile
$ source ~/.bash_profile

こちらで準備ができました。
次にバージョンの選択を行いましょう。

バージョンを選択する。

下記のコマンドを打つと、rbenvが持っているRubyのバージョンを全て表示してくれます。

$ rbenv install -l
Available versions:
-----一部抜粋-----
  2.6.2
  2.6.3
  2.7.0-dev
  2.7.0-preview1
-----一部抜粋-----

今回は2.6.3を選択します。(2.7.0はありますが、安定版ではないので今回はインストールを行いません。)
選択すると、ruby-buildが自動的に起動し、インストールを行ってくれます。

$ rbenv install 2.6.3
ruby-build: use openssl from homebrew
Downloading ruby-2.6.3.tar.bz2...
-> https://cache.ruby-lang.org/pub/ruby/2.6/ruby-2.6.3.tar.bz2
Installing ruby-2.6.3...
Installed ruby-2.6.3 to /Users/username/.rbenv/versions/2.6.3

選択したバージョンを使用する

下記のコマンドでどのrubyをインストールしたか確認できます。
*が今使っているバージョンです。systemというのは初期のバージョンを使用していることを指します。

$ rbenv versions
* system (set by /Users/username/.rbenv/version)
  2.6.3

今回は新しくインストールした"2.6.3"を使用してみましょう。

$ rbenv global 2.6.3
$ rbenv version
  system
* 2.6.3 (set by /Users/username/.rbenv/version)

実際にバージョンを確認してみましょう。

$ ruby -v
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin18]

これで無事にインストールできました!
違うバージョンを使用する際は再度バージョンを選択→インストール→選択する
を繰り返す事でバージョンを自由に変更することができます。

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

Rails で名前付きルーティングと通常ルーティングを併用したいとき

はじめに

Rails の URL で、通常の /hoge/:id のようなルーティングと /hoge/fuga のような名前付きルーティングを併用したいという課題がありました。

その際どのように対応したのかを備忘として残しておきます。

前提環境

  • Rails 5.2.2.1

サンプル例

name カラムのある Post モデルを例にします。

このモデルの name カラムに文字列が入っていれば、id ではなく name の文字列を URL として使うことを想定します。

  • name が空 → /posts/1
  • namehoge と入っている → /posts/hoge

方針

方針としては、Rails ガイド を参考に、以下のように進めました。

  • routes で利用するパラメータ識別子修正
  • ActiveRecord::Base#to_param をオーバーライド
  • 名前付きルーティング・通常ルーティングどちらでもレコードを find できるメソッド作成

対応

routes で利用するパラメータ識別子修正

まず、routes のパラメータ識別子を修正します。param オプションを使うことで可能です。

idname で find したかったので、id_or_name というパラメータ名にしています。

Rails.application.routes.draw do
  # 中略
  resources :posts, param: :id_or_name
  # 中略
end

ActiveRecord::Base#to_param をオーバーライド

次に、ActiveRecord::Base#to_param をオーバーライドします。

name があれば name を、存在しなければそのまま id を利用する形としました。

class Post < ApplicationRecord
  # 中略
  def to_param
    return name if name.presence
    super
  end
end

名前付きルーティング・通常ルーティングどちらでもレコードを find できるメソッド作成

最後に、Post モデルに名前付きルーティング・通常ルーティングどちらでもレコードを find できるメソッドを追加します。

idname でレコードを検索し、どちらかがマッチしていればそのレコードを返却します。
存在しない場合は一応 ActiveRecord::RecordNotFound で例外処理しています。

class Post < ApplicationRecord
  # 中略
  def self.find_by_id_or_name(id_or_name)
    if object = self.where(id: id_or_name).or(self.where(slug: id_or_name)).first
      object
    else
      raise ActiveRecord::RecordNotFound
    end
  end
end

利用するときは以下のようなメソッドの呼び出し方となります。

Post.find_by_id_or_name(params[:id_or_name])

終わりに

name 重複する場合など、考慮する点はまだあるかも知れませんが、一旦こちらで対応はできました。

もっと良いやり方などありましたらコメントいただけるとありがたいです!

参考

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

Rails ~会員登録~

これまでのあらすじ

  1. 仮登録でメールアドレスを登録
  2. 登録したメールアドレスへ本登録画面に飛ぶURLを記載したメールを送信
  3. メールでURLへ遷移
  4. 登録に必要な情報の入力
  5. 登録完了
  6. ログイン
  7. ログアウト

までやっていきたいと思います。


メール送信

前の記事でユーザ仮登録のモーダルウィンドウ表示まで実装しました。
で、登録ボタンで以下のアクションを呼びます。

top_controller.rb
  def create
    @temp_user = TempUser.create_temp_user(temp_users_params)
    respond_to do |format|
      if @temp_user.save
        UserMailer.with(temp_user: @temp_user, locale: params[:locale]).request_registration.deliver_later
        format.js { @status = "success" }
      else
        format.js { @status = "fail" }
      end
    end
  end

TempUserクラスの#create_temp_userメソッドで初期化します。

temp_user.rb
    # 仮ユーザの作成
    def create_temp_user(params)
      # temp_usersにmail_addressで検索 初期化する
      temp_user = find_or_initialize_by(mail_address: params[:mail_address])
      temp_user.last_name = params[:last_name]
      temp_user.first_name = params[:first_name]
      temp_user.token = create_token
      temp_user.expired_at = DateTime.now + 1
      return temp_user
    end

find_or_initialize_byでメールアドレスを検索します。
もし、仮登録済みで本登録していないメールアドレスの場合、後勝ちにして最後に入力した情報でUPDATEします。
もし、temp_userにない場合は、入力情報でINSERTします。
find_or_initialize_byを使用し、saveをするタイミングでINSERTかUPDATEか判定、UPSERTが実現できます。

expired_at(有効期限)は、とりあえず1日にしていますが、設定を外出ししたいですね。

saveメソッドの戻り値でSQLの成功・失敗を判定して処理を判定しています。

成功時は、登録情報を元にUserMailerでメールを作成しています。

user_mailer.rb
class UserMailer < ApplicationMailer
  default from: "hogehoge@gmail.com"

  def request_registration
    @temp_user = params[:temp_user]
    @locale = params[:locale]
    mail(to: @temp_user.mail_address, subject: I18n.t("mailers.user_mailer.request_registration.subject"))
  end
end

ApplicationMailerを継承したUserMailerです。
from:は送信元のメールアドレスを設定します。
request_registrationがメール送信の本体です。
paramsで引数を受け取ってメールを生成します。

メール本体は、viewsの下に作ります。

request_registration.html.erb
<%= stylesheet_link_tag "mailers/request_registration.css", media: "all" %>
<p class="message"><%= t('mailers.user_mailer.request_registration.dear', last_name: @temp_user.last_name, first_name: @temp_user.first_name) %></p>
<pre class="message"><%= t('mailers.user_mailer.request_registration.message_text', expired_at: l(@temp_user.expired_at, format: :default)) %></pre>
<div class="btn">
  <%= link_to t('mailers.user_mailer.request_registration.button'), {controller: 'account', action: 'regist', locale: @locale, token: @temp_user.token } %>
</div>

メソッド名のhtml.erb(HTMLメール)またはtext.erb(テキストメール)を雛形として作ります。
上記は、HTMLメールの雛形です。link_toでaccount_controller.rbのregistメソッドを指定しています。temp_user登録の際、生成したtokenをGETリクエストをパラメタとしてURLに付与し、link_toで生成しています。


メールを受信する

開発中に動作確認したいですが、実際に送ると、誤送信する恐れがあるので、以下のgemを入れます。

Gemfile
  # letter_opener_web
  gem 'letter_opener_web', '~> 1.0'

このgemはメールを送信・受信してくれます。
config/environments/development.rbに設定を追加します。

development.rb
config.action_mailer.delivery_method = :letter_opener_web

また、routes.rbに以下のパスを設定し、送信メールを確認できるようにします。

routes.rb
    # letter_opener_web
    mount LetterOpenerWeb::Engine, at: "/letter_opener" if Rails.env.development?

これで(http://localhost:3000/letter_opener) でアプリが送信したメールをブラウザで確認できます。


登録

メールに記載されたURLを押下すると、登録に必要な情報を入力する画面へ遷移できます。

パスワードについて

ライブラリを使わないでログイン機能を実装するために以下のgemを使います。

Gemfile
# Use ActiveModel has_secure_password
gem 'bcrypt', '~> 3.1.7'

Userモデルにhas_secure_passwordを追加します。

user.rb
class User < ApplicationRecord
  include ActiveModel::Validations
  has_secure_password validations: true

  validates :last_name, presence: true
  validates :first_name, presence: true
  validates :last_name_roman, upper_case_format: true, unless: Proc.new { |p| p.last_name_roman.blank? }
  validates :first_name_roman, upper_case_format: true, unless: Proc.new { |p| p.first_name_roman.blank? }
  validates :sex, inclusion: { in: [0, 1] }
  validates :birthed_on, presence: true
  validates :mail_address, uniqueness: true
  validates :password, password_format: true
  validate :already_used_mail_address

  # already used mail address
  def already_used_mail_address
    unless User.find_by(mail_address: mail_address).nil?
      errors.add(:mail_address, I18n.t("validate.already_use"))
    end
  end

  # create remember token
  def self.create_remember_token
    SecureRandom.urlsafe_base64
  end

  # encrypt
  def self.encrypt(token)
    Digest::SHA256.hexdigest(token.to_s)
  end
end

passwordとpassword_confirmation属性、さらにauthenticateメソッドが使用できるようになります。
さらにDB内ではpassword_digestというカラムで暗号化されたパスワードは保存されます。
(password_digestをmigrationで対象のテーブルに追加します。)

アプリケーションログには、入力値は当然出力されず
DBには、暗号化されたパスワード文字列が登録されます。

登録ボタンでcreateメソッドが呼び出され、登録成功したら完了画面、失敗したら再度登録画面をレンダリングします。

account_controller.rb
  def create
    @user = User.new(users_params)
    if @user.save
      render action: :complete
    else
      render action: :regist
    end
  end

ログイン・ログアウト

application_controller.rbに以下を定義します。

application_controller.rb
class ApplicationController < ActionController::Base

  # filter
  # actionの直前に実行されるfilter
  before_action :set_locale
  before_action :current_user
  before_action :require_sign_in!
  # helper methodとして使用できる
  helper_method :signed_in?

  def set_locale
    I18n.locale = locale
  end

  def locale
    @locale ||= params[:locale] ||= I18n.default_locale
  end

  def default_url_options(options = {})
    options.merge(locale: locale)
  end

  def current_user
    remember_token = User.encrypt(cookies[:remember_token])
    @current_user ||= User.find_by(remember_token: remember_token)
  end

  def sign_in(user)
    remember_token = User.create_remember_token
    # cookieにremember_tokenをsetする
    cookies.permanent[:remember_token] = remember_token
    # remember_tokenを更新する
    user.update_column(:remember_token, User.encrypt(remember_token))

    @current_user = user
  end

  def sign_out
    @current_user = nil
    # cookieのremember_tokenを削除する
    cookies.delete(:remember_token)
    redirect_to login_path
  end

  def signed_in?
    @current_user.present?
  end

  private

  def require_sign_in!
    redirect_to login_path unless signed_in?
  end
end

filter処理として
current_userメソッド
ログイン時にCookieとDBに登録したトークン情報を突き合わせて、ユーザ情報を取得する
require_sign_in!メソッド
ユーザ情報が取得できない場合(=ログインしていない場合)ログイン画面へリダイレクトする

これをapplication_controller.rbに定義することによりapplication_controllerを継承する全てのcontrollerにこのfilter処理が走ります。

これにより画面上でログイン状態・非ログイン状態を判断するわけです。

ログイン処理はsession_controller.rbに定義しています。

session_controller.rb
class SessionsController < ApplicationController
  # filter
  # actionの直前に実行されるfilterをskipする
  skip_before_action :require_sign_in!, only: [:new, :create]
  # actionの直前に実行されるfilter
  before_action :set_user, only: [:create]

  # GET /login
  def new
    redirect_to root_path
  end

  # PUT /login
  def create
    if @user.authenticate(@session.password)
      sign_in(@user)
    else
      @session.sign_in_failure
    end
    render "top/index"
  end

  # DELETE /logout
  def destroy
    sign_out
    redirect_to login_path
  end

  private

  def set_user
    @session = Session.new(session_params)
    if !@session.valid?
      render "top/index" and return
    end
    @user = User.find_by!(mail_address: @session.mail_address)
  rescue
    @session.sign_in_failure
    render "top/index"
  end

  def session_params
    params.require(:session).permit(:mail_address, :password)
  end
end

Sessionモデルはmail_addressとpasswordのログイン認証に必要な情報をもっているクラスです。

skip_before_actionは、actionの直前に実行されるfilterをskipするための宣言です。
当然ログイン画面表示とログイン処理にログイン認証のfilterが入っているとログインできないため、skipしています。

createメソッドの前のみset_userのfilterが適用されます。

set_userメソッドは画面から入力されたフォーム情報を元にmail_addressでユーザ情報を取得します。
取得できない場合、例外を発生させます。例外発生時はログイン画面をレンダリングします。

createメソッドは、set_userメソッドで取得したユーザ情報とパスワードを確認します。
確認できた場合、ログイン処理(application_controller.rbに定義)をします。

ログイン処理は、ログイン認証していることを示す、remember_tokenを画面に保持します。
remember_tokenはUserクラスで定義したメソッドでランダム文字列で生成されます。
それをCookieに保存し、その後DB(userテーブル)UPDATEします。

前述のログイン確認のfilterはこのremember_tokenを元にログインしているか、していないかを判断するというわけです。


終わりに

  • 勉強会の資料なので、急いで作ったので、もうちょっと修正します。
  • 参考リンクとかも記載せねば…

参考

工事中…

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

「プログラミング言語の歴史を会話方式で振り返る」を横目で見ながらの会話

この記事は,以下の記事を見ながら書いた。

プログラミング言語の歴史を会話方式で振り返る - Qiita

いろいろな言語が挙げられているが,「あの言語も書けばいいのに」というのは誰でもたくさん思いつくだろう。しかし,それを言い出すとキリが無いので別記事にした。

作風およびキャラクターは @Yametaro さんによる一連の記事を直接真似ている(許してや〜)。
登場人物は以下のとおり

  • やめ太郎:最近どっかの会社に転職したおじさんプログラマー。
  • ルビ子:やめ太郎が勤める会社の社長の娘。物マニア(決して monomania=偏執狂 ではない)というかオブジェ嗜好な女子。

なお,筆者はこの記事に出てくる三つの言語はどれも知らん。ツッコミ歓迎やで。

プロローグ

やめ太郎「つまりこの記事はパクリ言うことやな」
筆者「パクリとは人聞きが悪いな,オマージュやがな」
ルビ子「うち,めっちゃ好き! 最中も人形焼も!」
やめ太郎「それはおまんじゅうや!」

やめ太郎「ぼちぼち始めよか」
ルビ子「うち,C# と同い年や」
やめ太郎「えっ,シーシャ?」
ルビ子「水タバコとちゃうわ!」

SNOBOL

ルビ子「ちょっとマイナーなとこで SNOBOL とか」
やめ太郎「ワイ南国生まれやらウインタースポーツはちょっとなあ」
ルビ子「スノーボードちゃうわっ! SNOBOL やっ!」
やめ太郎「登場が 1962 年か,古いな」
ルビ子「うち,生まれてへんし」

やめ太郎「なんで SNOBOL なん?」
ルビ子「連想配列ってあるやん」
やめ太郎「Ruby ではハシシいうんやろ?」
ルビ子「大麻吸ってどないすんねん! ハッシュやっ!」
やめ太郎「せやなー」
ルビ子「あれのルーツが SNOBOL らしいねん」
やめ太郎「ほな,今のほとんどの言語に影響しとるやん」
ルビ子「そやねん」
やめ太郎「すごいな」
ルビ子「組込みで連想配列を持ったんは SNOBOL4 が最初らしいわ」
やめ太郎「へー」

Occam

ルビ子「次,Occam いこか」
やめ太郎「知らんからほっかむりするで」
ルビ子「スルーしたるわ」
やめ太郎「で,なんで Occam なん?」

ルビ子「Occam はトランスピューターっていう並列コンピューティング用のマイクロプロセッサーのために開発された言語やねんて」
やめ太郎「え? ルビ子ちゃん,ぴゅう太 知っとるんか,懐かしいなあ」
ルビ子「そうそう,日本語 BASIC で モシ 〜 ナラバ ……ってちゃうわっ!」
やめ太郎「まあどっちも 1980 年代前半に登場したんやけどな」
ルビ子「分かっとってボケたんかい!」
やめ太郎「てゆーか,ホンマにぴゅう太を知っとったとは」
ルビ子「Wikipedia で見ただけやで〜」

やめ太郎「そういえばワイの知り合いでもトランスピューターで数値計算しとったんがおったような」
ルビ子「今の GPU だのマルチコアだのよりも遥か昔によお」
やめ太郎「スゴイな」

ルビ子「Occam は実用的な並列計算機用言語として先駆的やったんやね」
やめ太郎「言語の特徴とかは?」
ルビ子「Wikipedia からのコピペやけど,直列に計算してほしいところはこう」

SEQ
   x := x + 1
   y := x * x

ルビ子「並列に計算してほしいところはこう」

PAR
   x := x + 1
   y := y * 2

やめ太郎「インデントが気になるな」
ルビ子「そう,Occam は階層構造をインデントで表すわけ」
やめ太郎「分かった! そういうのを,アレ,ええと,ユーミンの曲で『何をゴールにするねん』みたいな」
ルビ子「それは『ノーサイド』やろ! インデントで階層を表すんはオフサイドルールや!」
やめ太郎「オフサイドルールと言えばバイソンやな」
ルビ子「Python や!」
やめ太郎「掃除機ちゃうんか」
ルビ子「それは Dyson やっちゅーねん!」
やめ太郎「その Python のイメージが強いから 1990 年代かと思った」
ルビ子「オフサイドルール自体は 1960 年代からあるらしいで」
やめ太郎「へー」

CLU

やめ太郎「CLU って登場が 1974 年か,聞いたこともないわ」
ルビ子「あんまり使われへんかったみたいね」
やめ太郎「誰が考えたん?」
ルビ子「バーバラ・リスコフ と学生たち」
やめ太郎「リスコフって,あの?」
ルビ子「そう,『リスコフの置換原則』の人」
やめ太郎「へー」
ルビ子「この人,めちゃめちゃスゴイわ」

やめ太郎「で,なんで CLU みたいなマイナーな言語を?」
ルビ子「今のいろんな言語にかなり影響を与えてるみたいやねん」
やめ太郎「たとえば?」
ルビ子「抽象データ型という概念を導入して,のちのオブジェクト指向プログラミングに先駆けたみたい,知らんけど」
やめ太郎「オブジェクト指向のルーツの一つなんか〜」
ルビ子「関数が多値を返したり,複数の変数に多重代入したり,も CLU が先駆やったみたい」
やめ太郎「Ruby とか多重代入あるもんな」
ルビ子「Ruby のは配列を介してて CLU のとは若干違うけど」
やめ太郎「さよか」

ルビ子「Ruby にはもっと決定的な影響を与えてんねん」
やめ太郎「どんな?」
ルビ子「もう,これこそが Ruby の利点というような」
やめ太郎「というと?」
ルビ子「ブロック付きメソッド呼び出し」
やめ太郎「確かにそれのおかげでコードが簡潔に書けるもんな(知らんけど)」
ルビ子「メソッド呼び出しのとき,値は引数として渡すけど,そのほかに処理をブロックとして渡すことができる」
やめ太郎「それが CLU 由来なん?」
ルビ子「CLU は繰り返しを抽象化する仕組みとしてイテレーターというものを導入してん」
やめ太郎「疲れーたー
ルビ子「まいど,おおきに」
やめ太郎「Ruby との関係はどうなん?」
ルビ子「CLU のイテレーターは名前のとおり繰り返しが目的で,for 文でしか使えなかった」
やめ太郎「Ruby のイテレーターとはだいぶちゃうんかな」
ルビ子「matz は CLU のイテレーターと同じようなものを作ろうとしてメソッドにブロックを与えることを考えたみたい」
やめ太郎「それがブロックの由来かあ」
ルビ子「出来てみたら,繰り返しだけじゃなくて,いろいろ使えることが分かったんやね」
やめ太郎「ファイルを開いてなんかするのもブロックやしな」
ルビ子「Ruby が CLU の影響をどんなふうに受けたかは matz の以下の記事が参考になるわ」
Rubyist のための他言語探訪 【第 2 回】 CLU(Rubyist Magazine 0009 号)

ルビ子「制御構造の構文にも見るべきところがあるねん」
やめ太郎「どういう?」
ルビ子「Pascal や C,JavaScript とかって,if の節には〈文〉がつくやん?」
やめ太郎「せやなー」
ルビ子「複数の文からなるコードを与えたいときは?」
やめ太郎「{ } でくくるんやな」
ルビ子「そう,で,単文のときは { } が要らんねんけど,いわゆる ぶら下がり else 問題が起こりうる」
やめ太郎「それって,しゃあないんちゃう?」
ルビ子「いや,Ruby も Rust も Modula-2 もこの問題は起きひんで」
やめ太郎「なんでやのん?」
ルビ子「節は最初から中身が複数の文からなる前提で,終わりを表す記号やキーワードを設けたから」
やめ太郎「具体的にいうと?」
ルビ子「たとえば Ruby はこう」

if cond then

else

end

ルビ子「Rust ならこう」

if cond {

} else {

}

やめ太郎「Rust の例は C とどこがちゃうねん」
ルビ子「{ } が省略できないってところね」
やめ太郎「CLU もこういうタイプなわけ?」
ルビ子「そう,上の Ruby の例はそのまんま CLU の例でもある」
やめ太郎「へー,1970 年代の前半に解決済みいうことかあ」
ルビ子「C は CLU より古いけど,Java とか PHP,JavaScript みたいな後発の言語がなんで ALGOL の轍を踏み続けたんか理解に苦しむわ」
やめ太郎「せやなー(知らんけど)」

ルビ子「ほかにも,変数が任意の場所で宣言できたりとか」
やめ太郎「そんなん当たりまえちゃうん?」
ルビ子「いや昔の言語は先頭でまとめて宣言せなアカンかったんやて」
やめ太郎「へー」

ルビ子「例外処理も当時は画期的やったんやって」
やめ太郎「いま当たり前のいろんなもんが CLU で先駆的に導入されてたんやな」

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

RubyでTwitterからワードクラウドを作成

はじめに

Twitterのトレンド一覧では、単語しか出てこず、一体何に関する話題なのか分からない。
ということで、トレンドからツイートを取得してワードクラウドを作成することで、一目でどんな話題で盛り上がっているのか可視化しようと思います。

せっかちな方のために、先に結果を載せときます。
トレンド"#きのう何食べた" に対する実行結果
#きのう何食べた

使用技術

Ruby: 2.4.1
Mecab: 0.996

Twitterからデータを取得

アクセストークンの設定

まず、Twitterにアクセスするためのトークンなどを設定します。

require 'twitter'
client = Twitter::REST::Client.new do |config|
    config.consumer_key        = "Consumer Key (API Key)"
    config.consumer_secret     = "Consumer Secret (API Secret)"
    config.access_token        = "Access Token"
    config.access_token_secret = "Access Token Secret"
end

参考
https://qiita.com/shimisunet/items/c3a0b93fb13fa82ed8be

トレンドの取得

次にトレンドを取得します。

client.trends_place(23424856).take(3).each do |trend| # 23424856:日本のtrend
    p trend.name
end

ツイートの取得

トレンドのツイートを取得します。

client.search(word, exclude: "retweets").take(count).each do |tweet|
    p tweet.text
end

前処理

取得したツイートに対し、

  • URLの除去
  • 分かち書き (Mecab)

を行います。

URLの除去

Rubyのuriパッケージを利用します。

require 'uri'
URI.extract(text).uniq.each {|url| text.gsub!(url, '')}

参考
http://thr3a.hatenablog.com/entry/20180315/1521079183

分かち書き

Mecabを利用して分かち書きをします。
副詞など、どのツイートにも出てきそうな単語は除去します。

require 'mecab'
tagger = MeCab::Tagger.new
wakati = tagger.parse(text)
wakati.delete!("EOS")  # 分かち書きの結果に"EOS"が出てくるため除去
wakati = wakati.split("\n")  # 単語ごとに分割
words = []
wakati.each do |w|
    word = w.split("\t")  # タブ文字前に単語、後に単語の情報があるため分割
    if word[1].include?("形容詞") || word[1].include?("名詞")
        unless word[0] == "#"  # "#"は名詞に分類されるため除去
            words.push word[0]
        end
    end
end

ワードクラウド作成

解析に入ります。
分かち書きの結果、トレンド内のツイートに出てくる単語を格納した配列を作成します。
それを元にワードクラウドを作成します。

magic_cloudの裏側ではImage Magickが動作しており、:font_familyに'Arial Unicode'を指定することで日本語に対応できます。(重要)

require 'magic_cloud'
font = 'Arial Unicode'
words =  words.group_by(&:itself).map{ |key, value| [key, value.count] }.to_h
cloud = MagicCloud::Cloud.new(words, rotate: :none, scale: :linear, :font_family=>font)
cloud.draw(500, 250).write("#{word}.png")

参考
Rubyで配列内の重複する要素を数える方法
https://github.com/zverok/magic_cloud

考察

2019年6月22日の夜ごろのトレンド"#きのう何食べた"に対して、以下の結果が得られました。
#きのう何食べた.png

とりあえず、どんな話題なのか読み取れそうです。
私の考察としては、

  • ドラマらしい
  • 来週最終回らしい
  • 個人的にはyajuが気になる。

といったところです。

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

Rubyでワードクラウドを用いたTwitterトレンドの可視化

はじめに

Twitterのトレンド一覧では、単語しか出てこず、一体何に関する話題なのか分からない。
ということで、トレンドからツイートを取得してワードクラウドを作成することで、一目でどんな話題で盛り上がっているのか可視化しようと思います。

せっかちな方のために、先に結果を載せときます。
トレンド"#きのう何食べた" に対する実行結果
#きのう何食べた

使用技術

Ruby: 2.4.1
Mecab: 0.996

Twitterからデータを取得

アクセストークンの設定

まず、Twitterにアクセスするためのトークンなどを設定します。

require 'twitter'
client = Twitter::REST::Client.new do |config|
    config.consumer_key        = "Consumer Key (API Key)"
    config.consumer_secret     = "Consumer Secret (API Secret)"
    config.access_token        = "Access Token"
    config.access_token_secret = "Access Token Secret"
end

参考
https://qiita.com/shimisunet/items/c3a0b93fb13fa82ed8be

トレンドの取得

次にトレンドを取得します。

client.trends_place(23424856).take(3).each do |trend| # 23424856:日本のtrend
    p trend.name
end

ツイートの取得

トレンドのツイートを取得します。

client.search(word, exclude: "retweets").take(count).each do |tweet|
    p tweet.text
end

前処理

取得したツイートに対し、

  • URLの除去
  • 分かち書き (Mecab)

を行います。

URLの除去

Rubyのuriパッケージを利用します。

require 'uri'
URI.extract(text).uniq.each {|url| text.gsub!(url, '')}

参考
http://thr3a.hatenablog.com/entry/20180315/1521079183

分かち書き

Mecabを利用して分かち書きをします。
副詞など、どのツイートにも出てきそうな単語は除去します。

require 'mecab'
tagger = MeCab::Tagger.new
wakati = tagger.parse(text)
wakati.delete!("EOS")  # 分かち書きの結果に"EOS"が出てくるため除去
wakati = wakati.split("\n")  # 単語ごとに分割
words = []
wakati.each do |w|
    word = w.split("\t")  # タブ文字前に単語、後に単語の情報があるため分割
    if word[1].include?("形容詞") || word[1].include?("名詞")
        unless word[0] == "#"  # "#"は名詞に分類されるため除去
            words.push word[0]
        end
    end
end

ワードクラウド作成

解析に入ります。
分かち書きの結果、トレンド内のツイートに出てくる単語を格納した配列を作成します。
それを元にワードクラウドを作成します。

magic_cloudの裏側ではImage Magickが動作しており、:font_familyに'Arial Unicode'を指定することで日本語に対応できます。(重要)

require 'magic_cloud'
font = 'Arial Unicode'
words =  words.group_by(&:itself).map{ |key, value| [key, value.count] }.to_h
cloud = MagicCloud::Cloud.new(words, rotate: :none, scale: :linear, :font_family=>font)
cloud.draw(500, 250).write("#{word}.png")

参考
Rubyで配列内の重複する要素を数える方法
https://github.com/zverok/magic_cloud

考察

2019年6月22日の夜ごろのトレンド"#きのう何食べた"に対して、以下の結果が得られました。
#きのう何食べた.png

とりあえず、どんな話題なのか読み取れそうです。
私の考察としては、

  • ドラマらしい
  • 来週最終回らしい
  • 個人的にはyajuが気になる。

といったところです。

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

たのしいRuby(第6版) memo

たのしいRuby(第6版)を読んでのメモです。

p and print method

printメソッドは実行結果やメッセージなどを普通に表示したいとき、
pメソッドは実行中のプログラムの様子を確認したいとき、
と使い分ける。

magic comment

# encoding: 文字コード
でソースコードの文字コードを指定できる。

pメソッドで日本語の文字列を出力すると、
文字化けしたような出力になる場合、
-E 文字コード
の形式でコマンドラインオプションを指定する

opal

OpalはRubyのコードをJavaScriptに変換するコンパイラで、
WebブラウザでRubyを実行する環境を作ることもできる

require method

引数に指定されたライブラリを探して、そのファイルに書かれた内容を読み込む。
読み込みが終わると、requireメソッドの次の行から処理を再開する。

require_relativeメソッドは実行するプログラムが置かれたディレクトリを基準としてライブラリを探す

pseudo variables

nil,true,false,selfなどの特定の値を指し示すために予約された名前で
代入することによって値を変更することはできない

multiple substitution

複数の変数への代入を1つの式で行うことができる。
受け取る側の変数に1つだけ「*」を付けておくと、
その変数には余った値の配列が代入される。

一時変数を使わない値の入れ替えに使うこともできる。

a, b = 0, 1
a, b = b, a
p [a, b] #=> [1, 0]

配列を代入するときに左辺に複数の変数があると、
自動的に配列の要素を取り出して多重代入が行われる。
配列の先頭の要素だけを取り出したい場合には下記のようにも書ける

ary = [1, 2]
a, = ary
p a #=> 1

comparison operator

比較演算子の結果はtrueかfalseとなる

regular expression matching

マッチした場合には文字列中でマッチした部分の位置を、
マッチしなかった場合はnilを返す

boolean value of Ruby

falseとnilを除くオブジェクト全て
falseとnil

trueやfalseを返さないメソッドで、
意味のある値を返せない場合でもnilを返すメソッドであれば、条件判断に利用できる

unless statement

条件が偽(false or nil)のときに文を実行する

case statement

比較したいオブジェクトが1つで、
そのオブジェクトの値によって場合分けしたい場合、
case文を使ったほうがわかりやすく見えることがある。
正規表現を用いた場合分けもできる。

case文はwhenで指定した値に一致するかどうかを「===」演算子を使って判定する。
「===」は左辺が数値や文字列の場合は「==」と同じ意味を持つが、
正規表現の場合は「=~」と同じようにマッチしたかどうかを判定したり、
クラスの場合は右辺がそのクラスのインスタンスかどうかを判定するなど,
両辺の値を比較するよりも、もう少し緩い意味で同じかどうかを判断するために使う

object_id

すべてのオブジェクトはアイデンティティと値を持っている。
オブジェクトのIDはobject_id(or id)メソッドで得ることができる。
2つのオブジェクトが同じかどうか(IDが同じかどうか)はequal?で判定できる。
オブジェクトではなく、値が等しいかどうかを調べるには「==」を使う。

値が同じかどうかを判定するメソッドとしてeql?もあり、
たいていは同じように振る舞うが、数値クラスで再定義されているので、
異なる振る舞いをする。
多少厳密に比較を行う必要がある場合に使う。
例えば、ハッシュのキーとして、0と0.0は別のものとして扱われるが、
ハッシュオブジェクトの内部ではeql?メソッドを使ってキーの比較が行われている

until statement

while文は条件が成立している間は繰り返すのに対して、
until文は条件が成立するまで繰り返す

for statement

for文はRubyの内部処理としてはeachメソッドが実行される特殊な構文になっている。
したがって、eachメソッドを呼び出すことができるオブジェクトであれば、
for文のinのあとに指定することができる

blocked method call

オブジェクト.メソッド名(引数, ・・・) do |変数1, 変数2, ・・・|
  ブロックの内容
end

or

オブジェクト.メソッド名(引数, ・・・) {|変数1, 変数2, ・・・|
  ブロックの内容
}

ブロックの最初の「| 〜 |」で囲まれた部分に指定された変数は
ブロック変数またはブロックパラメータという。
この変数にはブロックを実行するたびに、メソッドからパラメータが渡される。
パラメータの数や値はメソッドごとに異なる

no receiver method

Rubyではレシーバを記述せずに使えるメソッドを関数的メソッドと呼ぶこともある。
実際にレシーバに該当するオブジェクトがないわけではなく、
それが省略されている。
関数的メソッドはレシーバの状態によって結果が変わることがないように作られている。
(例えば、printやsleepメソッド)

variable length argument

引数の数が決められないメソッドは、
変数名」の形式で定義し、与えられた引数をまとめて配列として得られる。
変数名」の形式の引数はメソッド定義の引数リストに1つだけ含めることができる

keyword argument

キーワード引数を使うと引数名と値をペアで引数として渡せるようになる。
「引数名: 値」の形式でデフォルト値も指定できる。
デフォルト値を指定したくない場合は、「引数名: 」と引数名だけ書く。
デフォルト値が省略された引数は呼び出し時に省略できない。

定義に存在しないキーワード引数をエラーにせずに受け取りたい場合は
「**変数名」の形式で受け取る

def meth(x: 0, y: 0, z: 0, **args)
  [x, y, z, args]
end

p meth(z: 4, y: 3, x: 2) #=> [2, 3, 4, {}]
p meth(x: 2, z: 3, v: 4, w: 5) #=> [2, 0, 3, {:v=>4, :w=>5}}

引数argsには、引数リストに存在しないキーワードをキーとして
ハッシュオブジェクトが設定される。

キーワード引数は通常の引数と組み合わせて用いることができる。

def func(a, b: 1, c: 2)
︙
end

aは必須、b,cはキーワード引数となり、
呼び出し時は
func(1, b: 2, c: 3)
のように最初の引数に続けて、キーワード引数を指定する。

ハッシュをキーワード引数として渡すことができる。
キーはシンボルである必要がある

instance_of?, is_a?

instance_of?はクラスのインスタンスであることを調べることができ、
is_a?は継承関係をさかのぼってクラスに属するかどうかを調べることができる。
スーパークラスを指定せずに定義したクラスは
Objectクラスの直接のサブクラスとなる

self variables

インスタンスメソッドの中で、
メソッドのレシーバ自身を参照するにはselfという特殊な変数を使う

private

レシーバを指定して呼び出せないメソッドにする。
レシーバを省略した形式でしか呼べないため、インスタンスの外側から利用できなくなる。
何も指定せずに定義されたメソッドはpublicになるが、
initializeメソッドだけは常にprivateとして定義される

module

クラスとモジュールは下記の点が異なる。
・モジュールはインスタンスを持つことができない。
・モジュールは継承できない。

モジュールの提供するメソッドは「モジュール名.メソッド名」という形式で参照する。
このような形式で使用するメソッドをモジュール関数という。
モジュール内で定義されたメソッドや定義と同名のものが定義されていない場合、
モジュール名の指定を省略できる。
includeを使えば、モジュールが持っているメソッドや定数名を
現在の名前空間に取り込むことができる。
また、クラスにモジュールを追加して複数のクラスでモジュールの機能を共有できる

メソッドをモジュール関数として外部に公開するにはmodule_functionを使う必要がある

Enumarable

Ruby標準の組み込みの機能で、
Mix-inにより機能を提供することができるモジュール

prepend method

Mix-inされたクラスでのメソッドよりも
モジュールのメソッドを優先することができる

extend method

extendメソッド(Object#extend)はモジュールで定義されたすべてのメソッドを
特異メソッドとしてオブジェクトに追加することができる。
モジュールを特異クラスにインクルードして、
オブジェクトにモジュールの機能を追加する

assignment operator

「+」と「=」を組み合わせて、「+=」とするように
二項演算子と代入を組み合わせた演算子。
「=」は右側

logical operator

・左側の式から順に評価される
・論理式の真偽が決定すると、残りの式は評価されない
・最後に評価された式の値が論理式全体の値となる

safe navigation operator(&. method)

nilチェック付きのメソッド呼び出すを行うことができる。
レシーバのオブジェクトがnilの場合はnilを返す。

RailsのActiveSupportにtryというメソッドがあるが、
&.の場合は呼び出そうとしているメソッドが定義されていない場合はエラーになる。
これはtryメソッドがrespond_to?でチェックしているため。
ちなみに、Rails6あたりから、
tryメソッドの内部でtry!メソッドを呼び出すのはやめるのかも
(https://github.com/rails/rails/commit/ba7d1265e3f2755f55243f32c0264c5c20e01610#diff-ed128222705b72beebd1daae7a6be237)

exception handling

例外処理にはbegin〜rescue〜end文を使用する。
rescueに続けて変数名を指定することで例外オブジェクトを得ることができる。
例外オブジェクトはclass,message,backtraceといったメソッドを持つ。

begin
  例外を発生させる可能性のある処理
rescue => 例外オブジェクトが代入される変数
  例外が起こった場合の処理
end

また、rescue節で補足する例外を指定することもできる。

メソッドの処理全体をbegin〜endでくくる場合はbeginとendを省略して、
rescue節やensure節を書くことができる。

ちなみに、rescueは修飾子として使うこともできる
例外を発生させる可能性のある処理 rescue 例外が起こった場合の処理

exception class

すべての例外はExceptionクラスのサブクラス。

rescue節で例外クラスを指定しなかった場合は
ExceptionのサブクラスであるStandardErrorとそのサブクラスが補足される。
自分で例外クラスを定義する場合は
StandardErrorクラスを継承したクラスを作り、さらにそれを継承するのが一般的とのこと

raise an exception

自分で例外を発生させるにはraiseメソッドを使う。

  • raise メッセージ
    (StandardErrorクラスのサブクラスである)RuntimeErrorを発生させる。
    新しく生成された例外オブジェクにメッセージとして文字列をセットする

  • raise 例外クラス
    指定した例外を発生させる

  • raise 例外クラス、メッセージ
    指定した例外を発生させ、
    新しく生成された例外オブジェクにメッセージとして文字列をセットする

  • raise
    rescue節の外ではRuntimeErrorを発生させ、
    rescue節の中では最後に発生した例外($!)をもう一度発生させる

x.divmod(y)

xをyで割ったときの商と余りを配列にして返す

securerandom library

安全な乱数を生成させるモジュールを提供

%Q, %q

「"」と「'」を含めた文字列を作りたいときは
「\"」「\'」などの特殊文字を使うよりも、%Qや%qを使うと簡単

desc = %Q{Rubyの文字列には「''」も「""」も使われます。}
str = %q|Ruby said, 'Hello world!!'|

Here documents

改行を含む長い文字列を作りたい場合は便利。
<<の後ろには終了の記号として" "か' 'で囲った文字列を書く。
インデントを揃えたいときは「<<」の代わりに「<<-」を使う。

5.times do |i|
  print(<<-"EOB")
    i: #{i}
  EOB
end

上記の結果

    i: 0
    i: 1
    i: 2
    i: 3
    i: 4

「<<~」を使うと行頭の空白が切り詰められる

5.times do |i|
  print(<<~"EOB")
    i: #{i}
  EOB
end

上記の結果

i: 0
i: 1
i: 2
i: 3
i: 4

running OS command

`ls -al`

each_line code example

each_lineメソッドを使って繰り返し新しい行を読み込む場合には、
chomp!メソッドなどで破壊的に改行文字を落とすという方法がある

f.each_line do |line|
  line.chomp!
  # lineを処理
end

string code conversion

encodeメソッドを使う

str = '文字列'
str.encode('utf-8')
p str.encoding

Hash#fetch

Hashの値の取り出しに使える。
第2引数を指定すれば、キーが登録されていないときに返す値として使用できる

h = Hash.new
h.store('R', 'Ruby')
p h.fetch('R', '(undef)') #=> "Ruby"
p h.fetch('N', '(undef)') #=> "(undef)"

creating a Hash with default values by block

キーによって異なる値を返したい場合や、
すべてのキーに対する値が同じオブジェクトになることを避けたい場合には、
Hash.newにブロックを指定する

h = Hash.new do |hash, key|
  hash[key] = key.upcase
end
h['a'] = 'b'
p h['a'] #=> "b"
p h['x'] #=> "X"
p h['y'] #=> "Y"

merging hashes

p ({'a': 'x'}.merge({'b': 'y'})) #=> {:a=>"x", :b=>"y"}

%r (regular expression)

%rを使って正規表現のオブジェクトを作ることができる。
正規表現中に「/」の文字を使いたいときに便利

%r(pattern)
%r<pattern>
%r|pattern|
%r{pattern}

matching of head and end

行頭が「^」、行末が「$」でマッチングする。
例えば、「^ABC」というパターンは「"012\nABC"」という文字列にもマッチする。
文字列の先頭は「\A」、文字列の末尾「\z」

matching with any character

「.」は任意の1文字とマッチ

repeating pattern

・「*」は0回以上の繰り返し
・「+」は1回以上の繰り返し
・「?」は0回または1回の繰り返し
・「{n}」はn回の繰り返し
・「{n, m}」はn〜m回の繰り返し

最短マッチでは
・「*?」は0回以上の繰り返しのうち最短部分
・「+?」は1回以上の繰り返しのうち最短部分

regular expression options

・「i」はアルファベットの大文字・小文字の違いを無視
・「x」は正規表現内の空白と「#」の後ろの文字を無視
・「m」は「.」が改行文字にもマッチ

Regexp.newメソッドでは第2引数にオプション定数を指定できる(複数可)

Regexp.new('Rubyスクリプト', Regexp::EXTENDED)
Regexp.new('Rubyスクリプト', Regexp::IGNORECASE | Regexp::MULTILINE)

p Regexp::EXTENDED #=> 2
p Regexp::IGNORECASE #=> 1
p Regexp::MULTILINE #=> 4

capturing groups in regular expression

$数字の形の変数でマッチした部分の一部を取り出せる。
「(?: )」でキャプチャする必要がないパターンをまとめることもできる。

$数字以外にも

「\$`」でマッチした部分よりも前の文字列、
「\$&」でマッチした部分そのものの文字列、
「\$'」でマッチした部分より後ろの文字列などを取りだすことができる

sub method and gsub method

subメソッドは最初にマッチ部分だけ、
gsubメソッドはマッチする部分すべてを書き換える

scan method

パターンにマッチした部分を取り出す(置き換えない)。
マッチした部分になんらかの処理を行うときに使う

'abracatabra'.scan(/.a/) do |matched|
  p matched
end

上記を実行すると

"ra"
"ca"
"ta"
"ra"

また、正規表現の中で「()」が使われていると、
そこにマッチした部分を配列にして返し、
ブロックの変数を「()」の数だけ並べると、
配列ではなくそれぞれの要素を取り出すことができる。

ブロックがない場合はマッチした文字列の配列を返す

standard input

標準入力はデータを受け取るためのIOオブジェクト。
組み込み定数STDINに割り当てられているほか、
グローバル変数$stdinからも参照されている。
レシーバを指定しないgetsなどのメソッドは$stdinからデータを受け取る。
標準入力の最初はコンソールに関連付けられていて、キーボード入力を受け取る

standard output

標準出力はデータを出力するためのIOオブジェクト。
組み込み定数STDOUTに割り当てられているほか、
グローバル変数$stdoutからも参照されている。
レシーバを指定しないputs,print,printfなどのメソッドは$stdoutへ出力する。
標準出力の最初はコンソールに関連付けられている

standard error

標準エラー出力は警告やエラーを出力するためのIOオブジェクト。
組み込み定数STDERRに割り当てられているほか、
グローバル変数$stderrからも参照されている。
警告メッセージを表示するためにwarnメソッドが$stderrへ出力する。
標準エラー出力も最初はコンソールに関連付けられている

input operation

io.gets(rs)
io.each(rs)
io.each_line(rs)
is.readlines(rs)

IOオブジェクトioからデータを1行読み込む。
行の区切りは引数rsで指定した文字列になるが、
引数が省略された場合は組み込み変数$(default "\n")が行の区切りとなる。

これらのメソッドは行の末尾の改行を含む文字列を返す。
文字列の末尾の改行文字を削除するにはchomp!メソッドが便利

getsメソッドやeach_lineメソッドを使って、行単位で読み込みを行うと、
それまでに何行読み込んだかが自動的に記録される。
その行数はlinenoメソッドで取得できる

binary mode and text mode

テキストモードは改行文字の変換が有効で、
バイナリモードは変換を行わない

zip file reading

zipに圧縮されたデータを読むときはzcatコマンドに展開してもらったデータを受け取ると便利で、
そのコマンドをIO.popenに記述すると
コマンド結果として標準出力に出力したデータをIOオブジェクトから受けることができる

stringio library

StringIOオブジェクトへの出力は実際にはどこへも出力されず、
オブジェクトの中に蓄えられ、あとからreadメソッドなどで読み出せる。

StringIOオブジェクトはすでに文字列として持っているデータを
IOオブジェクトのように見せかけることもできる。
巨大なデータはいったんファイルに保存し、そうでないデータはそのまま別の処理へ渡すという場合、
StringIOオブジェクトを使うと、IOオブジェクトか文字列かによって処理を分けなくて済む。
(例えば、open-uriライブラリでURIを開いたときに返されるオブジェクトは、
IOオブジェクトかStringIOオブジェクトなので、それぞれのオブジェクト毎の記述をしなくて済む)

fileutils library

fileutilsライブラリを読み込むことにより、
FileUtils.cp(ファイルのコピー)やFileUtils.mv(ファイルの移動)など、
ファイルを操作するメソッドを使用することができる。

FileUtils.mvを使うと、File.renameメソッドではできないファイルシステムやドライブをまたがったファイルの移動ができる

Dir.open example

Dir.openやFile.openはブロックを与えることによって、
closeメソッドの呼び出しを省略する。

Dir.open('/usr/local/lib/ruby/2.6.1') do |dir|
  dir.each do |name|
    p name
  end
end

dir.read method

Dir#readメソッドを実行すると、最初に開いたディレクトリに含まれるものの名前を1つ順に返す。
ここで読み出せるものは基本的に次の4種類のいずれか
・カレントディレクトリを表す「.」
・親ディレクトリを表す「..」
・その他のディレクトリ名
・ファイル名

Dir.glob method

Dir.globメソッドを使うと、シェルのように「」と「?」などのパターンを使ってファイル名を取得できる。
Dir.globメソッドはパターンにマッチしたパス名(ファイル名およびディレクトリ名)を配列にして返す。
カレントディレクトリにあるすべての隠しファイル名はDir.glob('.
')で取得できる

File.basename(path[, suffix])

パス名pathのうち、一番後ろの'/'以降の部分を返す。
拡張子suffixが指定された場合は戻り値から拡張子の部分が取り除かれる

File.split(path)

パス名pathをディレクトリ名の部分とファイル名の部分に分解し、
2つの要素からなる配列を返す。多重代入を使って受け取ると便利

dir, base = File.split('/usr/local/bin/ruby')
p dir #=> "/usr/local/bin"
p base #=> "ruby"

File.expand_path(path[, default_dir])

相対パス名pathをディレクトリ名default_dirに基づいて絶対パス名に変換する

find library

findライブラリに含まれるFindモジュールは
指定したディレクトリ以下に存在するディレクトリやファイルを再帰的に処理することができる

require 'find'

IGNORES = [ /^\./, /^\.svn$/, /^\.git$/ ]

def listdir(top)
  Find.find(top) do |path|
    if File.directory?(path)
      dir, base = File.split(path)

      IGNORES.each do |re|
        if re =~ base
          Find.prune
        end
      end
      puts path
    end
  end
end

listdir(ARGV[0])

FileUtils.mkdir_p(path)

階層の深いディレクトリも一度に作成できる。
pathを配列にして複数のディレクトリを作成することも可能

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

ActiveSupport::TimeZoneを0オフセットでインスタンス化すると1時間ずれる

指定したオフセット値(JSTなら9時間)で ActiveSupport::TimeZone を作り、それをもとに ActiveSupport::TimeWithZone インスタンスを作りたい。

jst = ActiveSupport::TimeZone[9]
# => #<ActiveSupport::TimeZone:0x00007fbbe3b034b0 @name="Osaka", @utc_offset=nil, @tzinfo=#<TZInfo::TimezoneProxy: Asia/Tokyo>>

jst.now
# => Fri, 21 Jun 2019 21:24:26 JST +09:00

+9は、問題なし。

utc = ActiveSupport::TimeZone[0]
# => #<ActiveSupport::TimeZone:0x00007fbbe3afb030 @name="Casablanca", @utc_offset=nil, @tzinfo=#<TZInfo::TimezoneProxy: Africa/Casablanca>>

utc.now
# => Fri, 21 Jun 2019 13:25:48 WEST +01:00

utc.tzinfo.current_period.utc_offset                      
# => 0

ファッ!? +00:00 が期待されるところ +01:00 となっている!?
TZInfoのutc_offsetは0を指しているようだが...

そもそもoffsetだけではtimezoneと言えないのでやろうとしていることが強引な気もするが、それはさておき、どうやるのが正解なのだろうか。


関係ありそうだが、詳しくは読んでない。
https://github.com/tzinfo/tzinfo/issues/98


動作確認環境
ruby 2.5.0p0 (2017-12-25 revision 61468) [x86_64-darwin16]
activesupport (5.2.1)
tzinfo (1.2.5)

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

Railsの起動エラーとMySQLのエラー解除方法(個人メモ)

Railsの起動エラーと、MySQLのエラー解除方法を個人的にメモする。
【PC:MacBookPro2012(13-inch, Mid 2012)、MacOS Mojave 10.14.4】
【ruby 2.3.7p456 (2018-03-28 revision 63024) [universal.x86_64-darwin18]】
【Rails 5.2.3】
【mysql Ver 8.0.15 for osx10.14 on x86_64 (Homebrew)】

MySQLのエラーについて。

まずはmysqlの場所を確認。

$ which mysql
/usr/local/bin/mysql

MySQLサーバーを起動させる。

$ mysql.server start
Starting MySQL
...... ERROR! The server quit without updating PID file (/usr/local/var/mysql/*****.local.pid).

PIDファイルを確認する。

$ ls -la /usr/local/var/mysql/*****.local.pid
ls: /usr/local/var/mysql/*****.local.pid: No such file or directory

MySQLサーバーをストップしてみる。

$ mysql.server stop
ERROR! MySQL server PID file could not be found!

sudoを入れて起動してみる。

$ sudo mysql.server start
Password:
Starting MySQL
. ERROR! The server quit without updating PID file (/usr/local/var/mysql/*****.local.pid).

安全にMySQLを起動してみる。

$ mysqld_safe
2019-04-24T03:34:24.6NZ mysqld_safe Logging to '/usr/local/var/mysql/*****.local.err'.
2019-04-24T03:34:24.6NZ mysqld_safe Starting mysqld daemon with databases from /usr/local/var/mysql
2019-04-24T03:34:29.6NZ mysqld_safe mysqld from pid file /usr/local/var/mysql/*****.local.pid ended

とりあえずアンインストールしてみる。

$ brew uninstall mysql
Uninstalling /usr/local/Cellar/mysql/8.0.15... (267 files, 234.6MB)

ちゃんと削除できたか確認。

$ which mysql
(何も出てこない)

再度インストールしてみる。

$ brew install mysql

これでmysql.server startしてもエラーだったので、上の操作を繰り返してみたけどダメ。

「PIDファイルが見つからない」ので、ファイルの生成をしてみる。

$ /usr/local/var/mysql/mysql > touch /usr/local/var/mysql/*****.local.pid
-bash: /usr/local/var/mysql/mysql: is a directory

奇跡的に起動しないか願いを込めて。

$ mysql.server start
Starting MySQL
...... ERROR! The server quit without updating PID file (/usr/local/var/mysql/*****.local.pid).

どこかで見つけたこのコマンドを入力してみる。

$ /usr/local/var/mysql/mysql > sudo mysql.server restart
-bash: /usr/local/var/mysql/mysql: is a directory

同じかい!!(ノ_<)

$ sudo chown -R _mysql:_mysql /usr/local/var/mysql /tmp/mysql.sock
Password:

何やらパスワードを求められた。。。いけるのか?

sudoで起動させてみよう。

$ sudo mysql.server start
Starting MySQL
..... ERROR! The server quit without updating PID file (/usr/local/var/mysql/*****.local.pid).

あかんのかい!(T . T)

sudoなしで起動できるようにするらしいコマンド。

$ sudo chown -R `whoami`:admin /usr/local/var/mysql /tmp/mysql.sock

起動させてみる。

$ mysql.server start
Starting MySQL
.. SUCCESS! 

キターーーーーーーーー!!!!!!♪───O(≧∇≦)O────♪
よーし!早速Railsをローカル環境で表示させてみよう。

Railsのシステムを起動。

$ rails s

ブラウザでlocalhost:3000を入力。(成功なら画面にRailsの初期画面が表示される)

なんとエラー!!恐怖の赤い画面!!
ActiveRecord::NoDatabaseError (Unknown database 'hello_sample7_development'):
え、データベースがない・・・?∑(゚Д゚)

ということでデータベースを作成。

$ rails db:create
Created database 'ファイル名_development'
Created database 'ファイル名_test'

再度Railsのシステム起動。

$ rails s

Railsの初期画面が出た!!!☆:.。. o(≧▽≦)o .。.:

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