20201013のRubyに関する記事は16件です。

【超初心者向け】ハッシュとシンボルの押さえておきたい最低限の知識

配列とハッシュの違い

配列は同じ意味を持つ複数の情報を1つにまとめたもので、ハッシュは関係のある複数の情報を1つにまとめたもの

ハッシュ

変数 = { キー1 => バリュー1, キー2 => バリュー2, キー3 => バリュー3 }

・別々の意味を持つ値を1つにまとめることができるので、複数の関係性のある値を管理するときに用いる
・「データ」とそれに対応する「名前」のセットを要素として持つ
・ハッシュではデータをバリュー、名前をキーという

# ハッシュのキーに文字列を使用した場合
hash1 = { "name" => "nick", "age" => 30, "country" => "UK" }

シンボル

・ハッシュのキーに数値として用いられるもの
・見た目は文字列のように見えるが実際の中身は数値である
・書き方は2種類あるがどちらも実態は同じ
・hash3が一番シンプルでよく使用される

# ハッシュのキーにシンボルを使用した場合
hash2 = { :name => "nick", :age => 30, :country => "UK" }
hash3 = { name: "nick", age: 30, country: "UK" }

⚠️コンピュータは文字列より数値の方が処理速度が早くなるため
シンボルの方がよく使用される


ここまでで超基礎の部分はクリアです!!
シンボルで書かれたハッシュに慣れるのがいいかと思いますが、理解をした上で使用するようにしましょう。
続いてはハッシュの追加や2重ハッシュの値の取得について説明したいと思います!
もう少し知識を吸収したい方はぜひ続きもご覧ください。


ハッシュへ要素を追加

teacher = { name: "nick"}
teacher[:age] = 30 # ハッシュ[追加するキー] = 値

puts teacher

# 出力結果
# {:name=>"nick", :age=>30}

ハッシュの値を変更

teacher = { name: "nick", age: 30}
teacher[:name] = "john" # ハッシュ[変更したい値のキー] = 値

puts teacher

# 出力結果
# {:name=>"john", :age=>30}

ハッシュの値を取得

teacher = { name: "nick", age: 30}
puts teacher[:name] # ハッシュ[取得したい値のキー]

# 出力結果
# nick

2重ハッシュの値を取得

#変数teacher_data→配列の内部に複数の情報をハッシュとして持つ
#teacher_dataからnameデータを取得する

teacher_data = [
  {
    teacher: {
      profile: {
        name: "nick"
      }
    }
  },
  {
    teacher: {
      profile: {
        name: "john"
      }
    }
  },
  {
    teacher: {
      profile: {
        name: "mac"
      }
    }
  }
]

teacher_data.each do |t|
  puts t[:teacher][:profile][:name]
end

# 出力結果
# nick
# john
# mac

【解説】
ブロック引数のtはteacher_dataのtを用いています。
putsのあとはハッシュ[取得したいキー]を取得したいデータ(今回の場合はname)まで連結して記述します。


お疲れ様でした!!
ハッシュとシンボルについての超基礎知識は以上です!
基礎が理解できていないと後々困ると思うので、この機会にぜひ理解が深まればな〜と思います。
わかりにくい点、至らない点ございましたら遠慮なくお知らせください!
それではありがとうございました。

参考

https://qiita.com/ryosuketter/items/257d672eb83210b5f8dc

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

【プログラミング初心者の方へ】メソッドってなに?

はじめに

最近プログラミングを始めた方、特にスクールに行かず「独学でやったるぜ!」と意気込むそこのあなた!

はい。自分もそうでした。そして理由もなく考えもなくRubyに手を出してるところも一緒です。仲間!

でも、、、

わからないことがわからない

ですよね。
コード書け!って言われてもなに書けばいいのかわかんない!まあそれが普通だと思います。

たまに歴半年くらいで開眼してメタプロまで書いちゃうような突然変異種(天才)もいますが気にしない方がよいです。そういう人は大概優しくて教えるのも上手なので色々教えてもらいましょう。

本題

Rubyに限らずですが、プログラミング初心者がよくつまづいたり、理解に苦しむところは

メソッド,引数

じゃないでしょうか。自分はそうでした。

自分で考えたメソッドを書く

これの意味が理解できなかったです。ああ懐かしいもどかしい。

自分の中でしっくりくる捉え方が見つかるまで時間がかかりました。(自分は割とすぐに実務に入ったのでその中で感覚を掴みました。長く独学をするのはあまりおすすめしません。)

メソッドにもインスタンスメソッド,クラスメソッドなるものがいてどう使うのかどこで使うのか全然わからない!って感じでした。

なので自分なりの解釈をここでお伝えしたいと思います!

メソッドの解釈

ターミナルを開いてirbを起動しましょう。以下のように入力してみてください。

irb
hoge = 'hogeはホゲホゲ'

続けて以下のコードを実行します。

irb
hoge.length

9という結果がでたあなたは天才です。素晴らしい。

はい。この時のlengthがメソッドです。

え?そんなの知ってるって?
さすがです。あなたはもう初心者ではないでしょう。

次はこちら

irb
class User
  def name
    'hoge'
  end
end

次に以下のコードを実行します。

irb
user = User.new
user.name

"hoge"という結果がでたあなたは天才です。素晴らしい。
はい。この時のnameがメソッドです。

わかった!と思ったあなたはもう次のステップに進んでいます。この記事を読み続ける必要はないでしょう。

ん?と思ったらもうちょっと読んでみてください。

今実行したのは、
Stringクラスlengthメソッド
Userクラスnameメソッド
の2つです。

lengthメソッドについては下記のURLを参考にどうぞ。
String#length (Ruby 2.7.0 リファレンスマニュアル)

StringクラスlengthメソッドはあらかじめRubyに組み込まれているものです。オブジェクトの文字長を返します。

Userクラスnameメソッドは今回自分で生み出したものということになります。こちらは中身がhogeでもfugaでもいいのです。

これらはどちらもインスタンスメソッドになります。それぞれのクラスのインスタンスに対してくっつけて使います。

HogeクラスのインスタンスはHoge.newで作れますね。

UserとかHogeとかに対して直接くっつけて使うメソッドはクラスメソッドと言います。違いはこれだけです。

自分で考えたメソッドを書く

初めはこれがすごく難しいものであると勘違いしていました。
でも概念がわかればすごく便利に使えます。今回書いたのはnameで中身はhogeだけでしたが、いろんな処理をまとめて書いておくことでそのメソッドを一発叩けば実行することができるようになります。

例えば(今回は引数も使っちゃいましょう)

irb
class Number
  def check(a, b)
    if a == 2
      a * b
    elsif a == b
      a / b
    else
      a + b
    end
  end
end

これでNumberクラスcheckメソッドを実行しましょう。

irb
num = Number.new
num.check(1, 2)
num.check(2, 2)
num.check(3, 3)

引数によって計算式が変わるメソッドですね。簡単なものでもirbを使って色々書いて実行してみるのがいいと思います。引数については今のでしっくりきたでしょうか。

クラスメソッドはこの最たるもので、バッチ処理をする時には大活躍します。まさに一撃!という感じです。

バッチ処理ってなんやねん!と思った方はこちら

要は溜めてまとまった処理をシステムの裏側で実行する仕組みです。

最後に

ここまで読んで、わかんない!という方。本当にすみません。それはこの記事の分かりにくさです。もっと分かりやすく書けるように勉強します。

ちなみに今この瞬間にメソッドについてよくわかってなくても、irbやコンソールでさっき出てきたlengthが使えれば問題ないです。そのうち分かります。あなたが分かった時や詳しくなった時には、その時に初心者の方に優しく教えてあげましょう。

最後まで読んでいただきありがとうございました。

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

Rubyで三角形を出力

本日授業で初めてRubyとやらを触ったので記念に記録。
○の出力のみで以下のような三角形をつくるという問題。
             ○
         ○○○
      ○○○○○
   ○○○○○○○

n = gets.to_i #入力

for x in 1..n
    (n-x).times{print(" ")} # 空白の数はn-1した数から1段につき1つずつ減っていく
    for y in 1..2*(x-1)+1 # ○の数は2*(x-1)+1
        print("○")
    end
    puts
end

はじめに、丸の左側にできている空白を出力し、それに続いて丸を出力する処理をします。そして、丸の出力が終わったらputsで改行をするという流れでプログラムを書きました。
左側にできる空白の数は一段目はn-1であり、それ以降-1されていきます。
○の数は、2(x-1)+1で求められます。
以上をプログラムに落とし込むと、無事、出力ができました。

やはり言語が違えば仕様も違うので戸惑いますが早く慣れていきたいです

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

link_toメソッドのpathの引数、要る・要らない

はじめに

アプリケーションを作るときに、いつもlink_toメソッドでエラーが出ていた。しかも、そのリンク先に飛ぶときではなく、link_toメソッドがあるページを表示させたときに、
ActionController::UrlGenerationError
このエラーが出ていて、悩まされた。いつも○○_pathと、ルーティングを確認して記述するところまでは問題がなく、躓かない。ここに( )で引数を渡すとなると、何を渡したらいいのか分からなくなっていた。

引数がなくてもOKなパターン

 アプリケーションを利用する誰もが同じページに飛ぶ場合は引数がなくてもよい。ログインページやroot_pathのページがこれにあたる。

やっぱりrails routesは便利だった

ターミナルでカレントディレクトリが作成中のアプリケーションのディレクトリで

% rails routes

を入力すると、ルーティングを教えてくれる。
pathの記述はもちろん、Prefixの部分を見る。しかし、
ここで着目すべきは、URI Patternの部分。ここで/:idなど、idがURIに含まれている場合は、引数を渡す必要がある。
:idとidがURIに含まれている場合は、引数に(user.id)などとすればよい。
また、引数によっては、.idを省略できる場合もある。

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

【Rails】検索ボックスの作成

手順

ルーティングの設定
コントローラーの設定
viewファイル:searchの作成

ルーティングの設定

profilesのルーティングにsearchを追加。

rutes.rb
resources :profiles do
    get :search, on: :collection
  end
search_profiles GET    /profiles/search(.:format)     profiles#search

コントローラーの設定

今回は:nameについて部分一致するケースを想定。

controller.rb
def search
    if params[:name].present?
      # 部分一致
      @profiles = Profile.where('name LIKE ?', "%#{params[:name]}%")
    else
      @profiles = Profile.none
    end
  end

viewの作成

search.html.erb
<h1>検索ボックス</h1>
<%= form_with url: search_profiles_path, method: :get, local: true do |f| %>
<%= f.text_field :name %>
<%= f.submit :search %>
<% end %>

<h1>検索結果</h1>
<% @profiles.each do |profile| %>
 <ul>
  <li><%= profile.name %><span><%= link_to "詳細", profile_path(profile) %></span></li>
 </ul>
<% end %>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

中高生6人でサービスを作ってプログラミングスクールのコンテストで1位を取った話

今回は、中学生1人、高校生5人でサービスのアイディアから実装まで行い、Life is Tech!という中高生向けのプログラミングスクールのコンテストでWEB部門1位になって、思ったこと、感じたことをお話ししたいと思います。

0. メンバー

メンバーは中学3年生〜高校3年生までの中高生で、中学生が1人の高校生が5人のチームで開発を行いました。
女の子が3人で男の子が3人で、学生のエンジニアのチームにしては女の子の比率が高いチームですが、普段からみんなすごく仲が良く、学校の放課後や休日に遊びに行ったり、ご飯食べたりする仲です。
(チームのお気に入りは釣りですが、どっかのウイルスのせいで最近は遊びに行けてません...ぴえん...)

1. どのようなサービスを作ったのか。

今回作成したサービスはAssemble Tempoという開発者達とチーム戦で戦う、新しい形の開発時間記録サービスです。
assembletempo_toppage.png
※開発段階の画面です。

『モチベーションを継続させるサービスを作る』という目標を設定して、どのようにすればモチベーションを継続させることができるか?とチームで考えた際に、ゲーム形式のサービスにしようという話になり、考えつきました。

次にどのようなゲームにするか、チーム内で話し合ったのですが、開発時間をチーム戦で競えばモチベーションを継続できるのでは、という話になり、チーム戦で開発時間を競うサービス作ることにしました。

2. 使用したプログラミング言語とサービス

プログラミング言語

プログラミング言語: Ruby, Javascript
フレームワーク: Sinatra
開発環境用DB: sqlite3
本番環境用DB: PostgreSQL

これらを採用した理由は、Life is Tech!のWebサービスコースで採用されているため、メンバーの大半が使用したことがあったり、教科書などがあるため、わからなくても教育が簡単だと判断したためです。

サービス

チーム開発を行う上で様々なサービスを使用しました。
まず、チームのコミュニケーションとして、Google HangoutsとSlackを使用しました。
Slackでは、コードに関する質問から新しいアイディア、完成した部分のフィードバックなどを行いました。

Google Hangoutsは某ウイルスの感染防止対策としてオンライン会議をする際に使いました。
Screen Shot 2020-10-07 at 0.58.54.png

コードの共有や管理にはGithubをプロジェクトのタスク管理にはTrelloを使用しました。

Trelloはメンバー同士で話し合って、どのように進めていくかを話し合い、ラベルを使って、優先度や難しさなどを表示しました。
Screen Shot 2020-10-07 at 1.03.34.png

3. 実装について。

実装は、フロントエンドとバックエンドのチームに別れて実装を行いました。

フロントエンド

まず初めにどのような画面が必要かをメンバーで話し合い、仮のフレームを作成しました。
ios__________.jpg
※グループディスカッションを行った際の写真です。

次に、ある程度必要な画面などが決まってきたらXDを使用して、フレームを作成しました。
Screen Shot 2020-10-07 at 14.03.25.png
※レスポンシブ版です。

バックエンド

バックエンドもフロント同様に、どのようなシステムを使うか、どのような実装を行うか、メンバーで話し合い実装しました。

バックエンドの主な機能

ユーザ認証・メール確認機能
グラフの計算・グラフのリアルタイム更新(Server Send events)
グループ分け機能
アイコン機能
タイマー機能

が主な機能です。

その中でも、実装に時間がかかった、Server Send eventsとタイマー機能に関して説明をしたいと思います。

Server Send events

Server Send eventsはhttpを使用して、リアルタイム通信を行う技術のことで、サーバからクライアントへのPushとして使えます。
WebSocketとの違いは、主にクライアントからサーバへの送信ができないことです。(代わりにAjaxを使用しています)

Server Send eventsはSinatraでのライブラリがなく、一から実装をしないといけないため、時間がかかりました。

ruby
set :server, 'thin'
$connections = []

get '/send_message' do
    message = "メッセージです。"
    $connections.each do |sse|
        sse << "retry: 500\n" +
                    "event: event\n" +
                    "data: #{message}\n\n" unless sse.closed?
    end
end

get '/sse', provides: 'text/event-stream' do
    stream :keep_open do |sse|
        $connections << sse
        graph_data.callback {
            $connections.delete(sse)
        }
    end
end

※Qiita用に変更しています

タイマー機能

タイマー機能は、タイマー用のテーブルを用意します。
新規作成は、テーブルにユーザのデータが無い場合はタイマーを作成します。
テーブルに、ユーザのデータがある場合は、タイマーを終了するようにしました。

ruby
# データベースに既に、データがあるか確認
if time = Timers.find_by(ユーザのID: session[:ユーザのセッション], 始まりの時間: now_time.all_day, 終わりの時間: nil)
    # 開発終了した時間と開発時間をDBに保存
    Timers.update(終わりの時間: now_time)
else
    # タイマーを新規作成
    Timers.create(ユーザのID: session[:ユーザのセッション], 始まりの時間: now_time)
end

※Qiita用に変更しています

4. チーム開発の感想

今回初めて、バックエンドの開発をするメンバーがいたり、メンバーのほとんどがGitを使ったことが無かった中で、リリースをできたことは、すごく嬉しかったです。

学んだこと。

一番学べたと思ったところは、機能を部分部分に分けてメンバーにお願いするところです。(PM)
メンバーによって、書ける言語や使える技術などが違うためそれを把握して、メンバーにお願いするところがすごく勉強になりました。
使ったことないような技術でも、これならできるかな?など考えながら、メンバーに機能の実装をお願いしました!!

他にも、メンバーのわからないところなどを教えることによって、アウトプットがたくさんできて、僕も知らなかったことなどを学ぶことができました。

大変だったこと。

大変だったことは、たくさんあります。
まず、チーム開発で一番重要である、コードの管理・共有のGitを使えるメンバーが僕しかいなく、Gitを教えるのが大変でした。

次に、学生特有の問題なのですが、みんな学校が違うため、予定が合わずディスカッションなどみんなで話し合う機会が少なく、大変でした。
他にも、テスト期間などになるとみんな返信がなくなり、ちゃんとリリースできるか心配でした。

メンバーに対して思ったこと。

チーム開発がするのが初めてだったと思うので、最初の方はGitがわからなかったり、大変だったと思います。
でも、みんなちゃんとついてくれてすごくいい作品ができたなと思いました。

最初の方は、「メンバーに迷惑かけちゃうかも」など言ってたメンバーも最後は「リリースできてよかった!」や「楽しかった!!」など言っていたので、僕は嬉しかったです。

プログラミングの技術の方も、上がっていて「Ruby全然わかんない!!」と言ってたメンバーがRubyを書けるようになったり、DB(ActiveRecord)を使えるようになったメンバーなどがいました!!
メンバーも技術があがったと実感しているみたいで、それを実感できるほど成長できたのはすごいと思いました。

5. 最後に

僕もメンバーも初めてが多かった今回のチーム開発ですが、コンテストには間に合い、1位を取れて嬉しかったです。
今回のチーム開発でPMや開発環境、サポート、実装面で反省することはたくさんありましたが、今後の開発などに生かしていけるといいなと思いました。

今後、どのような機能を実装するかなどの話し合いをしており、アップデートをしていく予定なのでサービスを使って、開発のモチベーションなどを高めてもらえると嬉しいです!!

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

[Rails]deviseの導入

はじめに

deviseとは、ユーザー管理機能を簡単に実装するためのgemです。

目次

1.deviseのインストール
2.deviseの設定ファイルを作成
3.モデルの作成
4.テーブルの作成
5.ビューの編集
6.コントローラーの編集

1. deviseのインストール

gemfileに以下を記述します。

gemfile
gem 'devise'

アプリのディレクトリで以下を実行します。

ターミナル
bundle install

gemをインストールした後はサーバーを再起動します。
サーバーを再起動することでgemが反映されます。

ターミナル
rails s

2. deviseの設定ファイルを作成

deviseを使用するためには、gemのインストールに加え、 devise専用のコマンドで設定ファイルを作成する必要があります。
以下を実行することで、追加したdeviseというgemの「設定関連に使用するファイル」を自動で生成することができます。

ターミナル
rails g devise:install

3. モデルの作成

deviseを利用する際には、アカウントを作成するためのUserモデルを新しく作成する必要があります。
作成には通常のモデルの作成方法ではなく、deviseのモデル作成用コマンドでUserモデルを作成します。

ターミナル
rails g devise user

また、rails g deviseコマンドによりモデルやマイグレーションを自動生成するだけでなく、routes.rbにルーティングも自動的に追加されます。

4. テーブルの作成

必要なカラムを追加する場合は、マイグレーションファイルに記述し以下を実行します。

ターミナル
rails db:migrate

5. ビューの編集

カラムを追加した場合は、追加したカラムを入力できるように、新規登録画面のビューを編集する必要があります。
デフォルトでは、deviseのビューファイルは隠れているので、以下を実行します。

ターミナル
rails g devise:views

6. コントローラーの編集

また、コントローラーを編集したい場合は以下を実行することでdevise管理下のコントローラーを作成することができます。

ターミナル
rails g devise:controllers users
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

筆者がエラーに苦しんだ備忘録【bundler: failed to load command: unicorn NameError: uninitialized constant Model】

初めに

この問題は解決済みです。
多分特例すぎて誰の参考にもならないです。
質問フォーマットに記入してたら、エラーの原因に気づいて解決しました。
筆者の備忘録として残してあります。

感想

エラー文を全部読みましょう。
でも、bundler: failed to load command: unicornって言われたら、unicorn疑っちゃう。。。
一行下のNameError: uninitialized constant Modelが超大事でした。
なんでローカル環境で起動しちゃうんですか〜
せめてエラー検知してくれ〜〜

前提・実現したいこと

Railsで開発したアプリをCapistranoを使用してデプロイしようとしましたが、
unicorn:startコマンドで必ず失敗します。
エラーを解決して、正しくunicornが起動できるようなりデプロイすることがゴールです。

発生している問題・エラーメッセージ

以前、capistranoでデプロイできていたのですが、1週間ほど空いた後にデプロイしたら、今回のエラーが発生しました。
デプロイしてない間に一部ソースコードは編集しました。

capistranoのエラー文

00:52 unicorn:start
      01 $HOME/.rbenv/bin/rbenv exec bundle exec unicorn -c /var/www/MyNote/current/config/unicorn.rb -E deployment -D 
      01 master failed to start, check stderr log for details
#<Thread:0x00007fbbfb37a690@/Users/ohishikaido/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/sshkit-1.21.0/lib/sshkit/runners/parallel.rb:10 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
        1: from /Users/ohishikaido/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/sshkit-1.21.0/lib/sshkit/runners/parallel.rb:11:in `block (2 levels) in execute'
/Users/ohishikaido/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/sshkit-1.21.0/lib/sshkit/runners/parallel.rb:15:in `rescue in block (2 levels) in execute': Exception while executing as ec2-user@54.150.220.39: bundle exit status: 1 (SSHKit::Runner::ExecuteError)
bundle stdout: Nothing written
bundle stderr: master failed to start, check stderr log for details
(Backtrace restricted to imported tasks)
cap aborted!
SSHKit::Runner::ExecuteError: Exception while executing as ec2-user@54.150.220.39: bundle exit status: 1
bundle stdout: Nothing written
bundle stderr: master failed to start, check stderr log for details


Caused by:
SSHKit::Command::Failed: bundle exit status: 1
bundle stdout: Nothing written
bundle stderr: master failed to start, check stderr log for details

Tasks: TOP => unicorn:start
(See full trace by running task with --trace)
The deploy has failed with an error: Exception while executing as ec2-user@54.150.220.39: bundle exit status: 1
bundle stdout: Nothing written
bundle stderr: master failed to start, check stderr log for details


** DEPLOY FAILED
** Refer to log/capistrano.log for details. Here are the last 20 lines:

〜省略〜

 DEBUG [2632c234] Finished in 0.563 seconds with exit status 0 (successful).

 DEBUG [b61a7332] Running [ -e /var/www/MyNote/shared/tmp/pids/unicorn.pid ] && kill -0 `cat /var/www/MyNote/shared/tmp/pids/unicorn.pid` as ec2-user@54.150.220.39

 DEBUG [b61a7332] Command: [ -e /var/www/MyNote/shared/tmp/pids/unicorn.pid ] && kill -0 `cat /var/www/MyNote/shared/tmp/pids/unicorn.pid`

 DEBUG [b61a7332]       bash: 0 行: kill: (21662) - そのようなプロセスはありません

 DEBUG [b61a7332] Finished in 0.565 seconds with exit status 1 (failed).

  INFO [d82915ac] Running $HOME/.rbenv/bin/rbenv exec bundle exec unicorn -c /var/www/MyNote/current/config/unicorn.rb -E deployment -D  as ec2-user@54.150.220.39

 DEBUG [d82915ac] Command: cd /var/www/MyNote/current && ( export RBENV_ROOT="$HOME/.rbenv" RBENV_VERSION="2.5.1" RAILS_ENV="production" ; $HOME/.rbenv/bin/rbenv exec bundle exec unicorn -c /var/www/MyNote/current/config/unicorn.rb -E deployment -D  )

 DEBUG [d82915ac]       master failed to start, check stderr log for details

本番環境(EC2内) /var/www/アプリ名/shared/log/unicorn.stderr.logに表示される内容

I, [2020-10-13T08:34:14.025904 #12943]  INFO -- : Refreshing Gem list
bundler: failed to load command: unicorn (/var/www/MyNote/shared/bundle/ruby/2.5.0/bin/unicorn)
NameError: uninitialized constant Model
  /var/www/MyNote/shared/bundle/ruby/2.5.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/active_support.rb:80:in `block in load_missing_constant'
  /var/www/MyNote/shared/bundle/ruby/2.5.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/active_support.rb:9:in `without_bootsnap_cache'
  /var/www/MyNote/shared/bundle/ruby/2.5.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/active_support.rb:80:in `rescue in load_missing_constant'
  /var/www/MyNote/shared/bundle/ruby/2.5.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/active_support.rb:59:in `load_missing_constant'
  /var/www/MyNote/releases/20201013083325/app/controllers/users/registrations_controller.rb:3:in `<top (required)>'
〜省略〜

解決した経緯

unicorn.stderr.logの確認。
コピペして貼り付けた時に原因に気づきました。
めっちゃくちゃしれーっと書いてありますね!!!!!

/var/www/MyNote/releases/20201013083325/app/controllers/users/registrations_controller.rb:3:in `<top (required)>'

おまえかーい!!!
僕の4時間と心の焦りを返してください。泣

原因のソースコード

users/registrations_controller.rb
class Model::RegistrationsController < Devise::RegistrationsController
end

そりゃ確かにNameError: uninitialized constant Modelになりますわ。。
Modelクラスなんて存在しませんからねー

補足情報(FW/ツールのバージョンなど)

Ruby2.5.1
Rails5.2.4.2
unicorn 5.4.1
AWS EC2 Amazon linux AMI

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

Rails で LTI 認証を実装する

LTI 認証とは

LTI認証という規格があります。
LTIとはLearning Tools Interoperability という単語の頭文字で、要するに学習支援ツール間で認証情報をやり取りする際の規格という意味です。

認証技術の中でも分野が特定されているかなりニッチな技術なので、知らない方も多いんではないでしょうか。僕も教育工学を専門にする前はLTIという単語すら知りませんでした。

以下にIMS GLOBAL から公式に出されているLTI認証の説明図をのっけて置きます。

個人的には、結局OAuthの一種と理解しているのですが、我々が開発するアプリケーションを Learning Tools, 認証の基盤となる大本のツールを Learning Platform として、Learning Tools は Learning Platform の拡張機能を提供する代わりに、認証情報を Learning Platform からもらいます。これによってユーザーは様々なアプリをユーザーを切り替えることなしに、使い分けることができるという利点があるわけです。このとき、Learning Toolsのほうを LTI Tool Provider, Learning Platformのほうを LTI Consumer と呼んだりもします。

ただ、問題はこれをどう実装するかです。実際、ニッチな技術ゆえにドキュメンテーションに乏しく、ネット上を探しても実装例はあまり公開されていません。そこで今回、この記事では Ruby on Rails を使った LTI 認証の実際について紹介したいと思います。

実装例

ライブラリのインストール

まずは、必要なライブラリをインストールします。

$ bundle add devise
$ bundle add ims-lti
$ bundle add oauth

Rails を使うメリットは lti 認証のためのライブラリがあるところです。今回は ims-lti を使って実装します。

認証キーの登録

config配下にlti_settings.yml ファイルを作成します。
内容は以下の内容を記載してください(コンシューマーキー、LTIシークレットは適宜安全な文字列を使用してください)。

config/lti_settings.yml
production:
  __consumer_key__: '__lti_secret__'

development:
  __consumer_key__: '__lti_secret__'

作成したら、これを読み込むための設定をconfig/application.rbに追加します。

config/application.rb
config.lti_settings = Rails.application.config_for(:lti_settings)

コントローラーの作成

ltis_controllerを作って以下の内容を記載しましょう

require 'oauth/request_proxy/action_controller_request'

class LtisController < ApplicationController
  skip_before_action :verify_authenticity_token, only: :launch

  def lti_launch
    # 受け取った consumer key が config/lti_settings.yml の中にあるかどうかを確認
    if not Rails.configuration.lti_settings[params[:oauth_consumer_key]]
      render :launch_error, status: 401
      return
    end 
    shared_secret = Rails.configuration.lti_settings[params[:oauth_consumer_key]]
    authenticator = IMS::LTI::Services::MessageAuthenticator.new(request.url, request.request_parameters, shared_secret)

    #Check if the signature is valid
    if not authenticator.valid_signature?
      render :launch_error, status: 401
      return
    end
    #check if the message is too old
    if DateTime.strptime(request.request_parameters['oauth_timestamp'],'%s') < 5.minutes.ago
      render :launch_error, status: 401
      return
    end

    # LTI 情報をsessionに保存
    session_data = {
      "fullname" => authenticator&.message&.lis_person_name_full,
      "email" => authenticator&.message&.lis_person_contact_email_primary,
      "user_id" => authenticator&.message&.user_id,
      "context_id" => authenticator&.message&.context_id,
      "context_title" => authenticator&.message&.context_title,
      "tool_consumer_instance_name" => authenticator&.message&.tool_consumer_instance_name
    }
    print(session_data)
    session['lti-authenticator'] = session_data
    sign_in_and_redirect(User.first)
  end
end

MessageAuthenticator にわたってきたリクエストと事前にこちらで保持しているキーの情報を渡すことで簡単に認証ができます。認証が成功したら、devise ライブラリを使ってsign_inすることで、アプリ側での認証を実装しています。

最後にlti_launchを起動するためのエンドポイントを定義しておきましょう。

config/routes.rb
  match 'lti/launch' => 'ltis#lti_launch', via: [:get, :post], as: :lti_launch

Learning Platform 側での設定

ここまでできたら、あとは Learning Platform 側で事前に lti_settings.yml に記載した認証情報を書いて指定したエンドポイントに遷移するように設定すれば完成です。このやり方は個々のLearning Platformによって異なるので割愛します。

(補足)LTI 遷移時に Rails で cross origin error が出たら

config/application.rb に以下の内容を追記してください

config/application.rb
    config.lti_settings = Rails.application.config_for(:lti_settings)
    config.action_dispatch.default_headers['Referrer-Policy'] = 'unsafe-url'
    config.action_controller.forgery_protection_origin_check = false
    config.action_controller.allow_forgery_protection = false

参考文献

Rails LTI Tool Provider
IMS LTI

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

デプロイ後に背景画像が適用されない場合の対処法

この記事を書いた背景

デプロイしたら背景画像が表示されない!
調べたら結構そういう人いたため。

実施環境

macOS Catalina 10.15.7
VS Code 1.50.0
Ruby 2.6.5
Rails 6.0.0
サーバー:AWS EC2
WEBサーバー:Nginx
アプリケーションサーバー:unicorn 5.4.1

対処法

僕はこれでいけました。
デプロイ後、本番に上がったアプリ等で背景画像が表示されます。

①背景画像は『/app/assets/images』フォルダに格納する

②背景画像を指定するCSSの種類を『SCSS』にする

③背景画像を指定するCSSの記述『background-image: image-url("画像名");』

scss
/*例*/
body { 
  background-image: image-url("wallpaper.jpg");
}

※この記述だと逆にローカル環境で背景画像が表示されない...笑

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

アプリをもっと良くしよう

コメント機能の追加

Commentのscaffoldをする

Commentの著者名、コメント本文、Ideaテーブルへの関係(reference)をscaffoldします。
rails generate scaffold comment user_name:string body:text idea:reference

マイグレーションをします。
rails db:migrate

モデルに関係(relation)を追加する

Ideaとcommentオブジェクト間の接続をRailsに認識させます。

app/models/idea.rb
class Idea < ApplicationRecord
has_many :comments
app/models/comment.rb
class Comment < ApplicationRecord
belongs_to :idea

コメントフォームの表示と編集

app/views/ideas/show.html.erb
<p>
  <strong>Picture:</strong>
  <%= image_tag(@idea.picture_url, width: 600) if @idea.picture.present? %>
</p>

<h3>Comments</h3>
<% @comments.each do |comment| %>
  <div>
    <strong><%= comment.user_name %></strong>
    <br />
    <p><%= comment.body %></p>
    <p><%= link_to 'Delete', comment_path(comment), method: :delete, data: { confirm: '削除してもよろしいですか?' } %></p>
  </div>
<% end %>
<%= render 'comments/form', comment: @comment %>
app/controllers/ideas_controller.rb
def show
  @comments = @idea.comments.all
  @comment = @idea.comments.build
end
app/views/comments/_form.html.erb
<div class="field">
  <%= form.label :body %>
  <%= form.text_area :body %>
</div>

<%= form.hidden_field :idea_id %>

最後に次の行を削除します。

app/views/comments/_form.html.erb
<div class="field">
  <%= form.label :idea_id %>
  <%= form.number_field :idea_id %>
</div>

HTML&CSSを使ってデザインしましょう

アプリケーションのレイアウトを適用する

app/assets/stylesheets/application.css
body { padding-top: 100px; }

上の行を以下のように書き換えます。

app/assets/stylesheets/application.css
body { padding-top: 60px; }

最後にapp/assets/stylesheets/scaffolds.scssを削除します。

ナビゲーションを良くしよう

"New Idea"ボタンをナビゲーションに常に表示します。

app/views/layouts/application.html.erb
<li class="active"><a href="/ideas">Ideas</a></li>
<li><%= link_to 'New Idea', new_idea_path %></li>

アイデアリストのデザイン

以下のように書き換えます。

app/views/ideas/index.html.erb
<h1>Listing ideas</h1>

<% @ideas.in_groups_of(3) do |group| %>
  <div class="row">
    <% group.compact.each do |idea| %>
      <div class="col-md-4">
        <%= image_tag idea.picture_url, width: '100%' if idea.picture.present? %>
        <h4><%= link_to idea.name, idea %></h4>
        <%= idea.description %>
      </div>
    <% end %>
  </div>
<% end %>

<h2>Ideaの詳細ページをデザイン</h2>

以下のように書き換えます。

```app/views/ideas/show.html.erb
<p id="notice"><%= notice %></p>

<div class="row">
  <div class="col-md-9">
    <%= image_tag(@idea.picture_url, width: '100%') if @idea.picture.present? %>
  </div>

  <div class="col-md-3">
    <p><b>Name: </b><%= @idea.name %></p>
    <p><b>Description: </b><%= @idea.description %></p>
    <p>
      <%= link_to 'Edit', edit_idea_path(@idea) %> |
      <%= link_to 'Destroy', @idea, data: { confirm: 'Are you sure?' }, method: :delete %> |
      <%= link_to 'Back', ideas_path %>
    </p>
  </div>
</div>



```

Carrierrwaveを使ったサムネイルの表示

ImageMagickのインストール

brew install imagemagickを実行します、

gem 'carrierwave'

の下に、

gem 'mini_magick'

を追加します。その後、以下のコマンドを実行します。
bundle

画像をアップロードしたときにサムネイルを作成する

app/uploaders/picture_uploader.rb
 # include CarrierWave::MiniMagick

version :thumb do
  process :resize_to_fill => [50, 50]
end

上の#を削除します。

サムネイルの作成

app/views/ideas/index.html.erb
<%= image_tag idea.picture_url, width: '100%' if idea.picture.present? %>

を以下のように変更します。

app/views/ideas/index.html.erb
<%= image_tag idea.picture_url(:thumb) if idea.picture.present? %>

Deciceで認証機能を追加

devise gemを追加

gem 'devise'

を追加します。次に以下のコマンドをターミナルで実行します。
bundle

アプリにdeviseをセットアップ

以下のコマンドを実行します。

rails generate devise:install

Deviceの環境設定

environmentファイルにデフォルトのurlオプションを追加します。

config/environments/development.rb
config.action_mailer.default_url_options = { host: 'localhost:3000' }

endの前に追加します。

app/views/layouts/application.html.erb
<% if notice %>
  <p class="alert alert-success"><%= notice %></p>
<% end %>
<% if alert %>
  <p class="alert alert-danger"><%= alert %></p>
<% end %>
<%= yield %>

さらに上の行を追加します。

また、以下を削除します。

app/views/ideas/show.html.erb
<p id="notice"><%= notice %></p>

同じようにapp/views/comments/show.html.erbでも削除します。
なぜなら、app/views/layouts/application.html.erbに同じ行を追加したためです。

Userモデルのセットアップ

User modelを作るためにbundled generator script を使います。

rails generate devise User
rails db:migrate

サインアップとログインリンクの追加

ユーザーがログインできる適切なリンク、または案内をナビゲーションバー右上のコーナーに追加する。

app/views/layouts/application.html.erb
<p class="navbar-text pull-right">
  <% if user_signed_in? %>
    Logged in as <strong><%= current_user.email %></strong>.
    <%= link_to 'Edit profile', edit_user_registration_path, class: 'navbar-link' %> |
    <%= link_to "Logout", destroy_user_session_path, method: :delete, class: 'navbar-link'  %>
  <% else %>
    <%= link_to "Sign up", new_user_registration_path, class: 'navbar-link'  %> |
    <%= link_to "Login", new_user_session_path, class: 'navbar-link'  %>
  <% end %>
</p>
<ul class="nav navbar-nav">
  <li class="active"><a href="/ideas">Ideas</a></li>
</ul>

最後にログインしていない時に登録した内容を確認できないようにする。

app/controllers/application_controller.rb
  before_action :authenticate_user!

endの前に追加します。

Gravatarでプロフィール写真を追加

Gravtastic gemを追加

gem 'gravtastic'

これをdeviseの下に追加します。

Terminalで、次のコマンドを実行します。

bundle

Gravatarをセットアップ

最後の行の下に次を追加します。

app/models/user.rb
include Gravtastic
gravtastic

Gravatarを設定する。

app/views/layouts/application.html.erb
<% if user_signed_in? %>

のなかに,以下のようになるように書き換えます。

app/views/layouts/application.html.erb
<% else %>
<%= image_tag current_user.gravatar_url %>

さらにデザインしよう

headerのデザイン

app/assets/stylesheets/application.css
nav.navbar {
  min-height: 38px;
  background-color: #f55e55;
  background-image: none;
}

.navbar a.brand { font-size: 18px; }
.navbar a.brand:hover {
  color: #fff;
  background-color: transparent;
  text-decoration: none;
}

tableのデザイン

以下のように書き換えます。

app/views/ideas/index.html.erb
<table class="table">

以下のコードを使って、画像のサイズを調整します。

<%= image_tag(idea.picture_url, width: 600) if idea.picture.present? %>

app/assets/stylesheets/ideas.scssの最後に以下を追加します。

app/assets/stylesheets/ideas.scss
.container a:hover {
  color: #f55e55;
  text-decoration: none;
  background-color: rgba(255, 255, 255, 0);
}

footerにスタイルを追加

app/assets/stylesheets/application.css
footer {
  background-color: #ebebeb;
  padding: 30px 0;
}

buttonにスタイルを追加

app/assets/stylesheets/ideas.scs
.container input[type="submit"] {
   height: 30px;
   font-size: 13px;
   background-color: #f55e55;
   border: none;
   color: #fff;
 }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsでコントローラーの単体テストコード時に「302」のエラーが出た時の対処法

はじめに

前回の記事の続きってわけでも無いのですが、似たような内容にはなっています。
それと「302」のエラーの中のあくまでも1例ですのでよろしく御願い致します。

エラー内容

それではまずはコントローラーの単体テストコード実装時に下記のようなエラーが出ました。
ご覧ください

ターミナル
 % bundle exec rspec spec/requests/orders_spec.rb

OrdersController
  GET /index
    indexアクションにリクエストすると正常にレスポンスが返ってくる (FAILED - 1)
    indexアクションにリクエストするとレスポンスに出品済みの商品の説明文が存在する (FAILED - 2)


Failures:

  1) OrdersController GET /index indexアクションにリクエストすると正常にレスポンスが返ってくる
     Failure/Error: expect(response.status).to eq 200

       expected: 200
            got: 302

       (compared using ==)
     # ./spec/requests/orders_spec.rb:24:in `block (3 levels) in <top (required)>'

  2) OrdersController GET /index indexアクションにリクエストするとレスポンスに出品済みの商品の説明文が存在する
     Failure/Error: expect(response.body).to include @item.details
       expected "<html><body>You are being <a href=\"http://www.example.com/users/sign_in\">redirected</a>.</body></html>" to include "商品の説明"
     # ./spec/requests/orders_spec.rb:29:in `block (3 levels) in <top (required)>'

〜中略〜

エラー内容の検証

それではエラー内容を検証していきたいと思います。
今回のエラーの原因となる該当箇所は2つあります。
まず1つ目は下記の部分です。

ターミナル
  1)   Failure/Error: expect(response.status).to eq 200

       expected: 200
            got: 302

次に2つ目は下記の部分です。

ターミナル
  2)   Failure/Error: expect(response.body).to include @item.details

        expected "<html><body>You are being <a href=\"http://www.example.com/users/sign_in\">redirected</a>.</body></html>" to include "商品の説明"

エラー内容の検証

それではエラー内容の検証をしていきます。

  • まず1つ目のエラーはexpectedで「200」(成功)を期待していますが実際に返ってきたレスポンスは「302」(Found)となっています。 ここで「302」(Found)についての詳しい説明を載せておきます。

302 (Found) エラーとは

「The HyperText Transfer Protocol (HTTP) の 302 Found リダイレクトステータスレスポンスコードは、リクエストされたリソースが一時的に Location で示された URL へ移動したことを示します。ブラウザーはこのページにリダイレクトしますが、検索エンジンはリソースへのリンクを更新しません 。」

上記を簡単に説明しますと「本来遷移したいページではなく違うページにリダイレクトされましたよ」という内容です。

(引用させて頂いた参考サイト)
HTTP レスポンスステータスコードについての参考サイト


  • それでは次に2つ目のエラーですが自分が着目したポイントは下記の部分です。
<a href=\"http://www.example.com/users/sign_in\">redirected</a>.

まず「users/sign_in」とあるのでログイン画面が関わっていると推測出来ます。そしてその次に「redirected」とあるのでここでもリダイレクトされたということが推測出来ます。

以上の検証内容から推測すると「リダイレクトでログイン画面に飛ばされたのかな?」と仮定することが出来ます。

ではそのような処理をしている箇所はと言うと「authenticate_user!」という部分が該当すると分かりました。ここで少し「authenticate_user!」について説明します。

authenticate_user!とは

処理が実行された時にユーザーがログインしていなければ、そのユーザーをログイン画面に遷移させます。という処理をしてくれるdevise用のメソッドです。

以上の点から自分はテストコード実行時は該当の「authenticate_user!」の箇所をコメントアウトすることにしました。

orders_controller.rb
class OrdersController < ApplicationController
  before_action :authenticate_user! (この行をコメントアウトする)

再度単体テストコードを実行

ターミナル
% bundle exec rspec spec/requests/orders_spec.rb

OrdersController
  GET /index
    indexアクションにリクエストすると正常にレスポンスが返ってくる
    indexアクションにリクエストするとレスポンスに出品済みの商品の説明文が存在する
    indexアクションにリクエストするとレスポンスに出品済みの商品の画像が存在する
    indexアクションにリクエストするとレスポンスに出品済みの商品の販売価格が存在する
    indexアクションにリクエストするとレスポンスに購入内容の確認の文言が存在する

Finished in 5.79 seconds (files took 10.02 seconds to load)
5 examples, 0 failures

上記の通り無事にテストコードを実行することが出来ました。

おわりに

今回のエラー(302)はコントローラーのテストコード実行時に「authenticate_user!」でログイン画面にリダイレクトされていたために起きたエラーでした。結合テストコードだとログインする処理は簡単なのですがコントローラーの単体テストコードの際にログイン出来るのか少し調べた限りではよくわかりませんでしたので今回はコメントアウトをすることで対処しました。

当記事内で参考にさせて頂いたサイト

HTTP レスポンスステータスコードについての参考サイト

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

【Ruby】ハッシュの取り出しについて

●●●問題●●●

puts hash.keys
puts hash.values

上記のメソッドを実行した時に

one
two
three
1
2
3

とターミナルに表示されるような変数hashを作成するためのコードをシンボルを使って記述してください。

●●●回答●●●

hash{one:1,two:2,three:3}

●●●解説●●●

まずはハッシュの構造から。
シンボルをつけるハッシュの記述方法は、キーの後ろにコロンをつけて定義する。

{キー:値}

また、ハッシュオブジェクトに対して、「オブジェクト.keys」とすることで、ハッシュのキーを取得。
「オブジェクト.values」とすることで、ハッシュの値を取得することができる。

(例)
hash{one:1} に対して

hash.keys ⇨ one
hash.values ⇨ 1

よって、上記の回答となる。

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

【Rails】Gem gretelを用いたパンくずリスト作成

そもそもパンくずリストとは

パンくずリストとは下記の写真のようにページを階層順に配列し、ユーザーが操作時に現在どの位置にいるかを可視化したリストです。このようなリストを位置型パンくずリストと呼びます。

スクリーンショット 2020-10-12 17.09.55.png

他にも属性型パンくずリストといいページを階層的に示したものでなく、そのページがどの種類のカテゴリに属しているかを示すリストもあります。属性型パンくずリストの例としてはECサイトが挙げられます。

パンくずリストを導入することによって主に下記の3点のメリットがあります。
・操作性の向上
→ユーザーがサイト内での位置情報を常に確認できる。

滞在時間の向上
→パンくずリストにより階層毎の移動が容易になり回遊性が高まる。

SEO効果
→パンくずリストによりクローラーの巡回をサポートする。

gretelのインストール

【ドキュメント】
https://www.rubydoc.info/gems/gretel

【GitHub】
https://github.com/lassebunk/gretel

Gemfileにて

gem 'gretel'

記載後は下記を実行

$ bundle install

これでインストールは完了です。

設定

下記のコマンドでパンくずリストを設定するためのファイルを作成します。

$ rails generate gretel:install

するとこのようなファイルが作成されます。

config/breadcrumbs.rb
crumb :root do
  link "Home", root_path
end

# crumb :projects do
#   link "Projects", projects_path
# end

# crumb :project do |project|
#   link project.name, project_path(project)
#   parent :projects
# end

# crumb :project_issues do |project|
#   link "Issues", project_issues_path(project)
#   parent :project, project
# end

# crumb :issue do |issue|
#   link issue.title, issue_path(issue)
#   parent :project_issues, issue.project
# end

# If you want to split your breadcrumbs configuration over multiple files, you
# can create a folder named `config/breadcrumbs` and put your configuration
# files there. All *.rb files (e.g. `frontend.rb` or `products.rb`) in that
# folder are loaded and reloaded automatically when you change them, just like
# this file (`config/breadcrumbs.rb`).

今回ブログアプリの管理画面にパンくずリストを実装するので下記のように記載します。

config/breadcrumbs.rb
#管理画面
crumb :root do
  link "Home", admin_dashboard_path
end

#記事(一覧)
crumb :admin_articles do
  link "記事", admin_articles_path
end

コードの説明

config/breadcrumbs.rb
crumb :(設定ファイル) do
  link "(パンくずリストに表示される名前)", (呼び出し元のパス)
end

ビューの設定

ここでbreadcrumbs.rbに記載したcrumb :admin_articles doが繋がります。

admin/articles/index.html.slim
- breadcrumb :admin_articles

下記を記載した箇所にパンくずリストが表示されます。
layouts/admin.html.slim
== breadcrumbs

親の設定

最後に下記のようにさらに階層を加えた表示方法を記載します。

スクリーンショット 2020-10-12 14.48.17.png

config/breadcrumb.rb
crumb :root do
  link "Home", admin_dashboard_path
end

crumb :admin_articles do
  link "記事", admin_articles_path
end

crumb :edit_admin_article do
  link "記事編集", admin_articles_path
  parent :admin_articles
end

先ほど「記事」のパンくず追加に加えて今回は「記事編集」というパンくずを追加しました。
前回と異なる点は記事と記事編集を紐付けるために記事編集の欄にparent :admin_articlesを記載しております。

最後にadmin/articles/edit.html.slimでもビューの設定を終えればパンくずの実装が可能になります。

参照したページの一覧

【ドキュメント】
https://www.rubydoc.info/gems/gretel

【Flexible Ruby on Rails breadcrumbs plugin.(GitHub)】
https://github.com/lassebunk/gretel

【gretelでパンくずリストを作成】
https://doruby.jp/users/kisuzuki/entries/gretel%E3%81%A7%E3%83%91%E3%83%B3%E3%81%8F%E3%81%9A%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E4%BD%9C%E6%88%90

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

【Rails】 あいまい検索機能の付け方

経緯

現在絶賛作成中のポートフォリオで、フリマアプリのラクマの仕上げに検索機能をの実装したいと思い作成。さほど難しくはなかったけれども、色々記載しておこうと思います。

環境

ruby 2.6.5
Rails 6.0.3.2
haml使用

イメージ図

ファイル名

ファイル名

手順

1、ルーティングの設定
2、検索フォームを作成
3、モデルに定義
4、コントローラーに設定
5、ビューの作成

の5つの手順になります。
簡単なので、サックと出来ると思います。

ルーティングの設定

今回は、7つの基本アクション以外のアクションを定義します。
7つの基本アクションとは、index new create 等のアクションの事ですね。
先ずは、コードから

routes.rb
resources :items do
  collection do
    get 'search'
  end
end

説明ですが、
今回は先程言った通り7つの基本アクション以外で定義します。要は新しいアクションを作るみたいなイメージです。
その為のコードが、collection ~ end の所です。
get の横のsearch が新しいアクション名です。

そして、collection ですが、ここはURLの指定先にidの必要の可否で変わります。
idの可とは、showアクションのような個々に登録されている商品の詳細ページなどに見にいく時にふられるものです(まぁ、ふられると言うと言葉のニュアンス的に違うのですが・・・)
なので、idなしで有ればcollection
idありで有ればmember となります。

今回は、特定のページにいく必要がないため、collectionを使用してルーティングを設定してます。



検索フォームを作成

先ずは、コードから

index.html.haml
= form_with(url: search_items_path, local: true, method: :get, class: "search-form") do |f|
  = f.text_field :keyword, placeholder: "検索する", class: "search-input"
  %label{for: "search-icon", class: "search-label"}
    %i.fas.fa-search
  .search-icon
    = f.submit "検索", class: "search-btn", id: "search-icon"

必要そうな所だけ説明します。

先ずは、urlの設定部分ですが、先程設定したsearchのルーティングで設定されるplefixを書きます。plefixをパスとして書く時は文字の最後に_pathをつけるような書き方をします。
調べ方は、ご存知だと思いますが、
ターミナルで rails routes です。

次に、まぁこれは無理に行う必要はないですが、上記の画像のように、ムシメガネ?みたいなアイコンとsubmit(送信ボタン)の紐付けについてです。
つまり、ムシメガネ?みたいなアイコンを押したら送信ボタンが押されるように設定していると言う事です。

やり方は、label:for と id で紐付けます。
上記のコードで言うと、
これと %label{for: "search-icon", class: "search-label"}
これで = f.submit "検索", class: "search-btn", id: "search-icon" です。
search-icon って言う同じ名前がありますよね。これで紐付けされています。

そして、今回は検索フォームにf.text_field :keywordと記述したので、このフォームに入力した値はコントローラーでparams[:keyword]と書くことで取得する事ができます。



モデルに定義

ここも、コードから

item.rb
def self.search(search)
  return Item.all unless search
  Item.where('name LIKE(?)', "%#{search}%")
end

言い忘れましたが、今回はitemって言う名前でモデル等を作ってます。

説明します。

検索したキーワードが含まれている投稿を取得するために、whereメソッドLIKE句を利用します。
whereメソッドは、モデル名.where('検索対象となるカラムを含む条件式')とする事でテーブル内の「条件に一致したレコードのインスタンス」を配列の形で取得できます。

LIKE句は、曖昧(あいまい)な文字列の検索をするときに使用するもので、whereメソッドと一緒に使います。
詳細は、省きますが今回は、searchが含まれるnameカラムのデータを検索するものです。
詳しく知りたい方は、下記のリンク先が分かりやすいと思います。
【SQL】LIKE句の基本的な使い方~複数検索する場合の方法まで解説

そもそも、searchって何?っと思われた方の向けに説明します。
要は引数の話です。
引数のsearch(1行目の(search))は、検索フォームから送信されたパラメーターが入ります。イメージしやすく言うと、検索フォームに みかん と入力して送信ボタンを押すとこの引数に みかん が入ると言う事ですね。
後は、文法の話です。
この引数にみかんというデータが入れば定義の中のsearchもみかんになるって訳です。つまり、このまま、みかんネタで言えば、itemテーブルのnameカラムにみかんと言う文字が入っているものを検索するって言う事です。
なので、言ってしまえば、1行目の(search)はどんな単語でも良いって事ですね。

次に、return Item.all unless search ですが、これは読んで字のごとくですが、
search(さっきので言えばみかん)と言う文字がなかったら他全てを出力すると言う意味です。

そして、最後に1行目のself.searchの説明です。
現時点だと、.searchというメソッドは使うことができません(この後コントローラーで定義するのに使います)
が、モデルで設定することにより、コントローラーで使用することができるようになります。そして、モデルの中でメソッドを定義する際には、メソッド名の頭にself.を付けると事で、コントローラーで使えるクラスメソッドになります。

以上で、モデルに定義は終了です。



コントローラーに設定

はたまた、ここもコードから

item_controller.rb
def search
  @items = Item.search(params[:keyword])
end

@items = Item.search(params[:keyword])のコードは、
上記で述べてきた、今迄の説明で事足りるかと思うので省略します。



ビューの作成

検索結果画面のビューの作成ですが、
ここは、search.html.hamlと言うファイルを作成して、
eachメソッドを使って出力すればオッケーかと思います。

search.html.haml
.contents__box
  - @items.each do |item|
  ここより下は今自分が作っているものに照らし合わせて作成お願いします。

これで、全ての実装が完了です!
実装出来なかったなどの不備が有ればご連絡ください!

ありがとうございました。

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

RailsでCSVインポートする時、nkfコマンドを使ったらめっちゃ楽だった

RailsでCSVファイルをインポートするとき、こんな感じに実装してた。

def read_csv
  begin
    rows = CSV.read(file, encoding: 'UTF-8')
  rescue CSV::MalformedCSVError
    begin
      # csvファイルがUTF-8でない場合はShift_JISで読む
      rows = CSV.read(file, encoding: 'Shift_JIS')
    rescue CSV::MalformedCSVError, Encoding::InvalidByteSequenceError, Encoding::UndefinedConversionError
        # csvファイルがUTF-8・Shift_JISでもない場合
        @error = 'ファイルのエンコードはUTF-8かShift_JISではありません。'
    end
  end
  rows&.shift # ヘッダーを削除する
  rows
end

問題点

例外処理が多くて面倒だし、そもそもインポートできる文字コード2つしかないとか…

解決策

どうやらnkfとかいうモジュールを使うとなんでも変換できそうだ…
https://docs.ruby-lang.org/ja/latest/class/NKF.html

  • nkfをPCにインストール
brew install nkf
nkf -w --overwrite 変換したいファイルのpath

このコマンドを使えばいい感じに変換できそう。

  • Railsの中にこのコマンドを組み込んでみる。
def read_csv(file)
  system("nkf -w --overwrite #{file.path}")
  rows = CSV.read(file)
  rows&.shift # 1行目のヘッダーを削除する
  rows
end

すっきりとしたコードになりました。

注意点

あらかじめbrew install nkfコマンド等でnkfをインストールしておかないと、nkfコマンドが使えないので気をつけてください。

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