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

クラスと使う場合と使わない場合の比較

クラスを使う場合と使わない場合の比較

 今回は、「クラスを使うプログラミングと、使わないプログラミングの違い」についてまとめます。

 例えば、ユーザを表すデータをプログラム上で処理したいとします。ユーザはデータとして氏名(first_nameとlast_name)と、年齢(age)を持ちます。ハッシュと配列を使うなら、次のように処理することができます。

# ユーザのデータを作成する
users = []
users << { first_name:'Alice', last_name:'Ruby', age:20 }
users << { first_name:'Bob', last_name:'Python', age:30 }

# ユーザのデータを表示する
users.each do [user]
  puts "氏名:#{user[:first_name]}#{user[:last_name]}、年齢:#{user[:age]}"
end

# => 氏名:Alice Ruby、年齢:20
# => 氏名:Bob Python、年齢:30

 氏名についてはメソッドを作っておくと、他にも氏名を使う場合が出てきた時にそのメソッドを再利用することができます。

# ユーザのデータを作成
users = []
users << { first_name:'Alice', last_name:'Ruby', age:20 }
users << { first_name:'Bob', last_name:'Python', age:30 }

# 氏名を作成するメソッド
def full_name(user)
  "#{user[:first_name]}#{user:[last_name]}"
end

# ユーザのデータを表示
users.each do |user|
  puts "氏名:#{full_name(user)}、年齢:#{user[:age]}"
end

# => 氏名:Alice Ruby、年齢:20
# => 氏名:Bob Python、年齢:30

 ですが、ハッシュを使うとキーをタイプミスした場合にnilが返ってきてしまします。間違ったキーを指定してもエラーにならないでの、ボーとしているとこの不具合に気づかない可能性があります。

users[0][:first_name]
# => 'Alice'

users[0][:first_mame]
# => 'nil'

 他にも、ハッシュは新しくキーを追加したり、内容を変更したりできるので「もろくて壊れやすいプログラム」になりがちです。

# 勝手に新しいキーを追加
users[0][:country] = 'japan'

# 勝手にfirst_nameを変更
users[0][:first_name] = 'Carol'

# ハッシュの中身が変更される
users[0]
# => { :first_name => "Carol", :last_name => "Ruby", :age => 20, :country => japan }

 ここで示したような小さなプログラムではハッシュのままでも問題ないかもしれないですが、大きなプログラムになってくると、とてもハッシュでは管理しきれなくなってきます。そこで登場するのがクラスです。こういう場合はUserクラスという新しいデータ型を作り、そこにデータを入れたほうがより堅牢なプログラムになります。

 構文の意味はのちほど詳しく説明するので、ここではUserクラスを導入した場合のコードの変化に着目します。

class User
  attr_reader :first_name, last_name, :age

  def initialize(first_name, last_name, age)
    @first_name = first_name
    @last_name = last_name
    @age = age
  end
end

# ユーザのデータを作成する
users = []
users << User.new('Alice', 'Ruby', 20)
users << User.new('Bob', 'Python', 30)

# 氏名を作成するメソッド
def full_name(user)
  "#{user.first_name}#{user.last_name}"
end

# ユーザのデータを表示する
users.each do |user|
  puts "氏名:#{full_name(user)}、年齢:#{user.age}"
end

# => 氏名:Alice Ruby、年齢:20
# => 氏名:Bob Python、年齢:30

 Userクラスを導入すると、タイプミスをしたときにエラーが発生します。

users[0].first_name
# => 'Alice'

users[0].first_mame
# => NoMethodError: undefined method 'first_name' for #<~>

 新しい属性(データ項目)を追加したり、内容を変更したりすることも防止できます。

users[0].country = 'japan'
# => NoMethodError: undefined method 'country=' for # <User:~>

users[0].first_name = 'Carol'
# => NoMethodError: undefined method 'first_name=' for # <User:~>

 また、クラスの内部にメソッドを追加することもできます。例えば、先ほど作成したfull_nameメソッドはUserクラスの内部で定義した方がシンプルになります。

class User
  attr_reader :first_name, :last_name, :age

  def initialize(first_name, last_name, age)
    @first_name = first_name
    @last_name = last_name
    @age = age
  end

  def full_name
    "#{@first_name}#{@last_name}"
  end
end

# ユーザのデータを作成する
users = []
users << User.new('Alice', 'Ruby', 20)
users << User.new('Bob', 'Python', 30)

# ユーザのデータを表示する
users.each do |user|
  puts "氏名:#{user.full_name}、年齢:#{user.age}"
end

# => 氏名:Alice Ruby、年齢:20
# => 氏名:Bob Python、年齢:30

 クラスはこのように、内部にデータを保持しさらに自分が保持しているデータを利用する独自のメソッドを持つことができます。データとそのデータに関するメソッドが常にセットになるので、クラスを使わない場合に比べてデータとメソッドの整理がしやすくなります。このサンプルプログラムのような小さなプログラムではそこまでメリットが見えないかもしれないが、プログラムが大規模になればなるほど、データとメソッドを一緒に持ち運べるクラスのメリットが大きくなってきます。

オブジェクト指向(おまけ)

 いかに効率よく開発を行うかを突き詰めた考え方のこと。
 オブジェクト指向には大きく分けて4つ重要な考え方があります。

①設計
②カプセル化
③継承
④ポリモーフィズム

①設計

 物と物の関係性とかを考えて作っていく、このプロセスのこと。
 どれだけ効率よく無駄なく簡単に出来るか設計していくか考えるのですが、この設計をしていく中で重要な概念が「カプセル化」「継承」「ポリモーフィズム」です。

ポイント

・物の振る舞いや定義が明確になっているか
・利用する人がわかりやすいような形になっているか
・利用者が増えても拡張性の高い物となっているか
・違う物との関係性でバランスが崩れないか

②カプセル化

 他のプログラムから干渉されないように作る考え方

③継承

 同じようなプログラムは共通化して使う考え方

④ポリモーフィズム(多態性)

 汎用的な形にできるようにする考え方

まとめ

 「カプセル化」「継承」「ポリモーフィズム」の考え方をベースに設計していき、実際にコードを進めていく、コードの開発をしていくというのがオブジェクト指向という考え方になります。

 

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

Ruby3.0.0からwebrickが削除されてSinatraの環境構築で困惑した件

Onoda Kenta(@onoda_kenta)と申します。プログラミング初学者です。

Rubyの学習の一環として簡単なwebアプリを作成するためにまずはRailsではなくSinatraを利用することにしました。

そのための環境構築にDockerを使用したのですが、RubyのイメージをRuby:latestとしたためversionがRuby3.0.0になってしまい導入に際して少し戸惑ったので共有したいと思います。

結論から述べると

  • Ruby3.0.0からwebrickが削除された
  • 実行にはthin,puma,reel,HTTP,webrick等のアプリケーションサーバーが必要
  • webrickを利用したい場合はRubyGemsからインストールする
  • もしくはRuby2.x系を利用する

それでは実際に環境構築をしてみたいと思います。

環境

前提として今回利用した環境は以下の通りです。

  • Windows 10 Pro 20H2 64bit
  • WSL 2 Ubuntu 20.04 LTS
  • Docker version 20.10.2
  • Ruby 3.0.0
  • Sinatra 2.1.0
  • webrick 1.7.0

Docker + Ruby3.0.0でSinatraの環境構築

Sinatra: READMEに記載されている通りの手順をDockerを利用して環境構築をしていきたいと思います。

Ruby3.0.0のイメージからコンテナの立ち上げ

まずはRubyのコンテナを立ち上げます。

$ docker container run -it -d -p 4567:4567 --name sinatra_test ruby:latest

-it -dのオプションはRubyのコンテナをバックグラウンドで実行したままにするためにつけています。

Sinatraでは通信にデフォルトでポート番号4567を使用するため-pでホストのポート番号4567にコンテナのポート番号4567を割り当てます。

それと今回は--nameでコンテナにsinatra_testという名前をつけました。

Unable to find image 'ruby:latest' locally

~中略~

d4f1f93f0c61eb98d1007f2012e3d4d5225e9d3996e809c609be1ab6e1586169

実行中のコンテナを確認してみます。

$ docker container ls
CONTAINER ID   IMAGE         COMMAND   CREATED         STATUS         PORTS                    NAMES
d4f1f93f0c61   ruby:latest   "irb"     6 minutes ago   Up 6 minutes   0.0.0.0:4567->4567/tcp   sinatra_test

ちゃんとRubyのコンテナが立ち上がっています。

rbファイルの実行

次にSinatra用のrbファイルを実行するために準備をします。

$ mkdir sinatra_test
$ cd sinatra_test
~/sinatra_test$ touch myapp.rb

まずは適当に作業用のディレクトリを作成します。今回はsinatra_testとしました。作成したsinatra_testディレクトリに移動しmyapp.rbというファイルを作成します。

myapp.rb
require 'sinatra'

get '/' do
  'Hello world!'
end

myapp.rbは上記のように記述します。

myapp.rbをコンテナ内にコピーします。

~/sinatra_test$ docker container cp myapp.rb sinatra_test:/

ここからはコンテナ内で作業をしていきます。

$ docker container exec -it sinatra_test bash
root@d4f1f93f0c61:/#

コンテナ内に入れました。まずはコピーしたmyapp.rbが存在するか見てみます。

root@d4f1f93f0c61:/# ls
bin  boot  dev  etc  home  lib  lib64  media  mnt  myapp.rb  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

次にrubyのversionを確認してみましょう。

root@d4f1f93f0c61:/# ruby -v
ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-linux]

Sinatraをインストールします。

root@d4f1f93f0c61:/# gem install sinatra
Fetching sinatra-2.1.0.gem

~中略~

Successfully installed sinatra-2.1.0
6 gems installed

これで本来ならmyapp.rbを実行すればWebアプリが立ち上がるはずです。-oでhostをlocalhostにセットします。実行してみましょう。

root@d4f1f93f0c61:/# ruby myapp.rb -o 0.0.0.0
/usr/local/bundle/gems/sinatra-2.1.0/lib/sinatra/base.rb:1755:in `detect_rack_handler': Server handler (thin,puma,reel,HTTP,webrick) not found. (RuntimeError)
        from /usr/local/bundle/gems/sinatra-2.1.0/lib/sinatra/base.rb:1493:in `run!'
        from /usr/local/bundle/gems/sinatra-2.1.0/lib/sinatra/main.rb:45:in `block in <module:Sinatra>'

エラーが出てしまい起動しません。Server handler (thin,puma,reel,HTTP,webrick) not found.と書いてあります。

Ruby 3.0.0 リリースに以下のような記述があります。

その他の注目すべき2.7からの変更点

  • 以下のライブラリは標準添付ライブラリから削除されました。3.0以降で使いたい場合はrubygemsから利用してください。
    • sdbm
    • webrick
    • net-telnet
    • xmlrpc

Ruby3.0.0になりRuby2.7まで標準で添付されていたwebrickというアプリケーションサーバーのライブラリが削除されてしまったため、サーバーが起動しなかったようです。

webrickのインストール

webrickが削除されrubygemsから利用してくださいとのことなので、改めてwebrickをインストールしてみます。

root@d4f1f93f0c61:/# gem install webrick
Fetching webrick-1.7.0.gem
Successfully installed webrick-1.7.0
1 gem installed

webrickがインストールされたのでもう一度myapp.rbを実行してみましょう。

root@d4f1f93f0c61:/# ruby myapp.rb -o 0.0.0.0
[2021-01-28 14:04:00] INFO  WEBrick 1.7.0
[2021-01-28 14:04:00] INFO  ruby 3.0.0 (2020-12-25) [x86_64-linux]
== Sinatra (v2.1.0) has taken the stage on 4567 for development with backup from WEBrick
[2021-01-28 14:04:00] INFO  WEBrick::HTTPServer#start: pid=36 port=4567

無事に起動しました。この状態でhttp://localhost:4567/ にアクセスすればHello world!が表示されます。

Ruby2.7.2の場合

ちなみにRuby2.x系はwebrickが標準で添付されているため、Sinatraをインストールした後myapp.rbを実行すればサーバーが起動します。実際にやってみます。

$ docker container run -it -d -p 4567:4567 --name sinatra_test ruby:2.7.2

~中略~

root@e66f1ebb8a85:/# ruby -v
ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-linux]
root@e66f1ebb8a85:/# gem install sinatra

~中略~

Successfully installed sinatra-2.1.0
6 gems installed
root@e66f1ebb8a85:/# ruby myapp.rb -o 0.0.0.0
[2021-01-28 14:16:35] INFO  WEBrick 1.6.0
[2021-01-28 14:16:35] INFO  ruby 2.7.2 (2020-10-01) [x86_64-linux]
== Sinatra (v2.1.0) has taken the stage on 4567 for development with backup from WEBrick
[2021-01-28 14:16:35] INFO  WEBrick::HTTPServer#start: pid=24 port=4567

コンテナのイメージをruby:2.7.2とした以外は先程と同じ手順でやりました。Sinatraをインストールした後に$ gem install webrickをしなくてもmyapp.rbを実行すればちゃんとサーバーが起動しています。

まとめ

今回はDockerでSinatraの環境構築をする際、ruby:latestにしてしまったことが原因で途中でエラーが発生してしまいました。Sinatraの環境構築をする場合冒頭にも書きましたが以下の点を意識するといいと思います。

  • Ruby3.0.0からwebrickが削除された
  • 実行にはthin,puma,reel,HTTP,webrick等のアプリケーションサーバーが必要
  • webrickを利用したい場合はRubyGemsからインストールする
  • もしくはRuby2.x系を利用する

また今回の件に限らずネットの情報をもとに環境構築をする際はなるべくversionを揃えたほうがいいことがわかりました。

この記事が少しでも皆さんのお役に立てれば幸いです。

参考サイト

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

Fakerについて

目次

①Fakcerとは
②Fakerの使い方

①Fakcerとは

ダミーデータを自動で作成してくれるもの。

②Fakerの使い方

1.Gemfileにfakerを記述

Gemfile
 gem 'faker'
ターミナル
 bundle install

2.seeds.rbにダミーデータを作成できるように記述する(こちらを参考に)

seeds.rb
 50.times do |n|
  email = Faker::Internet.email
  password = "password"
  User.create!(name: name,email: email, password: password,password_confirmation: password)
end

3.ターミナルにてデータを生成する

ターミナル
 rails db:seed

以上

◯補足
seeds.rbとは、初期データを生成してくれるファイルのこと。このファイルにデータを生成するコードを書いておくだけで、アプリにデータを備えさせることができる。

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

RailsでjQueryを利用する方法 (Webpacker使用)

環境

Rails 6.0.3
Ruby 2.6.6

導入

WebpackのProvidePluginを利用することでreqireを明示することなくどこでもjQueryを使用することが可能になります。

jQueryをインストール

$ yarn add jquery

plugin用のファイルの作成

config/webpack/plugins/provide.js
const webpack = require('webpack')
module.exports = new webpack.ProvidePlugin({
    $: 'jquery',
    jquery: 'jquery'
});

webpackの設定リストに先ほどのプラグインを追加する

特定の環境でしか使用しないのであればenviroment.jsではなくtest.js,development.jsなどに記述する。

config/webpack/environment.js
const { environment } = require('@rails/webpacker')
// 編集開始
const provide = require('./plugins/provide')
environment.plugins.prepend('provide', provide)
// ここまで編集
module.exports = environment

これでrequireを記述せずともjsファイル内でjQueryが使用可能となります。
試しに、

app/javascript/packs/hello.js
$(function() {
  console.log("hello");
});
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    .
    .
    .
    <%= javascript_pack_tag 'hello', 'data-turbolinks-track': 'reload' %>
    .
    .
  </head>
</html>

これでjQueryが使用できていることがわかります。

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

N+1問題とその解決方法

目次

①N+1問題とは
②具体例
③解決方法

①N+1問題とは

データベースからデータを取り出す際に、必要以上にSQLが発行され動作が悪くなってしまう問題のことをいう。

②具体例

例えば、Userがcommentを投稿するwebアプリがあるとする。ここで、複数のUserが投稿したcommentを全権取得する場合、ユーザーのnickenameを表示させることをしたいケースを考える。

class CommentsController < ApplicationController
  def index 
   @comments = Comment.all
  end
end
#コメントを全件取得している
<% @comments.each do |comment| %>
 <%= comment %>
<small><%= comment.user.nickname %></small>
<%end%>
#一つ一つのコメントに対してのユーザーのニックネームを表示している

上記の流れを言語化すると
まず、コメントを全件取得している(DBに入っているデータを2件とする。)→1回
そして、それに紐ずくuserを1人取得する→2回
最後に、紐づいているuserを1人取得する。→3回

commentsテーブルに対して1回、userの人数分取得(今回は2回)なので計3回アクセスしていることになる。
これがN+1問題。もしこれが1000件になったら、その分DBにアクセスしなければならなくなる。

③解決方法

結論としては、icludesメソッドを使う。
icludesメソッドにより、commentsに紐づいているuserを一気に取得できる。
仮にcommentが3つ取得できて、user_idがid=1,id=4,id=8とわかっている場合、usersテーブルから一気に持ってくることでこの問題は解決する。

class CommentsController < ApplicationController
  def index 
   @comments = Comment.includes(:user)
  end
#コメントを全件取得し,それに紐づくuserをDBから一気に取得している
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

mergeメソッドについて

目次

①mergeメソッドとは
②どんな時にしようするのか
③まとめ

①mergeメソッドとは

ハッシュを結合させる時に使うRubyのメソッド

②どんな時にしようするのか

結論は、ハッシュを結合させる時。
例えば、form_withでユーザーが投稿した内容はハッシュの中に、キーと一緒に入っているが、ユーザーが記入しない内容も保存したい時に使う。ユーザーのidやその商品のidについては、ユーザーが直接入力することはないが、パラメーターに含めて、DBの保存したい。そのような時に、mergeメソッドを使って、パラメーターに含めたいキーと値を記述する。下記の例では、user_idをcrrent_user.idから、item_idをURLに含めたparamsから取ってきて、パラメーターに含めている。

 def image_params
    params.require(:image).permit(:memo,:text).merge(user_id:current_user.id,item_id:params[:id])
 end

③まとめ

requireで指定したモデルに対して送られるパラメーター以外に、他のカラムと該当するデータを持ってきたい時に、mergeメソッドをつかう。

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

f.collection_selectにclassを設定する

やりたいこと

collection_selectのクラスにform-controlを指定して見た目を整えたい。

失敗例

f.collection_select :thank_ids, @thank, :id, :human, prompt: "選択して下さい", class: "form-control"

オプションの後にクラスを記述しても何故か適用されない。

成功例

f.collection_select :thank_ids, @thank, :id, :human, {prompt: "選択して下さい"}, {class: "form-control"} 

オプションとクラスを{}で囲むことで適用された。クラスだけでなくオプションも{}で囲まないと適用されなかった。
ちなみにオプションを指定しない場合でも下記の通りクラスはオプションの後に書く必要がある。

f.collection_select :thank_ids, @thank, :id, :human, {}, {class: "form-control"} 
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

cannot load such file -- nokogiri/nokogiri (LoadError) のエラーを解決するまで。

rails チュートリアルで学習中にrails sを実行したときに起きるエラーです。
一度解決したのですが、新しくプロジェクトを作ると同じエラー発生。一度目はひたすら記事調べていろいろした結果うまくいっただけなので、何が要因かわからずだった。なので、次すぐ解決できよう記事に志。

require': cannot load such file -- nokogiri/nokogiri (LoadError)

200行以上のエラー文の最後に、上の文章が出現。nokogiri。nokogiriをloadできないらしい。
よくわからんけど、gemfileにnokogiriを読み取れるよう設定。

gem 'nokogiri'

で、

bundle install

Traceback (most recent call last):
2: from /Users/irieryoutaira/.rbenv/versions/2.6.3/bin/bundle:23:in <main>'
1: from /Users/irieryoutaira/.rbenv/versions/2.6.3/lib/ruby/2.6.0/rubygems.rb:302:in
activate_bin_path'
/Users/irieryoutaira/.rbenv/versions/2.6.3/lib/ruby/2.6.0/rubygems.rb:283:in find_spec_for_exe': Could not find 'bundler' (2.2.6) required by your /Users/irieryoutaira/ruby/environment/toy_app/Gemfile.lock. (Gem::GemNotFoundException)
To update to the latest version installed on your system, run
bundle update --bundler.
To install the missing version, run
gem install bundler:2.2.6`

エラー。bundle 2.2.6のverをインストールするのに失敗。?はて
updateしなよって書いてある気がするから、update。

同じエラー。。
よし、ぐぐろう。

ググると、rvenvのrubyが使われてない説浮上。

which ruby 
/usr/bin/ruby

本当だ。。

rvenvのrubyが使えるようにする。
全環境で、rvenv使われていいので、

rbenv global 2.6.3

そうすると、

which ruby
/Users/irieryoutaira/.rbenv/shims/ruby

いいじゃないですか✨

と思って、
bundle update
変わらず、エラー。むむむっ。
再度、ぐぐる。
bundlerをインストールするしい。

gem install bundler

さささ、bundle install実行! そろそろ成功していいんやで。
失敗。でも、

try passing them all to `bundle update`

updateしてくださいらしい。します。

You have requested:
  listen = 3.1.5

The bundle currently has listen locked at 3.4.1.
Try running `bundle update listen`

If you are updating multiple gems in your Gemfile at once,
try passing them all to `bundle update`

listenがダメ??
謎謎の謎ちゃんだけど、指示どおり

bundle update listen

したら、

You have requested:
  spring = 2.1.0

The bundle currently has spring locked at 2.1.1.
Try running `bundle update spring`

If you are updating multiple gems in your Gemfile at once,
try passing them all to `bundle update`

次は、springがダメ。
いいでしょう。springもアップデートしますよ。

bundle update spring

すると

You have requested:
  listen = 3.1.5

The bundle currently has listen locked at 3.4.1.
Try running `bundle update listen`

If you are updating multiple gems in your Gemfile at once,
try passing them all to `bundle update`

デジャブ( ゚д゚)
springアップデートする前と、同じエラー。
再度、bundle update listenすると、
springのエラー。
この二つの堂々巡りがおきました。

ぐぐります。
Gemfileを
Gemfile.lockの内容より古いバージョンを指定してインストールしようとすると発生するエラーらしい。

なるほどなるほど。Gemfile.lockは消しちゃいます。bundle installで成功したら、また自動で作られるらしいから大丈V

すると、、

An error occurred while installing pg (1.1.4), and Bundler cannot
continue.
Make sure that `gem install pg -v '1.1.4' --source 'https://rubygems.org/'`
succeeds before bundling.

postgressSQL(綴りは違う気もする。)が見つかりません。だそうです。そりゃそうだよ。mysqlしか、使ったことないんだもん。なんで、前回はできんとん。。
なので、postgressSQLをインストール

brew install postgresql

そうして、

bundle install

成功!

よし!と言うことで、そろそろ、rails sしてみます。

 Webpacker configuration file not found /Users/irieryoutaira/ruby/environment/toy_app/config/webpacker.yml. Please run rails webpacker:install Error: No such file or directory @ rb_sysopen - /Users/irieryoutaira/ruby/environment/toy_app/config/webpacker.yml (RuntimeError)

。。。頭はげるわ。Webpacker configuration fileなる物が見つからないそう。
このwebpackerというのは、javaScriptで使うオープンソースのJavaScriptモジュールバンドラーらしい。具体的には、Webアプリケーションを構成するリソース(jsファイル、cssファイル、画像ファイル等々)を一つにまとめてくれるツールらしい。

Webpackerをインストールします。

rails webpacker:install 

でけたでけた

さあ、rails sしてみるぜ。

。。。成功!うわあ、感動した!たくさん知識を貸してくれた記事の著者さんたちありがとう!

参考にした記事は以下にあげときます。
https://techacademy.jp/magazine/19895

https://www.google.com/search?q=To+install+the+missing+version%2C+run+%60gem+install+bundler%3A2.2.6

https://qiita.com/white_aspara25/items/d5e19b82be17048d9215

https://qiita.com/_kanacan_/items/c1499f6c13b1c41da982

https://qiita.com/hirokik-0076/items/c5ee73f439ee53dbe45f

https://qiita.com/yukiweaver/items/9f07b32f31c7e863fe1c

https://qiita.com/mimickn/items/83f6f9186cbc132683ba

https://www.fundely.co.jp/blog/tech/2020/01/22/180037/

https://qiita.com/NaokiIshimura/items/8203f74f8dfd5f6b87a0

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

ActiveRecordにはどんな種類があるのか?書き方は?

ActiveRecordとは

人間で言う「脳みそ」のようなものです。勉強し始めた頃、私はこのように覚えました(笑)データベースへ指示を送るメソッドの総称のことをいい、一つ一つのコードはコントローラーへ記述します。少し難しく言うと、ruby on railsでよく使われるORMの1つです。ORM(Object Relational Mapping)とはオブジェクト指向の言語でオブジェクトとして使うために変換してくれるものです。ActiveRecordのメソッドを使うことで、データベースのレコードからデータを取り出してくれたり、レコードへ新しいデータを追加することができます。よく使うActiveRecordのメソッドの種類と書き方をまとめてみました。
間違いやもっと良い書き方があれば、教えてください!

種類と書き方

今回は「title」と言う名前のモデルがあるとして、コードの書き方を説明します。

all 全てのデータを受け取る

Title.all

find ある1つのデータを受け取る

Title.find(取得したいテーブルの数字)
ex) Title.find(3)
#Titleモデルのtitlesテーブルの3番目の値を受け取る

new インスタンスを生成する

Title.new

save インスタンスを保存する

Title.save

order 並び順には2種類あり、それらを使って並び順を変更する
asc→小さいものから大きいもの、古いものから新しいもの(昇順)
desc→上の逆の並び(降順)

Title.order("カラム名 並び順")
ex) Title.order("name ASC")
#nameカラムを古いものから並べて、受け取る

where 条件に一致したテーブルを配列で受け取る

Title.where('カラムを含ませた条件式')
ex) Title.where('name > 5')
#5より大きいnameカラムを受け取る
Title.where('id: 1')
#idが1のテーブルを受け取る

create テーブルにレコードを保存する

Title.create(カラム名: 保存する値)
ex) Title.create(name: params[:first])
#nameカラムにfirstと送られてきたparamsを保存する

includes 関連するモデルを一度にまとめて受け取ることができる
allと似ているがallで受け取る場合、アソシエーションがある時に受け取る回数が多くなってしまい、N+1問題が発生する。その時にincludesを使うことで一度のアクセスだけで多くのデータを受け取ることができるためN+1問題を解決してくれる。

Title.includes(:モデル名)
ex) Title.includes(:name)
#nameモデルが関連するモデル情報をまとめて受け取る

以上で紹介を終わります。一覧はこちらです。ぜひ参考にしてみてください!
rails ガイド

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

【Rails】Google Maps API を利用して現在地から店舗検索を実装する

目標

  1. 現在地を中心に表示
  2. 登録済みの店舗(:studio)をピンを立てて表示。ピンをクリックすると詳細画面へのリンクが表示され、リンククリック後に詳細画面へ遷移。 2.gif

開発環境

  • Ruby: 2.6.4
  • Rails: 5.2.4
  • OS: macOS Catalina

前提条件

  • Google Maps APIを利用して、マップが1件以上表示できること

以前、投稿した記事の内容の続きです。
一度、目を通しておくとわかりやすいかと思います。

【Rails】Google Maps APIを利用して登録した住所をMap表示する

Geolocation APIを有効化

Geolocation APIを利用して、現在地情報を取得します。
下記の記事を参考に、Geolocation APIを有効化しました。
Geocoding APIの有効化についてまとめてある記事ですが、手順は同じです。

【Rails】Geocoding APIを用いて高精度で緯度経度を算出し、Google Mapに表示する方法

実装

今回は、現在地検索用に新たにビューを作成しているのでコントローラーの編集も行っていきます。

現在地検索ページ→ maps.html.erb

コントローラーの編集

gon.studiosを定義します。

app/controllers/studios_controller.erb
  def maps
    gon.studios = Studio.all
  end

ビューの編集

<%= include_gon %>を記述することで、Javascript内で使用可能にする。

app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>StudioDig</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= include_gon %> #追記
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.9.0/css/all.css">
    <%= stylesheet_link_tag    'application', media: 'all' %>
    <%= javascript_include_tag 'application' %>

では、実際に記述していきます。

app/views/studios/maps.html.erb
#ここにマップ表示される
<div id="map"></div>


<script>
let map;
let marker = []; // マーカーを複数表示させたいので、配列化
let infoWindow = []; // 吹き出しを複数表示させたいので、配列化
const studios = gon.studios; // コントローラーで定義したインスタンス変数を変数に代入

function initMap(){
  // 現在位置の特定
  navigator.geolocation.getCurrentPosition(function (position){
    // LatLngに位置座標を代入
    LatLng = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);

    // mapの初期位置設定
    map = new google.maps.Map(document.getElementById('map'), {
      center: LatLng,
      #倍率はお好みで
      zoom: 13
    });
    // map.setCenterで地図が初期位置に移動
    map.setCenter(LatLng);

    // forは繰り返し処理
    // 変数iを0と定義し、
    // その後gonで定義したstudios分繰り返し加える処理を行う
    for (let i = 0; i < studios.length; i++){
      // studios[i]は変数iのユーザーを取得している
      marker[i] = new google.maps.Marker({
        map: map,
        position: {
          // DBに保存してある、緯度・経度を呼び出す
          lat: studios[i].latitude,
          lng: studios[i].longitude
        }
      });

      // 変数iを変数idに代入
      let id = studios[i]['id']

      // infoWindowは吹き出し
      infoWindow[i] = new google.maps.InfoWindow({
        // contentで中身を指定
        // 今回は文字にリンクを貼り付けた形で表示
        content: `<a href='/studios/${id}'>${studios[i].name}</a>`
      });
      // markerがクリックされた時、
      marker[i].addListener("click", function(){
        // infoWindowを表示
        infoWindow[i].open(map, marker[i]);
      });
    }
  });
}
</script>

<script async defer
      src="https://maps.googleapis.com/maps/api/js?key=<%= ENV['GMAP_API'] %>&callback=initMap">
</script>

CSS等で、高さ・幅の設定をしていないとマップが表示されません。忘れずに設定しておいてください。

application.scss
...
#map {
  height: 500px;
  width: 100%;
}
...

おわりに

Google Maps API実装の初期段階で、エラーがどこに出てくるのかわからず「エラーどこ..?」となったのでエラーがどこに表示されるかを簡単に説明して終わりとします。

javascriptで実装しているので、ブラウザのデバッグツールからエラー内容が確認できます。

10.png

該当ページで、デバッグツールを開きます。
エラーがある際は、右上に赤いバツ印があるのでそれをクリック

11.png

すると、下のコンソールにエラーが表示されます。

12.png

さらに、コンソールの右上部分をクリック
すると、今度は右上に具体的なエラー箇所が表示されます。

経験則ですが、具体的なソースコードを見てもわけわからんコードが羅列してあるようなときは、APIが起因(APIが正常に起動してないとか)であることが多かった印象です。
そんなときは、もう一度ターミナルからログを確認したり、Google Maps APIの設定を見直してみると良いかもしれません。
(筆者はそこでめちゃくちゃ苦戦しました。)

参考記事

【Rails】Geolocation APIを用いて位置情報を取得する方法
【Ruby on Rails】Googlemapの複数ピン立て、吹き出し、リンク
【Rails】Google Maps APIを利用して登録した住所をMap表示する

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

【Railsチュートリアル】第4章 Rails風味のRuby 演習と解答

4.2.1 文字列

4.2.1 - 1

city変数に適当な市区町村を、prefecture変数に適当な都道府県を代入してください。

> city = "たこ焼きが"
 => "大阪府" 
> prefecture = "おいしい県"
 => "大阪市" 

4.2.1 - 2

先ほど作った変数と式展開を使って、「東京都 新宿区」のような住所の文字列を作ってみましょう。出力にはputsを使ってください。

> puts "#{city} #{prefecture}"
たこ焼きが おいしい県
 => nil

4.2.1 - 3

上記の文字列の間にある半角スペースをタブに置き換えてみてください。(ヒント: 改行文字と同じで、タブも特殊文字です)

> puts city + " " + prefecture
たこ焼きが おいしい県
 => nil 

4.2.1 - 4

タブに置き換えた文字列を、ダブルクォートからシングルクォートに置き換えてみるとどうなるでしょうか?

> puts city + ' ' + prefecture                                              
たこ焼きが おいしい県
 => nil 

4.2.2 オブジェクトとメッセージ受け渡し

4.2.2 - 1

"racecar" の文字列の長さはいくつですか? lengthメソッドを使って調べてみてください。

> "racecar".length
 => 7 

4.2.2 - 2

reverseメソッドを使って、"racecar"の文字列を逆から読むとどうなるか調べてみてください。

> "racecar".reverse
 => "racecar" 

4.2.2 - 3

変数sに "racecar" を代入してください。その後、比較演算子(==)を使って変数sとs.reverseの値が同じであるかどうか、調べてみてください。

> s = "racecar"
 => "racecar" 
> s == s.reverse
 => true 

4.2.2 - 3

リスト 4.9を実行すると、どんな結果になるでしょうか? 変数sに "onomatopoeia" という文字列を代入するとどうなるでしょうか?(ヒント: 上矢印(またはCtrl-Pコマンド)を使って以前に使ったコマンドを再利用すると一からコマンドを全部打ち込む必要がなくて便利ですよ。)

> puts "It's a palindrome!" if s == s.reverse
It's a palindrome!
 => nil

> s = "onomatopoeia"
 => "onomatopoeia" 
> puts "It's a palindrome!" if s == s.reverse
 => nil  

palindrome = 回文
onomatopoeia = 擬音、擬声、擬音語

4.2.3 メソッドの定義

4.2.3 - 1

リスト 4.10のFILL_INの部分を適切なコードに置き換え、回文かどうかをチェックするメソッドを定義してみてください。ヒント: リスト 4.9の比較方法を参考にしてください。

> def palindrome_tester(s)
>   if s == s.reverse
>     puts "It's a palindrome!"
>     else
>     puts "It's not a palindrome."
>     end
>   end
 => :palindrome_tester 

4.2.3 - 2

上で定義したメソッドを使って “racecar” と “onomatopoeia” が回文かどうかを確かめてみてください。1つ目は回文である、2つ目は回文でない、という結果になれば成功です。

> puts palindrome_tester("racecar")
It's a palindrome!
 => nil

> puts palindrome_tester("onomatopoeia")
It's not a palindrome.
 => nil 

4.2.3 - 3

palindrome_tester("racecar")に対してnil?メソッドを呼び出し、戻り値がnilであるかどうかを確認してみてください(つまりnil?を呼び出した結果がtrueであることを確認してください)。このメソッドチェーンは、nil?メソッドがリスト 4.10の戻り値を受け取り、その結果を返しているという意味になります。

> palindrome_tester("racecar").nil?
It's a palindrome!
 => true

4.3.1 配列と範囲演算子

4.3.1 - 1

文字列「A man, a plan, a canal, Panama」を ", " で分割して配列にし、変数aに代入してみてください。

> a = "A man, a plan, a canal, Panama".split(',')
 => ["A man", " a plan", " a canal", " Panama"]  

4.3.1 - 2

今度は、変数aの要素を連結した結果(文字列)を、変数sに代入してみてください。

> s = a.join
 => "A man a plan a canal Panama" 

4.3.1 - 3

変数sを半角スペースで分割した後、もう一度連結して文字列にしてください(ヒント: メソッドチェーンを使うと1行でもできます)。リスト 4.10で使った回文をチェックするメソッドを使って、(現状ではまだ)変数sが回文ではないことを確認してください。downcaseメソッドを使って、s.downcaseは回文であることを確認してください。

> s = s.split.join
 => "AmanaplanacanalPanama"

> def palindrome_tester(s)
>   if s == s.reverse
>     puts "It's a palindrome!"
>     else
>     puts "It's not a palindrome."
>     end
>   end
 => :palindrome_tester 
> palindrome_tester(s)
It's not a palindrome.
 => nil 

> palindrome_tester(s.downcase)
It's a palindrome!
 => nil 

4.3.1 - 4

aからzまでの範囲オブジェクトを作成し、7番目の要素を取り出してみてください。同様にして、後ろから7番目の要素を取り出してみてください。(ヒント: 範囲オブジェクトを配列に変換するのを忘れないでください)

> a = ('a'..'z').to_a
 => ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"] 

> a[-7]
 => "t" 

4.3.2 ブロック

4.3.2 - 1

範囲オブジェクト0..16を使って、各要素の2乗を出力してください。

> (0..16).each{|i| puts i*i }
0
1
4
9
16
25
36
49
64
81
100
121
144
169
196
225
256
 => 0..16

# 下記でも同じ
> (0..16).each do |i|
>     puts i * i
>   end

4.3.2 - 2

yeller(大声で叫ぶ)というメソッドを定義してください。このメソッドは、文字列の要素で構成された配列を受け取り、各要素を連結した後、大文字にして結果を返します。例えばyeller(['o', 'l', 'd'])と実行したとき、"OLD"という結果が返ってくれば成功です。ヒント: mapとupcaseとjoinメソッドを使ってみましょう。

> def yeller(s)
>   s.map(&:upcase).join
>   end
 => :yeller 
> yeller(['o', 'l', 'd'])
 => "OLD" 

4.3.2 - 3

random_subdomainというメソッドを定義してください。このメソッドはランダムな8文字を生成し、文字列として返します。ヒント: サブドメインを作るときに使ったRubyコードをメソッド化したものです。

> def random_subdomain
>   ('a'..'z').to_a.shuffle[0..7].join
>   end
 => :random_subdomain 
> random_subdomain 
 => "mqntsphl" 

4.3.2 - 4

リスト 4.12の「?」の部分を、それぞれ適切なメソッドに置き換えてみてください。ヒント:split、shuffle、joinメソッドを組み合わせると、メソッドに渡された文字列(引数)をシャッフルさせることができます。

> def string_shuffle(s)
>   s.split('').shuffle.join
>   end
 => :string_shuffle 
> string_shuffle("foobar")
 => "boroaf" 

4.3.3 ハッシュとシンボル

4.3.3 - 1

キーが'one'、'two'、'three'となっていて、それぞれの値が'uno'、'dos'、'tres'となっているハッシュを作ってみてください。その後、ハッシュの各要素をみて、それぞれのキーと値を"'#{key}'のスペイン語は'#{value}'"といった形で出力してみてください。

> num = { one: "uno", two: "dos", three: "tres" }
 => {:one=>"uno", :two=>"dos", :three=>"tres"} 
> num.each do |key, value|
>     puts "'#{key}'のスペイン語は'#{value}'"
>   end
'one'のスペイン語は'uno'
'two'のスペイン語は'dos'
'three'のスペイン語は'tres'
 => {:one=>"uno", :two=>"dos", :three=>"tres"}

4.3.3 - 2

person1、person2、person3という3つのハッシュを作成し、それぞれのハッシュに:firstと:lastキーを追加し、適当な値(名前など)を入力してください。その後、次のようなparamsというハッシュのハッシュを作ってみてください。1)キーparams[:father]の値にperson1を代入、2)キーparams[:mother]の値にperson2を代入、3)キーparams[:child]の値にperson3を代入。最後に、ハッシュのハッシュを調べていき、正しい値になっているか確かめてみてください。(例えばparams[:father][:first]がperson1[:first]と一致しているか確かめてみてください)

> person1 = { :first => "mou", :last => "toon" }
 => {:first=>"mou", :last=>"toon"} 
> person2 = { :first => "coco", :last => "toon" }                           
 => {:first=>"coco", :last=>"toon"} 
> person3 = { :first => "em", :last => "toon" }                             
 => {:first=>"em", :last=>"toon"} 
> params[:father] = person1
 => {:first=>"mou", :last=>"toon"} 
> params[:mother] = person2
 => {:first=>"coco", :last=>"toon"} 
> params[:child] = person3
 => {:first=>"em", :last=>"toon"} 
> params[:father][:first] == person1[:first]
 => true

4.3.3 - 3

> user = { name: "moutoon", email: "moutoon@example.com", password_digest: ('a'..'z').to_a.shuffle[0..15].join }
 => {:name=>"moutoon", :email=>"moutoon@example.com", :password_digest=>"frkwnzxbdlpmojhi"}

4.3.3 - 4

Ruby API(訳注: もしくはるりまサーチ)を使って、Hashクラスのmergeメソッドについて調べてみてください。次のコードを実行せずに、どのような結果が返ってくるか推測できますか? 推測できたら、実際にコードを実行して推測があっていたか確認してみましょう。

> { "a" => 100, "b" => 200 }.merge({ "b" => 300 })
 => {"a"=>100, "b"=>300}

4.4.1 コンストラクタ

4.4.1 - 1

1から10の範囲オブジェクトを生成するリテラルコンストラクタは何でしたか?(復習です)

> a = 1..10
 => 1..10

リテラルコンストラクタ

Rubyでは、文字列(String)・配列(Array)・ハッシュ(Hash)・範囲(Range)などのクラスに対し、当該オブジェクトのインスタンスを暗黙的に作成する記法が存在します。 Railsチュートリアルでは、こうした記法を「リテラルコンストラクタ」と呼んでいます。

4.4.1 - 2

今度はRangeクラスとnewメソッドを使って、1から10の範囲オブジェクトを作ってみてください。ヒント: newメソッドに2つの引数を渡す必要があります

> b = Range.new(1,10)
 => 1..10

4.4.1 - 3

比較演算子==を使って、上記2つの課題で作ったそれぞれのオブジェクトが同じであることを確認してみてください。

> a == b
 => true

4.4.2 クラス継承

4.4.2 - 1

Rangeクラスの継承階層を調べてみてください。同様にして、HashとSymbolクラスの継承階層も調べてみてください。

> Range.class.superclass
 => Module 
> Range.class.superclass.superclass
 => Object 
> Range.class.superclass.superclass.superclass
 => BasicObject 
> Range.class.superclass.superclass.superclass.superclass
 => nil

# Hash, Symbolクラスも同様

4.4.2 - 1

リスト 4.15にあるself.reverseのselfを省略し、reverseと書いてもうまく動くことを確認してみてください。

> class Word < String
>   def palindrome?
>     self == reverse
>     end
>   end
 => :palindrome? 
> s = Word.new("level")
 => "level"
> s.palindrome?
 => true

4.4.3 組み込みクラスの変更

4.4.3 - 1

palindrome?メソッドを使って、“racecar”が回文であり、“onomatopoeia”が回文でないことを確認してみてください。南インドの言葉「Malayalam」は回文でしょうか? ヒント: downcaseメソッドで小文字にすることを忘れないで。

> class Strong
>   def palindrome?
>     self == self.reverse
>     end
>   end
 => :palindrome? 
> "racecar".palindrome?
 => true 
> "onomatopoeia".palindrome?
 => false 
> "Malayalam".downcase.palindrome?
 => true

4.4.3 - 2

リスト 4.16を参考に、Stringクラスにshuffleメソッドを追加してみてください。ヒント: リスト 4.12も参考になります。

> class String
>   def shuffle
>     self.split('').shuffle.join
>     end
>   end
 => :shuffle 
> "moutoon".shuffle
 => "uonomto"

4.4.3 - 3

リスト 4.16のコードにおいて、self.を削除してもうまく動くことを確認してください。

> class String
>   def shuffle
>     split('').shuffle.join
>     end
>   end
 => :shuffle 
> "moutoon".shuffle
 => "omnotou"

4.4.4 コントローラクラス

4.4.4 - 1

第2章で作ったToyアプリケーションのディレクトリでRailsコンソールを開き、User.newと実行することでuserオブジェクトが生成できることを確認してみましょう。

> user = User.new
 => #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>

4.4.4 - 2

生成したuserオブジェクトのクラスの継承階層を調べてみてください。

> user.class
 => User(id: integer, name: string, email: string, created_at: datetime, updated_at: datetime)
> user.class.superclass
 => ApplicationRecord(abstract)
> user.class.superclass.superclass
 => ActiveRecord::Base
> user.class.superclass.superclass.superclass
 => Object
> user.class.superclass.superclass.superclass.superclass
 => BasicObject
> user.class.superclass.superclass.superclass.superclass.superclass
 => nil

4.4.5 ユーザークラス

4.4.5 - 1

Userクラスで定義されているname属性を修正して、first_name属性とlast_name属性に分割してみましょう。また、それらの属性を使って "Michael Hartl" といった文字列を返すfull_nameメソッドを定義してみてください。最後に、formatted_emailメソッドのnameの部分を、full_nameに置き換えてみましょう(元々の結果と同じになっていれば成功です)

class User
  attr_accessor :first_name, :last_name, :email

  def initialize(attributes = {})
    @first_name = attributes[:first_name]
    @last_name = attributes[:last_name]
    @email = attributes[:email]
  end

  def full_name
    "#{@first_name} #{@last_name}"
  end

  def alphabetical_name
    "#{@last_name}, #{@first_name}"
  end

  def formatted_email
    "#{full_name} <#{@email}>"
  end

end
> require './example_user'
 => true
> user = User.new(first_name: "mou", last_name: "toon", email: "moutoon@example.com")
 => #<User:0x00007f3bd406ec50 @first_name="mou", @last_name="toon", @email="moutoon@example.com"> 
2.6.3 :004 > user.full_name
 => "mou toon"
> user.formatted_email
 => " <moutoon@example.com>"

4.4.5 - 2

"Hartl, Michael" といったフォーマット(苗字と名前がカンマ+半角スペースで区切られている文字列)で返すalphabetical_nameメソッドを定義してみましょう。

> user.alphabetical_name
 => "toon, mou"

4.4.5 - 3

full_name.splitとalphabetical_name.split(', ').reverseの結果を比較し、同じ結果になるかどうか確認してみましょう。

> user.full_name.split == user.alphabetical_name.split(', ').reverse
 => true

memo

unless文

条件式が偽の場合の処理を記述するのに使われる。

> string = "moutoon"
 => "moutoon" 
2.6.3 :043 > puts "The string '#{string}' is nonempty." unless string.empty?
The string 'moutoon' is nonempty.
 => nil 

string(文字列)empty?(空白)でなければ"The string '#{string}' is nonempty."を出力せよ。

配列と範囲オブジェクト

> ('あ'..'お').to_a
 => ["あ", "ぃ", "い", "ぅ", "う", "ぇ", "え", "ぉ", "お"] 

ひらがなもできるのかな?と試してみたらできました。
捨て仮名も一緒に表示されるようですね。

さいごに

Rubyの基礎のキ部分はProgateとドットインストールで学習してきましたが、ここにきて学習の点と点がつながった繋がった感じがして、とても楽しかったです。すべて理解できたとは言えませんが、少しずつ進んでいきたいと思います!

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

Ruby復習②

Ruby復習①からの続きです。

クラスの定義

Rubyファイル
class Menu #クラスは大文字で始める
end

attr_accessor

Rubyファイル
class Menu 
  attr_accessor :name # :nameはインスタンス変数
end

複数のインスタンス変数

Rubyファイル
class Menu 
  attr_accessor :name 
  attr_accessor :price
end

インスタンスの生成

Rubyファイル
class Menu 
.
.
end
menu1 = Menu.new
#menu1にMenuクラスから生成したインスタンスを代入した

インスタンス変数に値を代入する

Rubyファイル
class Menu 
  attr_accessor :name 
  attr_accessor :price
end
menu1 = Menu.new
menu1.name = "すし"
puts menu1.name
コンソール
すし

インスタンスメソッド

クラスの中でメソッドを定義し、呼び出す

クラスの中で定義したメソッドはインスタンスメソッドと呼ばれる

Rubyファイル
class Menu 
  attr_accessor :name 
  attr_accessor :price
  def show
    puts "私はメニューです"
  end
end
menu1 = Menu.new
menu1.show
コンソール
私はメニューです

インスタンスメソッドの中でインスタンス変数を使う

Rubyファイル
class Menu 
  attr_accessor :name 
  attr_accessor :price
  def show_name
    puts "私は#{self.name}です"
  end
end
menu1 = Menu.new
menu1.name = "おでん"
menu1.show_name
コンソール
私はおでんです

initializeメソッド

Menu.newでMenuインスタンスが生成された直後に、initializeメソッドが呼び出され、その中の処理が実行される

Rubyファイル
class Menu 
.
.
  def initialize
    puts "メニューが生成されました"
  end
  .
  .
end
menu1 = Menu.new
コンソール
メニューが生成されました

initializeメソッドの引数

Rubyファイル
class Menu 
.
.
  def initialize(message)
    puts message
  end
  .
  .
end
menu1 = Menu.new("こんにちは")
コンソール
こんにちは

require

menu.rb
class Menu
.
.
end
index.rb
require "./menu"
.
.

gets.chomp

gets.chompと記述すると入力を受け付けることができる

Rubyファイル
puts "名前を入力してください"
name = gets.chomp
puts "あなたの名前は#{name}です"
コンソール
名前を入力してください
Tanaka #自分で打ち込む
あなたの名前はTanakaです 

gets.chomp.to_i

gets.chomp.to_iと記述すると数値の入力を受け付けることができる

Rubyファイル
puts "数字を入力してください"
number = gets.chomp.to_i
puts "#{number}の2倍は{number * 2}です"
コンソール
数字を入力してください
3 #自分で打ち込む
3の2倍は6です

継承

food.rb
require "./menu"
class Food < Menu
end
#Menuが親クラス、Foodは子クラス

super

オーバーライドしたメソッドの中で、superとすることで、
親クラスの同名のメソッドを呼び出すことができる

menu.rb
class Menu
  attr_accessor :name
  attr_accessor :price
  def initialize(name:, price:)
    self.name = name
    self.price = price
  end
end
food.rb
class Food < Menu
  attr_accessor :calorie
  def initialize(name:, price:, calorie:)
    super(name: name, price: price)  #親クラスの同名メソッドを呼び出す
    self.calorie = calorie
  end
  .
  .
end

Dateクラス

Dateクラスの読み込み

Dateクラスは日付を扱う

Rubyファイル
require "date"
.
.

Dateクラスのインスタンス

Rubyファイル
require "date"
date1 = Date.new(2021, 1, 28) #引数に「年・月・日」を渡して、Dateインスタンスを生成する
puts date1
コンソール
2021-1-28

Dateクラスのインスタンスメソッド

Rubyファイル
require "date"
date1 = Date.new(2021, 1, 28)
puts date1.sunday? #sunday?メソッドはDateクラスのインスタンスの日付が日曜日かどうかを真偽値で返す
コンソール
false

今日の日付のDateインスタンスを取得する

Rubyファイル
require "date"
date1 = Date.today
puts date1
コンソール
2021-1-28   #今日の日付が出力される

クラスメソッドを呼び出す

Rubyファイル
class Menu
.
.
  def Menu.is_discount_day?
  .
  .
  end
end
puts Menu.is_discount_day?
コンソール
true

ri コマンド

ri コマンドで知りたい命令、オブジェクトのドキュメントを見ることができる

print, put, p

Rubyファイル
print "hello world" #渡したオブジェクトを文字列にして表示させるための命令
puts "hello world" #printの動作+ 改行が付く
p "hello world" #デバッグ用
コンソール
hello worldhello world
"hello world"

定数の命名ルール

定数は英大文字始まりでないといけない。
慣習的に全部大文字にすることが多い。
定数は書き換えると警告は出るが、値は書き換えられてしまうので、注意する

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

Ruby復習①

ポートフォリオ作成に向けて、1月中に基礎学習を終わらせます。
HTML/CSS、JavaScriptの復習を終わらせたので、
次にRubyの復習をProgate、ドットインストールを用いて行っていきます。
なお、記述する内容は自分が忘れがちな所、苦手な所です。
HTML/CSS編、JavaScript編同様、自分用の備忘録的に投稿していくので(箇条書きで)、
読みづらいor間違えていることを書いている可能性もあるので、悪しからず。
(できるだけ他の方が見てもためになれば良いなとは思って投稿しますが・・・)
箇条書きで書いたことに関して、新たな発見、学び、気づき等あれば、
それぞれ個々に詳細を記述していこうと思います。

文字列

Rubyファイル
puts 'Hello World'
# シングルクォーテーションorダブルクォーテーションで囲む

変数

変数の定義

Rubyファイル
name = "Taro"
# 変数 = 代入する値の形で変数を定義できる

変数名のルール

○良い例・・・ 英単語を用いる、2語以上の場合はアンダーバーで区切る
× 悪い例・・・ 数字から始まる、ローマ字、日本語

変数展開

Rubyファイル
name = "鈴木"
puts "こんにちは#{name}さん"
#変数展開を用いたい場合はシングルクォーテーションではなく、ダブルクォーテーションを使うことに注意
コンソール
こんにちは鈴木さん

if文

Rubyファイル
if 条件式
  処理
end
Rubyファイル
score = 90
if score > 80
  puts "よくできました"
end
コンソール
よくできました

条件式の出力

Rubyファイル
score = 90
puts score > 80
コンソール
true

比較演算子

Rubyファイル
a == b #aとbが等しいとき、true
a != b #aとbが異なるとき、 true

else

Rubyファイル
if 条件式
  処理
else
  処理
end
Rubyファイル
score = 100
if score == 100
  puts "満点です"
else
  puts "満点ではありません"
end

elsif

Rubyファイル
if 条件式1
  処理
elsif 条件式2
  処理
else
  処理
end
Rubyファイル
score = 75
if score > 80
  puts "よくできました"
elsif score > 60
  puts "まずまずです"
else 
  puts "がんばりましょう"
end
コンソール
まずまずです

条件の組み合わせ

Rubyファイル
score = 68
if score <= 80 && score > 60
  puts "まずまずです"
end
コンソール
まずまずです

配列を変数に代入する

Rubyファイル
names = ["Yamada", "Sato", "Tanaka"]
puts names
puts names[1]
コンソール
Yamada
Sato
Tanaka
Sato

配列の要素と変数展開

Rubyファイル
names = ["Yamada", "Sato", "Tanaka"]
puts "私の名前は#{names[1]}です"
コンソール
私の名前はSatoです

each文

Rubyファイル
配列.each do |変数名|
  #実行したい処理
end
Rubyファイル
names = ["Yamada", "Sato", "Tanaka"]
names.each do |name|
  puts name
end
コンソール
Yamada
Sato
Tanaka

ハッシュ

Rubyファイル
{キー1 => 1, キー2 => 2}

#例
{"name" => "Yamada", "age" => 20}
#"name","age"がキー、"Yamada",20が値

ハッシュを変数に代入する

Rubyファイル
user = {"name" => "Yamada", "age" => 20}
puts user
コンソール
{"name" => "Yamada", "age" => 20}

ハッシュの要素を用いる

Rubyファイル
user = {"name" => "Yamada", "age" => 20}
puts user{"name"}
コンソール
Yamada

ハッシュの要素の更新

Rubyファイル
user = {"name" => "Yamada", "age" => 20}
puts user
user["age"] = 30
puts user
コンソール
{"name" => "Yamada", "age" => 20}
{"name" => "Yamada", "age" => 30}

ハッシュに要素を追加する

Rubyファイル
user = {"name" => "Yamada", "age" => 20}
puts user
user["gender"] = "male"
puts user
コンソール
{"name" => "Yamada", "age" => 20}
{"name" => "Yamada", "age" => 20, "gender" => "male"}

ハッシュのキーにシンボルを用いる

Rubyファイル
user = {"name" => "Yamada", "age" => 20}
# 以下のように書き換えられる
user = {:name => "Yamada", :age => 20}
puts user[:name]
コンソール
Yamada

キーがシンボルのハッシュの省略

Rubyファイル
user = {:name => "Yamada", :age => 20}
# 以下のように書き換えられる
user = {name: "Yamada", age: 20}
puts user[:name]
コンソール
Yamada

ハッシュの書き方まとめ

Rubyファイル
#キーが文字列の書き方
{"name" => "Yamada", "age" => 20}
#キーがシンボルの書き方
{:name => "Yamada", :age => 20}
#キーがシンボルの書き方(省略形)
{name: "Yamada", age: 20}

ifとnil

if文の条件

true・・・ falseとnil以外
false・・・ false,nil

配列の要素にハッシュを入れる

Rubyファイル
users = [
  {name: "Yamada", age: 20},
  {name: "Sato", age: 25}
]
user = users[1]
puts users[1]
puts user[:name]
puts users[1][:name]
コンソール
{name: "Sato", age: 25}
Sato
Sato

each

Rubyファイル
users = [
  {name: "Yamada", age: 20},
  {name: "Sato", age: 30}
]
users.each do |user|
  puts user
end

users.each do |user|
  puts user[:name]
end
コンソール
{name: "Yamada", age: 20}
{name: "Sato", age: 30}

Yamada
Sato

メソッド

メソッドの定義

Rubyファイル
def メソッド名
  処理
end
Rubyファイル
def introduce
  puts "こんにちは"
  puts "私はたろうです
end

メソッドの呼び出し

Rubyファイル
def introduce
  puts "こんにちは"
  puts "私はたろうです
end
introduce  #introduceメソッドを呼び出している
コンソール
こんにちは
私はたろうです

引数を受け取るメソッドの定義

Rubyファイル
def メソッド名(引数名)
  処理
end
Rubyファイル
def introduce(name)
  puts "こんにちは"
  puts "私は#{name}です"
end

introduce("たろう")
introduce("はなこ")
コンソール
こんにちは
私はたろうです
こんにちは
私ははなこです

複数の引数を受け取る

Rubyファイル
def introduce(name, age)
  puts "私は#{name}です"
  puts "#{age}歳です"
end

introduce("たろう",20)
コンソール
私はたろうです
20歳です

戻り値

Rubyファイル
def add(a,b)
  return a + b
end
sum = add(1,3)
puts sum
コンソール
4

様々な戻り値

Rubyファイル
def negative?(number)
  return number < 0
end
puts negative?(5)
コンソール
false

複数のreturn

Rubyファイル
def judge(score)
  if score > 80
    return "よくできました"
  end
  return "がんばりましょう"
end
puts judge(70)
コンソール
がんばりましょう

キーワード引数

Rubyファイル
def introduce(name:, age:, food:)
  puts "私は#{name}です"
  puts "年齢は#{age}歳です"
  puts "好きな食べ物は#{food}です"
end

introduce(name: "たろう", age: 20, food: "すし")
コンソール
私はたろうです
年齢は20歳です
好きな食べ物はすしです
続きはRuby復習②
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsでいいね数ランキング機能を作る!

はじめに

PFでランキング機能を実装したので、
備忘録として書いていきたいです。

いいね機能とランキング機能の実装は以下を参考にさせて頂きました。
Railsでお手軽ランキング機能
Ruby on Rails 〜同率順位も含めたランキング機能〜

環境

rails 5.2.4

前提

投稿機能
いいね機能
を実装している

基本的には上記2つの記事を参考にさせて頂いてますが、
変数などは私のアプリケーションのものに変更している箇所もあります。

ランキング機能実装

images/index.html.erb
  <h1>いいね数ランキング</h1>
    <!--lastFavoritesはその投稿のいいね数を代入するための場所-->
    <% lastFavorite = 0 %>
    <!--jは順位を示す変数記号は何でもよい-->
    <% j = 1 %>
    <% @all_ranks.each.with_index(1) do |ranking,i| %>
     <div class="card shadow-lg">
      <% if i == 1 %>
       <% lastFavorite = ranking.favorites.count %>
      <% end %>
      <!--1位はelseの下に行き2位以下はelseの上を回る-->
      <% if ranking.favorites.count != lastFavorite %>
       <% j = i %>
       <p><%= j %></p>
       <!--ユーザー名-->  
        <p><%= ranking.user.name %></p>
       <!--投稿画像-->
       <div>
        <%= attachment_image_tag image, :image, size: "400x400", format: 'jpg', fallback: "no_image.jpg" %>
       </div>
       <p><%= ranking.favorites.count %> いいね</p>
       <!--ここでfavorites.countをlastFavoritesに代入して上のlastFavoritesに代入する-->
       <% lastFavorite = ranking.favorites.count %>
      <% else %>
       <% lastFavorite = ranking.favorites.count %>
       <p><%= j %></p>
       <!--ユーザー名-->
       <p><%= ranking.user.name %></p>
       <!--投稿画像-- >
       <div>
        <%= attachment_image_tag image, :hobby, size: "400x400", format: 'jpg', fallback: "no_image.jpg" %>
       </div>
       <p><%= ranking.favorites.count %> いいね</p>
      <% end %>
     </div>
    <% end %>

解説

解説は参考元のこちらとほとんど被っています。
Ruby on Rails 〜同率順位も含めたランキング機能〜

コメントアウトして書いてありますが分解すると、

image/index
    <!--lastFavoritesはその投稿のいいね数を代入するための場所-->
    <% lastFavorite = 0 %>
    <!--jは順位を示す変数記号は何でもよい-->
    <% j = 1 %>

ここで表示する投稿のいいね数を保存するlastFavoriteを宣言します。
jは順位を示すための変数です。それ以上の役割はないので何でもいいです。
順位のjとでも考えてください。

image/index
    <% if i == 1 %>
     <% lastFavorite = ranking.favorites.count %>
    <% end %>

ここで1位のいいね数がlastFavoriteに代入されます。

image/index
      <% if ranking.favorites.count != lastFavorite %>
       <% j = i %>
       <p><%= j %></p>
       <!--ユーザー名-->  
        <p><%= ranking.user.name %></p>
       <!--投稿画像-->
       <div>
        <%= attachment_image_tag image, :image, size: "400x400", format: 'jpg', fallback: "no_image.jpg" %>
       </div>
       <p><%= ranking.favorites.count %> いいね</p>
       <!--ここでfavorites.countをlastFavoritesに代入して上のlastFavoritesに代入する-->
       <% lastFavorite = ranking.favorites.count %>
      <% else %>
       <% lastFavorite = ranking.favorites.count %>
       <p><%= j %></p>
       <!--ユーザー名-->
       <p><%= ranking.user.name %></p>
       <!--投稿画像-- >
       <div>
        <%= attachment_image_tag image, :hobby, size: "400x400", format: 'jpg', fallback: "no_image.jpg" %>
       </div>
       <p><%= ranking.favorites.count %> いいね</p>
      <% end %>
     </div>
    <% end %>

1位は、

<% if i == 1 %>
 <% lastFavorite = ranking.favorites.count %>
<% end %>

の8行目のif文でlastFavorite = ranking.favorites.countとなっているので、
12行目のif文に弾かれelse以下に行き1位として表示されます。

2位以下は12行目直下の文に行きます。

image/index
   <% if ranking.favorites.count != lastFavorite %>
       <% j = i %>
       <p><%= j %></p>
       <!--ユーザー名-->  
        <p><%= ranking.user.name %></p>
       <!--投稿画像-->
       <div>
        <%= attachment_image_tag image, :image, size: "400x400", format: 'jpg', fallback: "no_image.jpg" %>
       </div>
       <p><%= ranking.favorites.count %> いいね</p>
       <!--ここでfavorites.countをlastFavoritesに代入して上のlastFavoritesに代入する-->
       <% lastFavorite = ranking.favorites.count %>

2位以下は基本的にここのif文をぐるぐる回ることになります。
2位以下を順番に表示していき、

仮に2位が50いいねの場合、ranking.favorites.countに50いいねが代入されlastFavoritesが50になります。
いいね数が同数の投稿があればelse以下に行きます。

3位が49いいねの場合、if文直下に行きます。
そして、また一番下のranking.favorites.countに49いいねが代入されlastFavoritesが49になり...
と繰り返し処理が行われます。

最後の説明が分かりにくかったら申し訳ないです。
間違いがありましたらご指摘お願い致します。
解説は以上となります。

ここまで見て頂きありがとうございました。

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

railsでコメント投稿機能を非同期化する(jQueryなし)

開発環境

Mac OS Catalina 10.15.7
ruby 2.6系
rails 6.0系

前提

同期でのコメント投稿機能は実装済みとする
JavaScriptのフレームワークは使っていません

各テーブルとアソシエーションは以下の通り

users テーブル

Column Type Options
nickname string null: false
email string null: false, unique: true
encrypted_password string null: false

Association

  • has_many :posts
  • has_many :comments
  • has_many :likes

postsテーブル

Column Type Options
title string null: false
explanation text
category_id integer null: false
animal_name string
user references null: false, foreign_key: true

Association

  • belongs_to :user
  • has_many :comments

commentsテーブル

Column Type Options
user references null: false, foreign_key: true
post references null: false, foreign_key: true
content string null: false

Association

  • belongs_to :user
  • belongs_to :post

部分テンプレートに切り出す

まずは差し替えたい部分を切り出します。
自分の場合はコメント部分を切り出す事にしました。
切り出したものはapp/views/commentsのなかに配置しましょう。(commentsディレクトリが無ければ作ってください。)

_comment.html.erb
# このIDは削除する時に使うもので、コメント投稿の非同期化には関係ありません。
<p id="comment_<%= comment.id %>">
  <strong><%= link_to comment.user.nickname, user_path(comment.user.id) ,class: "comment-user"%>:</strong>
  <%= comment.content %>
  <% if user_signed_in? && current_user.id == comment.user.id %>
    <span><%= link_to '[削除する]', post_comment_path(post.id, comment.id), method: :delete, class: "comment-delete", remote: true %></span>
  <% end %>
</p>
show.html.erb
<div class="comment-container">
   <div class = "comment-box">
      <h2>気になった投稿にコメントしよう!</h2>
      <% if user_signed_in? %>
        <%= form_with(model: [@post, @comment], remote: true) do |form| %>
          <%= form.text_area :content, placeholder: "コメントする", rows: "2" %>
          <%= form.submit "コメントを送信する" %>
        <% end %>
      <% else %>
        <strong><p class = "alert">※※※ コメントの投稿には新規登録/ログインが必要です ※※※</p>
        </strong>
      <% end %>
      <div class="comments" id="comments">
        <h4><コメント一覧></h4>
        <% @comments.each do |comment| %>
          # この部分を切り出しました。
          <%= render "comments/comment", post: @post, comment: comment %>
        <% end %>
      </div>
    </div>
 </div>

ポイントは部分テンプレート内で使う変数を渡してあげる事です。

# この部分のこと
post: @post, comment: comment 

form_withをremote: trueにする

次にform_withのlocal: trueをremote: trueに変更します。

変更前

show.html.erb
<%= form_with(model: [@post, @comment], local: true) do |form| %>
  <%= form.text_area :content, placeholder: "コメントする", rows: "2" %>
  <%= form.submit "コメントを送信する" %>
<% end %>

変更後

show.html.erb
<%= form_with(model: [@post, @comment], remote: true) do |form| %>
  <%= form.text_area :content, placeholder: "コメントする", rows: "2" %>
  <%= form.submit "コメントを送信する" %>
<% end %>

これで、コントローラーのcreateアクションのビューの参照先が、create.js.erbに変わりました。(コントローラーはlocal: trueにするとhtml.erbのビューを、remote: trueにするとjs.erbのビューを探しに行きます。)

コントローラーのリダイレクトの記述を削除する

せっかくremote: trueにしてもリダイレクトしてしまうので、削除しましょう。

変更前

comments_controller.rb
class CommentsController < ApplicationController

  def create
    @comment = Comment.create(comment_params)
    redirect_to post_path(params[:post_id])
  end

  def destroy
    @comment = Comment.find(params[:id])
    @comment.destroy
    redirect_to post_path(params[:post_id])
  end

  private
  def comment_params
    params.require(:comment).permit(:content).merge(user_id: current_user.id, post_id: params[:post_id])
  end

end


変更後

comments_controller.rb
class CommentsController < ApplicationController

  def create
    @comment = Comment.create(comment_params)
  end

  def destroy
    @comment = Comment.find(params[:id])
    @comment.destroy
  end

  private
  def comment_params
    params.require(:comment).permit(:content).merge(user_id: current_user.id, post_id: params[:post_id])
  end

end

create.js.erbを編集する

commentsディレクトリにcreate.js.erbファイルを作り、以下のように編集します。

create.js.erb
var element = document.querySelector(".comments")
element.innerHTML += '<%= j(render partial: "comments/comment", locals: {post: @comment.post, comment: @comment}) %>'
document.querySelector("#comment_content").value = ""

コードを説明すると、まず1行目で、コメントの一覧表示がされるcommentsクラスを持つ要素を取得して、elementという変数に代入しています。(詳しくは上記のshow.html.erbを参照してください)

その後、変数elementにinnerHTMLを使い、切り出したcomment.html.erb(コメントの中身の部分)を追加しています。
また、_comment.html.erbでは変数commentと変数postが使われているので、commentsコントローラーで変数@commentに保存したコメントを代入する記述を書き(上記comments
controller.rb参照)、create.js.erbでlocalsオプションを使って、データを渡してあげましょう。

これでコメント投稿の非同期化は完成です。

しかし、このままだと、コメントの投稿フォームに、投稿したコメントが残ってしまうので、最後にコメントの中身を削除するために、投稿フォームをIDで取得して、空にする記述を書いています。

長くなりましたが、以上です。
参考になれば幸いです。

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

【Rails】Google Maps APIを利用して登録した住所をMap表示する

目標

アプリケーションで登録した住所を元にgoogle mapを表示させる機能実装についてまとめます。
入力フォームに入力した住所から、詳細ページにてマップを表示、ピンを立てて位置表示させるまで実装します。
1.gif

開発環境

  • Ruby: 2.6.4
  • Rails: 5.2.4
  • OS: macOS Catalina

前提条件

  • 既に、住所を登録するためのテーブルおよびカラムは作成済みであること
    私は、音楽スタジオの口コミレビューサイトを作成しました。デモでは、スタジオを新規作成し、その詳細ページにマップ表示されるといったものです。
    本記事で登場する変数では下記を使用します。それぞれご自身の環境に合わせて書き換えてください。
    テーブル名:studios
    カラム名:address

Google Mapの機能を使うための準備

こちらに関してはこの記事内では省略します。以下の三点が準備出来ている前提で進めていきます。
参考にさせていただいた記事のリンクも貼っておきます。
1. APIキーを取得する
2. Maps JavaScript APIの有効化
 ↪https://qiita.com/matsubishi5/items/196fa1941da2152b6d5d
3. Geocoding APIの有効化
 ↪https://qiita.com/matsubishi5/items/1b784dbbe5f1c336ac70

緯度、経度を登録するためのカラムを用意

緯度(latitude)、経度(longitude)のカラムを追加します。
db/migrate/...マイグレーションファイルへの記述でカラムを追加します。

class AddDetailsToStudios < ActiveRecord::Migration[5.2]
  def change
    #住所(address)カラムは登録済み
    add_column :studios, :latitude, :float
    add_column :studios, :longitude, :float
  end
end

緯度、経度はfloat型にします。floatとは浮動小数点数型です。
難しい名前ですがざっくり言えば小数を扱える型です。
緯度、経度は小数点以下を含む数値によって表されるので、こちらの型にします。

記述したらrails db:migrateしてください。

Gemを導入する

Gemfile
gem 'gon'
gem 'geocoder'

gem 'gon'
 ↪コントローラーで定義したインスタンス変数を、viewのjavascript内で使用できるようにする
gem 'geocoder'
↪住所から緯度、経度を算出する

マップは、緯度と経度の情報を元に表示します。
geocoderは、登録した住所から緯度、経度を自動で算出してくれるいいヤツです。

記述したらbundle installしてください。

モデルの編集

次に、geocoderを使うために適用するモデルに以下の記述をします。
今回はスタジオの位置情報を表示したいので/app/models/studio.rbに記述しました。

studio.rb
class Studio < ApplicationRecord
  geocoded_by :address #追記
  after_validation :geocode, if: :address_changed? #追記

これでaddressを登録した際にgeocoderが緯度、経度のカラムにも自動的に値を入れてくれるようになります。

geocorderの設定ファイルを作成し、編集

今の状態でも、登録した住所から緯度、経度を算出してくれますが、
東京都渋谷区といった、大まかな住所までしか算出してくれません。

東京都渋谷区◯-◯◯-◯◯◯のような細かい位置まで算出するためにはどうすればよいでしょうか?

ここで活躍するのが、Geocoding APIです。
geocoderでもGoogle Map APIの情報源を使えるように設定すれば解決します。

configフォルダ内にgeocoder.rbファイルを作成します。

$ bin/rails g geocoder:config

ターミナルで上のコマンドを行うことによってconfig/initializers/geocoder.rbファイルができます。

作成されたファイルの中身を変更してgeocoderでgoogle mapのAPIを使って緯度、経度を検索できるようにします。

geocoder.rb
Geocoder.configure(
  # Geocoding options
  # timeout: 3,                 # geocoding service timeout (secs)
   lookup: :google,         # name of geocoding service (symbol)
  # ip_lookup: :ipinfo_io,      # name of IP address geocoding service (symbol)
  # language: :en,              # ISO-639 language code
   use_https: true,           # use HTTPS for lookup requests? (if supported)
  # http_proxy: nil,            # HTTP proxy server (user:pass@host:port)
  # https_proxy: nil,           # HTTPS proxy server (user:pass@host:port)
   api_key: ENV['GMAP_API'],               # API key for geocoding service
  # cache: nil,                 # cache object (must respond to #[], #[]=, and #del)
  # cache_prefix: 'geocoder:',  # prefix (string) to use for all cache keys

  # Exceptions that should not be rescued by default
  # (if you want to implement custom error handling);
  # supports SocketError and Timeout::Error
  # always_raise: [],

  # Calculation options
  # units: :mi,                 # :km for kilometers or :mi for miles
  # distances: :linear          # :spherical or :linear
)

これでGoogle Map APIを用いてgeocoderの精度をあげることができます。
より詳細な場所の指定ができるようになりました。

api_key: ENV['GMAP_API'],
APIキーは、他人に知られてしまうと悪用される危険性があります。
そこで、dotenv-railsというGemを使用して、geocoder.rbに直接APIキーを記入しないことでこの危険性を回避できます。

今回は、GMAP_API(私が命名しただけ)に、実際のAPIキーを格納しています。
geocoder.rb内にENV['GMAP_API']と記入することで、APIキーを渡すことができます。

詳細は、こちらの記事を参考にしました。記事の途中に出てきます。
https://qiita.com/matsubishi5/items/196fa1941da2152b6d5d#%E5%AE%9F%E8%A3%85

コントローラーの編集

studios_controller.rbを編集

studios_controller.rb
  def show
    @studio = Studio.find(params[:id])
    gon.studio = @studio #追記
  end

ビューを編集

application.html.erbを編集

application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>StudioDig</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= include_gon %> #追記
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.9.0/css/all.css">
    <%= stylesheet_link_tag    'application', media: 'all' %>
    <%= javascript_include_tag 'application' %>

gonを読み込みます。
CSSとJavaScriptより先に読み込んでいる事に注意して下さい。

show.html.erbスタジオ詳細ページを編集

studios/show.html.erb
<div id="map"></div>


<script>
  let map;

  function initMap() {
    // geocoderを初期化
    geocoder = new google.maps.Geocoder()

    map = new google.maps.Map(document.getElementById('map'), {
      // コントローラーで定義した変数から緯度経度を呼び出し、マップの中心に表示
      center: {
        lat: gon.studio.latitude,
        lng: gon.studio.longitude
      },
      // マップの倍率はお好みで
      zoom: 17,
    });

    marker = new google.maps.Marker({
      // コントローラーで定義した変数から緯度経度を呼び出し、マーカーを立てる
      position: {
        lat: gon.studio.latitude,
        lng: gon.studio.longitude
      },
      map: map
    });
  }
</script>

<script async defer
      src="https://maps.googleapis.com/maps/api/js?v=3.exp&key=<%= ENV['GMAP_API'] %>&callback=initMap">
</script>

<script async defer....</script>
これを記述しないと、マップが表示されません。忘れずに記述してください。

<%= ENV['GMAP_API'] %>
環境変数化したAPIキーです。
ビュー内で使う場合は、上記のように書きます。

CSS等で、高さの設定をしていないとマップが表示されません。忘れずに設定しておいてください。

application.scss
...
#map {
  height: 500px;
  width: 100%;
}
...

ハマったエラー

CLOUD9で開発していたために起こったエラーです。

上記のように、間違いなく実装しているのになぜか、Geocoding APIを利用すると緯度、経度を算出しなくなるエラーにハマりました。

Geocoding APIを利用しない設定(gemのgeocoderのみで算出する設定)にすると、緯度、経度が算出できます。(かなり大まかな住所しか特定しませんが..)

これは、Google Maps API自体の問題だろうと調べてみたところ、
HTTPリファラーの設定が問題だったようです。

HTTPリファラーとは、自分が設定したURL以外からのアクセスが出来ないようにする設定です。セキュリティ対策ですね。
例えば、http://localhost:3000/のアクセスを許可する場合、リファラーにlocalhostと登録する。

ただ、CLOUD9はサーバーを再起動するごとに、IPアドレスが変わってしまうので
下手にリファラーを登録すると、Google Maps APIにアクセスができなくなります。

スクリーンショット 2021-01-27 22.08.51.png

こんな感じで、設定していましたが

スクリーンショット 2021-01-27 21.58.18.png

制限なしに変更しました。

すると、登録した住所から緯度、経度を算出できるようになりました。
セキュリティ的には、設定したほうがいいんでしょうけどね
今回は、動いたので良しとしました笑

同じ様なエラーにハマっている方の手助けになればと思います。

参考記事

【Rails】Google Mapの表示方法
【Rails】Geocoding APIを用いて高精度で緯度経度を算出し、Google Mapに表示する方法
Rails 登録した住所をGoogle Mapで表示させる
Rails5でGoogleMapを表示してみるまで

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

[Rails]Google Maps APIを利用して登録した住所をMap表示する

目標

アプリケーションで登録した住所を元にgoogle mapを表示させる機能実装についてまとめます。
入力フォームに入力した住所から、詳細ページにてマップを表示、ピンを立てて位置表示させるまで実装します。
1.gif

開発環境

  • Ruby: 2.6.4
  • Rails: 5.2.4
  • OS: macOS Catalina

前提条件

  • 既に、住所を登録するためのテーブルおよびカラムは作成済みであること
    私は、音楽スタジオの口コミレビューサイトを作成しました。デモでは、スタジオを新規作成し、その詳細ページにマップ表示されるといったものです。
    本記事で登場する変数では下記を使用します。それぞれご自身の環境に合わせて書き換えてください。
    テーブル名:studios
    カラム名:address

Google Mapの機能を使うための準備

こちらに関してはこの記事内では省略します。以下の三点が準備出来ている前提で進めていきます。
参考にさせていただいた記事のリンクも貼っておきます。
1. APIキーを取得する
2. Maps JavaScript APIの有効化
 ↪https://qiita.com/matsubishi5/items/196fa1941da2152b6d5d
3. Geocoding APIの有効化
 ↪https://qiita.com/matsubishi5/items/1b784dbbe5f1c336ac70

緯度、経度を登録するためのカラムを用意

緯度(latitude)、経度(longitude)のカラムを追加します。
db/migrate/...マイグレーションファイルへの記述でカラムを追加します。

class AddDetailsToStudios < ActiveRecord::Migration[5.2]
  def change
    #住所(address)カラムは登録済み
    add_column :studios, :latitude, :float
    add_column :studios, :longitude, :float
  end
end

緯度、経度はfloat型にします。floatとは浮動小数点数型です。
難しい名前ですがざっくり言えば小数を扱える型です。
緯度、経度は小数点以下を含む数値によって表されるので、こちらの型にします。

記述したらrails db:migrateしてください。

Gemを導入する

Gemfile
gem 'gon'
gem 'geocoder'

gem 'gon'
 ↪コントローラーで定義したインスタンス変数を、viewのjavascript内で使用できるようにする
gem 'geocoder'
↪住所から緯度、経度を算出する

マップは、緯度と経度の情報を元に表示します。
geocoderは、登録した住所から緯度、経度を自動で算出してくれるいいヤツです。

記述したらbundle installしてください。

モデルの編集

次に、geocoderを使うために適用するモデルに以下の記述をします。
今回はスタジオの位置情報を表示したいので/app/models/studio.rbに記述しました。

studio.rb
class Studio < ApplicationRecord
  geocoded_by :address #追記
  after_validation :geocode, if: :address_changed? #追記

これでaddressを登録した際にgeocoderが緯度、経度のカラムにも自動的に値を入れてくれるようになります。

geocorderの設定ファイルを作成し、編集

今の状態でも、登録した住所から緯度、経度を算出してくれますが、
東京都渋谷区といった、大まかな住所までしか算出してくれません。

東京都渋谷区◯-◯◯-◯◯◯のような細かい位置まで算出するためにはどうすればよいでしょうか?

ここで活躍するのが、Geocoding APIです。
geocoderでもGoogle Map APIの情報源を使えるように設定すれば解決します。

configフォルダ内にgeocoder.rbファイルを作成します。

$ bin/rails g geocoder:config

ターミナルで上のコマンドを行うことによってconfig/initializers/geocoder.rbファイルができます。

作成されたファイルの中身を変更してgeocoderでgoogle mapのAPIを使って緯度、経度を検索できるようにします。

geocoder.rb
Geocoder.configure(
  # Geocoding options
  # timeout: 3,                 # geocoding service timeout (secs)
   lookup: :google,         # name of geocoding service (symbol)
  # ip_lookup: :ipinfo_io,      # name of IP address geocoding service (symbol)
  # language: :en,              # ISO-639 language code
   use_https: true,           # use HTTPS for lookup requests? (if supported)
  # http_proxy: nil,            # HTTP proxy server (user:pass@host:port)
  # https_proxy: nil,           # HTTPS proxy server (user:pass@host:port)
   api_key: ENV['GMAP_API'],               # API key for geocoding service
  # cache: nil,                 # cache object (must respond to #[], #[]=, and #del)
  # cache_prefix: 'geocoder:',  # prefix (string) to use for all cache keys

  # Exceptions that should not be rescued by default
  # (if you want to implement custom error handling);
  # supports SocketError and Timeout::Error
  # always_raise: [],

  # Calculation options
  # units: :mi,                 # :km for kilometers or :mi for miles
  # distances: :linear          # :spherical or :linear
)

これでGoogle Map APIを用いてgeocoderの精度をあげることができます。
より詳細な場所の指定ができるようになりました。

api_key: ENV['GMAP_API'],
APIキーは、他人に知られてしまうと悪用される危険性があります。
そこで、dotenv-railsというGemを使用して、geocoder.rbに直接APIキーを記入しないことでこの危険性を回避できます。

今回は、GMAP_API(私が命名しただけ)に、実際のAPIキーを格納しています。
geocoder.rb内にENV['GMAP_API']と記入することで、APIキーを渡すことができます。

詳細は、こちらの記事を参考にしました。記事の途中に出てきます。
https://qiita.com/matsubishi5/items/196fa1941da2152b6d5d#%E5%AE%9F%E8%A3%85

コントローラーの編集

studios_controller.rbを編集

studios_controller.rb
  def show
    @studio = Studio.find(params[:id])
    gon.studio = @studio #追記
  end

ビューを編集

application.html.erbを編集

application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>StudioDig</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= include_gon %> #追記
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.9.0/css/all.css">
    <%= stylesheet_link_tag    'application', media: 'all' %>
    <%= javascript_include_tag 'application' %>

gonを読み込みます。
CSSとJavaScriptより先に読み込んでいる事に注意して下さい。

show.html.erbスタジオ詳細ページを編集

studios/show.html.erb
<div id="map"></div>


<script>
  let map;

  function initMap() {
    // geocoderを初期化
    geocoder = new google.maps.Geocoder()

    map = new google.maps.Map(document.getElementById('map'), {
      // コントローラーで定義した変数から緯度経度を呼び出し、マップの中心に表示
      center: {
        lat: gon.studio.latitude,
        lng: gon.studio.longitude
      },
      // マップの倍率はお好みで
      zoom: 17,
    });

    marker = new google.maps.Marker({
      // コントローラーで定義した変数から緯度経度を呼び出し、マーカーを立てる
      position: {
        lat: gon.studio.latitude,
        lng: gon.studio.longitude
      },
      map: map
    });
  }
</script>

<script async defer
      src="https://maps.googleapis.com/maps/api/js?v=3.exp&key=<%= ENV['GMAP_API'] %>&callback=initMap">
</script>

<script async defer....</script>
これを記述しないと、マップが表示されません。忘れずに記述してください。

<%= ENV['GMAP_API'] %>
環境変数化したAPIキーです。
ビュー内で使う場合は、上記のように書きます。

CSS等で、高さの設定をしていないとマップが表示されません。忘れずに設定しておいてください。

application.scss
...
#map {
  height: 500px;
  width: 100%;
}
...

ハマったエラー

CLOUD9で開発していたために起こったエラーです。

上記のように、間違いなく実装しているのになぜか、Geocoding APIを利用すると緯度、経度を算出しなくなるエラーにハマりました。

Geocoding APIを利用しない設定(gemのgeocoderのみで算出する設定)にすると、緯度、経度が算出できます。(かなり大まかな住所しか特定しませんが..)

これは、Google Maps API自体の問題だろうと調べてみたところ、
HTTPリファラーの設定が問題だったようです。

HTTPリファラーとは、自分が設定したURL以外からのアクセスが出来ないようにする設定です。セキュリティ対策ですね。
例えば、http://localhost:3000/のアクセスを許可する場合、リファラーにlocalhostと登録する。

ただ、CLOUD9はサーバーを再起動するごとに、IPアドレスが変わってしまうので
下手にリファラーを登録すると、Google Maps APIにアクセスができなくなります。

スクリーンショット 2021-01-27 22.08.51.png

こんな感じで、設定していましたが

スクリーンショット 2021-01-27 21.58.18.png

制限なしに変更しました。

すると、登録した住所から緯度、経度を算出できるようになりました。
セキュリティ的には、設定したほうがいいんでしょうけどね
今回は、動いたので良しとしました笑

同じ様なエラーにハマっている方の手助けになればと思います。

参考記事

【Rails】Google Mapの表示方法
【Rails】Geocoding APIを用いて高精度で緯度経度を算出し、Google Mapに表示する方法
Rails 登録した住所をGoogle Mapで表示させる
Rails5でGoogleMapを表示してみるまで

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

git push heroku masterでruby-2.6.5 on heroku-20とエラーが出た件

はじめに

今回はHerokuでのデプロイの際に以下のようなエラーが出たので解決策を記録しておこうと思います。

ターミナル
remote:  !
remote:  !     The Ruby version you are trying to install does not exist on this stack.
remote:  !     
remote:  !     You are trying to install ruby-2.6.5 on heroku-20.
remote:  !     
remote:  !     Ruby ruby-2.6.5 is present on the following stacks:
remote:  !     
remote:  !     - cedar-14
remote:  !     - heroku-16
remote:  !     - heroku-18
remote:  !     
remote:  !     Heroku recommends you use the latest supported Ruby version listed here:
remote:  !     https://devcenter.heroku.com/articles/ruby-support#supported-runtimes
remote:  !     
remote:  !     For more information on syntax for declaring a Ruby version see:
remote:  !     https://devcenter.heroku.com/articles/ruby-versions
remote:  !

開発環境

  • Ruby 2.6.5
  • Bundler 2.1.4
  • Rails 6.0.0
  • MySQL 5.6.50

解決方法

結論から言うとHerokuのstackruby 2.6.5に対応していないのが原因です。
今回の場合だと、stack-20が対応していないので、stackのバージョンを下げることで解決できます

エラー分の真ん中あたりにも
heroku-16
heroku-18などと提案してくれているので、stack-18にバージョンを下げます。

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

ターミナル
% heroku stack:set heroku-18 -a アプリ名
# 以下のように出力されれば成功
Setting stack to heroku-18... done

次にheroku apps:infoコマンドでバージョンを確認してみましょう。

ターミナル
% hroku apps:info
=== my_app
Addons:         cleardb:ignite
Auto Cert Mgmt: false
Dynos:          
Git URL:        https://git.heroku.com/my_app.git
Owner:          メールアドレス
Region:         us
Repo Size:      0 B
Slug Size:      0 B
Stack:          heroku-18
Web URL:        https://my_app.herokuapp.com/

stackがheroku-18になっていることを確認しましょう。

ここまで来たら、Commit履歴を残しておくといいでしょう。

ターミナル
% git commit --allow-empty -m "Downgrading to heroku-18"

ターミナルで以上のコマンドを実行するとGitにCommit履歴が残っているので、pushしておきましょう。

ここまで来たらHerokuにpushしてデプロイしましょう。
git push heroku masterをターミナルで実行し、デプロイをしましょう。

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

RailsのActiveModelを使って検索機能を実装していく

検索機能を実装する

検索フォームを実装していきます。

まずは方針を決めます

下のような手順で進めていきたいと思います。

①activemodelの機能を使って、SearchFormクラスを作成します。

②ルーティング設定。postルーティングにsearchを追加する。

③post、comment、usernameをSQLから検索するメソッドをpostモデル内につくる

④SearchFormクラス内の実装をしていく

⑤コントローラーで挙動を設定していく

⑥検索した時のviewの実装

SearchFormクラスの作成

まず「検索する」ことにおいて、何を検索するか?という事を考えます。今回は、投稿、コメント、ユーザーの名前が検索できるようなフォームを実装したいと考えています。そして、既存の各テーブルからデータを探すことを想定しています。(posts、comments、usersのテーブルがすでに存在している状態)なので、新しいテーブルは必要としません。

それでも、検索するワードのパラメータを送受信する方法として、form_withなどのActiveRecordの機能が使える方が便利です。そういう場合、データベースを作成しなくてもActiveRecordの機能が使える、ActiveModelの機能を導入します。

SearchFormクラスのためのファイルを生成し、コードを書いていきます!

  $touch app/forms/search_form.rb
app/forms/search_form.rb
   class SearchForm
     include ActiveModel::Model
     include ActiveModel::Attributes

     attribute :post_content, :string
     attribute :comment_content, :string
     attribute :name, :string
   end

解説していきます。

  include ActiveModel::Model

ActiveModelのModelモジュールを導入しています。これで、ActiveRecordの機能が使えるようになります。

  include ActiveModel::Attributes

ActiveModelのAttributesモジュールを導入すると、attributeメソッドが使えるようになります。attributeメソッドは、属性名と型を定義することができるメソッドです。今回は、post_content、comment_content、nameをstring型の属性として定義しています。

ルーティングの設定

posts_controllerのsearchアクションへ接続するためのルーティングを設定していきます。

config/routes.rb
    Rails.application.routes.draw do
      resources :posts, shallow: true do
        collection do
          get :search
        end
      end
    end

collectionを使ってsearchアクションを追加しています。


collectionとmemberの違い

ルーティング設定において、アクションを追加する方法としてcollectionとmemberがあります。これら違いは、collectionは全てのデータを対象としていて、memberは特定のデータを対象としているという点です。なのでmemberで追加した場合、リクエストに対してidパラメータを指定しなければいけません。今回の検索においては、データ全体から探すのでcollectionを使用しました。

ルーティングを調べると以下のように表示されると思います。

    #memberを使用した場合
    GET    'posts/:id/search'      => 'posts#search'
    #collectionを使用した場合
    GET    'posts/search'      => 'posts#search'

検索するメソッドを作成

postモデル内に、投稿、コメント、ユーザーの名前をSQLから検索するメソッドを作成します。

app/models/post.rb
    scope :post_like, -> (post_content) { where('content LIKE ?', "%#{post_content}%")  }
    scope :user_like, -> (name) { joins(:user).where('name LIKE ?', "%#{name}%") }
    scope :comment_like, -> (comment_content) { joins(:comments).where('comments.content LIKE ?', "%#{comment_content}%") }

解説していきます。

   scope :post_like, -> (post_content) { where('content LIKE ?', "%#{post_content}%")  }  

scopeで定義していきます。クラスメソッドでも出来ますが、1行でコードが綺麗に書けるので今回はscopeを使います。whereの第一引数は、Postsテーブルのcontentカラムに対してLIKE句を使い検索クエリを定義しています。第二引数に、ワイルドカード(%)を用いてpost_contentを記述します。そうすることで、post_contentに入った文字を0文字以上で一致したものを曖昧検索することができるようになります。

あとの二つも基本的に挙動は同じです。Postモデルなので、joinメソッドでuserテーブル、commentテーブルを連結することを忘れないでください。

SearchFormクラスの実装

SearchFormクラスを完成させていきます。

app/forms/search_form.rb
    class SearchForm
        def search
            scope = Post.distinct
            scope = split_post_content.map{ |word| scope.post_like(word)}.inject{
            |result, scp| result.or(scp) } if post_content.present?
            scope = scope.comment_like(comment_content) if comment_content.present?
            scope = scope.user_like(name) if name.present?
            scope
        end

        private

        def split_post_content
            post_content.strip.split(/[[:blank:]]+/)
        end
    end

解説していきます。

   post_content.strip.split(/[[:blank:]]+/)

searchメソッドで後ほど使うので、先にsplit_post_contentメソッドの挙動を解説します。
stripでpost_contentに入った文字の先頭と末尾の空白文字を全て取り除いてくれます。そして、splitで文中で空白文字がある場合は、要素に分けられ配列に格納されます。例えば、「ドラゴン ボール」と検索した場合、[ドラゴン、ボール ]という二つの要素として配列に格納されるということです。では、空白文字はどう判断しているでしょう?それは、splitの引数に正規表現を使っているからです。

POSIX文字クラスって何?

今回、空白文字を判断する正規表現にPOSIX文字クラスというものを使っています。[::]という表現方法が、POSIXブラケットと呼ばれます。これは文字集合を表すためのようなものらしいです。[:blank:]では、スペースとタブの空白文字にマッチします。[:alnum:]は、英数字にマッチします。余談ですが、元々英語圏でのみ想定して作られたものがunicodeによる拡張のおかげで日本語も構成文字として拡張されたみたいです。


searchメソッドを解説していきます。

  scope = Post.distinct

distinctによって、postの重複レコードを一つにまとめてくれます。これによって、各データに一意性を持たせることができます。

  scope = split_post_content.map{ |word| scope.post_like(word)}.inject{ |result, scp| result.or(scp) } if post_content.present?

先ほどのsprit_post_contentメソッドによって、検索文字の要素が何個か配列に格納されている状態です。その配列は、mapメソッドによりブロック内のwordにひとつずつ代入されます。postモデルで作成したpost_likeメソッドをつかって引数のwordに入った文字を先ほどのscopeに代入されたpostのデータから検索しています。検索でマッチしたデータがmapメソッドによって配列に再度格納されます。この時、分けた検索条件ごとにデータも分かれているはずです。なので、injectを使って、再度格納された配列をひとつずつresultとscpに代入していき、orメソッドで分かれた条件を一つのデータ一覧としてまとめます。

文章で説明するとなかなかにくどくなってしまいましたので、大まかには下の挙動のようなことをしています。

    Post
    .where(content: 'ドラゴン')
    .or(Post.where(content: 'ボール'))

    SELECT "posts".*
    FROM "posts"
    WHERE ("posts"."content" = 'ドラゴン' OR "posts"."content" = 'ボール')

Image from Gyazo

    scope = scope.comment_like(comment_content) if comment_content.present?
    scope = scope.user_like(name) if name.present?

コメント、ユーザーの名前もpostモデルで定義したSQL検索用のメソッドを用いています。先ほどpost検索のなかで説明したことと同じなので割愛します。

これでSearchFormクラスは完成です。

controllerの設定

検索フォームはヘッダーに実装するので、どのページに遷移しても使える状態にしなくてはいけません。なので、今回はapplication_controllerでSearchFormのインスタンスを生成します。また検索フォームで入力されたparamsを受け取るためのメソッドも作成します。

app/controllers/application_conntroller.rb
    class ApplicationController < ActionController::Base
        before_action :set_search_posts_form

        def set_search_posts_form
            @search_form = SearchForm.new(search_params)
        end

        def search_params
            params.fetch(:search, {}).permit(:post_content, :comment_content, :name)
        end   
    end

解説していきます。

  params.fetch(:search, {}).permit(:post_content, :comment_content, :name)

ここでは、paramsに対してfetchメソッドを使用しています。paramsに:searchキーがない場合は、{}がデフォルト値として評価されるのでActionController::ParameterMissingのエラーが起きないようになっています。

  before_action :set_search_posts_form

これによって、set_search_posts_formメソッドがどのページに遷移しても働くので、どのページでもヘッダーから検索できるようになります。

さらに、post_controllerで実装していきます。

app/contoroller/posts_controller.rb
    class PostsController < ApplicationController
        def search
            @posts = @search_form.search.includes(:user).page(params[:page])
        end  
    end

application_controllerで設定したparamsの入った@search_formを今度はposts_controllerのsearchアクション内で、SearchFormクラスで定義したsearchメソッドを使用し、検索する。そして、pageメソッドで検索で取得したページネーション対応の全データを取得。includeは、N+1問題をが起きないように記載。この@postsは、後ほど実装する検索した後のpost/searchのviewページとしてのpostのデータとなる。

viewの実装

検索フォームを作成します。

app/views/layouts/_hedder.html.slim
    = render "posts/search", search_form: @search_form
app/views/posts/_search.html.slim
    = form_with(model: search_form, scope: :search, url: search_posts_path, method: :get,
                class: "form-inline my-2 my-lg-0 mr-auto", local: true) do |f|
        = f.text_field :post_content, class: "form-control mr-sm-2", placeholder: "本文"
        = f.text_field :comment_content, class: "form-control mr-sm-2", placeholder: "コメント"
        = f.text_field :name, class: "form-control mr-sm-2", placeholder: "ユーザー名"
        = f.submit 'SEARCH', class: "btn btn-outline-success my-2 my-sm-0"

解説します。

    = form_with(model: search_form, scope: :search, url: search_posts_path, method: :get,
                class: "form-inline my-2 my-lg-0 mr-auto", local: true) do |f|

modelオプションでserch_formのインスタンスを設定、scopeオプションを使うことによってそれぞれの値は、params[:search]というパラメータに格納されます。search_posts_pathをurl指定することによって、post_controllerのsearchアクションに飛びます。methodオプションでhttpアクションをgetに指定します。デフォルトはpostとなっています。

最後に、検索した後のページを作成します。

app/views/posts/search.html.slim
    .container
        .row
            .col-md-8.col-12.offset-md-2
                h2.text-center
                    | 検索結果: #{@posts.total_count}= render @posts
                = paginate @posts

これで検索機能が完成するはずです!

参照

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

【銀座Rails#26 LT登壇】江上「 大量データでもサクサク動くRailsになるために」

【銀座Railsとは?】

リンクアンドモチベーションが支援をする技術コミュニティです。リンクアンドモチベーションでは、技術コミュニティの支援を通して、エンジニア同士のコミュニティ形成や活性化、Railsについての知識を交換することでエンジニアの技術の進歩に貢献したいと考えています。

今回は銀座Rails#26 に登壇をした弊社テックリード 江上のLTをレポートします!

リンクアンドモチベーションのテックリード 江上について

2018年12月にベンチャー企業からリンクアンドモチベーションに転職。現在はテックリード兼エンジニアリングマネージャーとして活躍中。最近の趣味は、料理。新型ウィルスの影響で自宅で過ごす時間が増えたことをきっかけに、各国の料理に挑戦中。稀にピザがうまく膨らまなかったりと苦戦している。

? 江上の関連記事
キャリアのスタートは、大手グルメサイトの「食べログ」。フルスタック、テックリードを経験してきた江上がLMの開発組織を語る

1.モチベーションクラウドによる大手顧客対応

リンクアンドモチベーションでは、これまでの組織人事コンサルティングのノウハウをもとに開発した、「モチベーションクラウド」というサービスを提供しています。

「モチベーションクラウド」は、6,620社、157万人のデータベースから組織状態を診断し、組織改善に活用できる国内初の組織改善クラウドです。こちらのサービスのエンタープライズ顧客対応において、Railsアプリの性能改善を実施しましたのでレポートします。

今回は、主にエンタープライズ顧客対応の概要と、銀座Railsという場なので、その際にRailsの書き方の工夫で対応した箇所をみなさんにご紹介したいと思います。

私たちは、主に3つの施策を試みました。まず初めに行なったのは「UXの変更」です。これまでベンチャー企業のお客様を中心に展開していたことから、お客様から求められるポイントとして「簡単に使える」「見た目がかっこいい」という点がありました。しかし、お客様が大企業や公的機関のようなエンタープライズになると、デザイン性よりも操作ミスが起きにくいという点が重視されるようになりました。そのため操作ミスが起こりづらいデザインや機能の開発に重点を置いてプロダクトを改修していきました。

次に「セキュリティレベルの向上」に取り組みました。ベンチャー企業を対象にした時、セキュリティとして問題がなければ導入を決定してくれる企業も多かったのですが、エンタープライズなどの大手企業様の基準では、Excelファイル数十枚におよぶセキュリティチェックシートが用意されています。それらの項目に全てチェックが入らないと導入ができない場合が多いので、大手企業様の基準に沿った、ハイレベルなセキュリティを実現させる必要がありました。

最後は「パフォーマンス向上への取り組み」です。これまでは、一企業あたり数百人規模のユーザーが利用することを目安に開発をしてきましたが、数万人のユーザーが利用してもスムーズに動くことが必須のため、早急にパフォーマンスの向上を図る必要がありました。

▼お話しした内容の詳細はQiitaにまとめましたのでそちらをご参照ください!
SaaSでRails使うなら知らないとまずい!!Railsで一括処理するときに遅いコードと対策5選

パフォーマンスの向上と言っても、その課題は様々です。アーキテクチャの変更などで対応したケースもありますが、今回はRailsのコード部分である「非効率な書き方」についてお話しします。

2.パフォーマンス改善を実際に試行しての所感と今後の課題感

今回、パフォーマンス改善にかけた期間はおよそ9ヶ月間ですが、数千人が使用すると遅いと感じるアプリケーションだった状態から、5万人の顧客までスムーズに処理できるように改善することができました。

APIに関しては、改善前は11個ほど動きませんでしたが、改修後はそれらが問題なく動くようになりました。また、改修後のAPIは平均で20倍ほど速くなりました。

また、非動機のバッチは3つ動かないものがありましたが、それも無事に可動し改修後のバッチは平均で11倍速ぐらいになりました。それぞれのパフォーマンスをさらに局所的に見てみると、100倍速くなったというものもいくつかありました。

9ヶ月間のパフォーマンス改善で感じたことは、結局コードの部分だけで解決できる問題は少ないということでした。「Railsのコードを変える」ということ以外にも、仕様や設計を変えるなどの対応が必要になるといった課題が浮き彫りになりました。

例えば、性能を改善する前はN+1の問題が大量に出ていたので、それが原因だと思い改修のための工数を甘めに見積もっていたところがありました。しかし、結果的にはDB設計の変更をしなければならないケースが3割ほどあり、全体の工数としての5割以上を占めていたため、目に見えるものだけが、全てではないなと感じました。

こうした経験を踏まえても、コードだけで解決できるケースは稀なのかなと今では思っています。また、設計を変えるよりも、仕様を変える方が大変だと感じています。SaaSということもあり、仕様変更をするとなると、各所への連携やお客さんへの伝達が多くなるので、その点はかなり大変でした。開発初期からpagingをしっかり設計するといったような、想定できることは早期に対応することがとても大事だと実感しています。

さて、最後になりますが、モチベーションクラウドは日々アップデート中です。現在、1万人以上規模の大企業のお客様への導入が加速しています。ベンチャー企業を中心にサービスを展開してきましたが、エンタープライズ領域に進出しておりプロダクトの成長フェーズです。

3.最後に「私たちは最近こんなことやっています!」

・パフォーマンス劣化が起こらないようにdetadogでRUMでの監視
・pdfの生成処理をlambdaに移設
・マイクロサービス化したりなどデータレイク構築
・非同期処理をバッチサーバーからworkerに移行
・アーキテクチャの見直し
・ElasticSeachの使用やデータマート構築の検討

少しでもご興味がありましたら、ぜひカジュアルに情報交換をさせてください!

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

英単語しりとりプログラムをRubyで書いてみた

練習問題- プログラミングスレまとめ in VIPにある英単語しりとりプログラミングを書いてみました
仕様は問題文にあるものを元にちょっと変えてます

全力を投じた物なので、コード本体や読みやすさなど、気になる所があればご指摘お願いします!!!!!!

ディレクトリ構造

word_chain_game
├── lib
│   ├── dictionary.txt
│   ├── main_proces.rb
│   └── word_proces.rb
└── word_chain_game.rb

コードたち

word_cahin_game.rb

word_cahin_game.rb
require_relative('./lib/main_proces')

first_person, first_char = bigin_game
loop_game(first_person, first_char)

main_proces.rb

main_proces.rb
require_relative('word_proces.rb')

# ゲーム進行処理
def bigin_game
  first_person = ["あなた", "わたし"].sample
  a_z_out_x_z  = ("a".."y").to_a - ["x"]
  first_char   = a_z_out_x_z.sample

  puts "me: 英単語しりとりゲームを始めます。"
  puts "me: 同じ単語を使うか空行を入力するとあなたの負けです"
  sleep 1

  puts "me: 先行は#{first_person}です"
  puts "me: 最初の文字は#{first_char}です" if first_person == "あなた"

  return first_person, first_char
end

def loop_game(first_person, word)
  # ループで戻ってくるか先手がわたしの時computerの番になる
  loop.with_index do |_, i|
    sleep 1
    word = computer_main(word) if i != 0 || first_person == "わたし"

    word = user_main(word)
  end
end

def exit_game(luser, last_word = nil)
  case luser
  when"computer"
    puts "me: まいりました!あなたの勝ちです"
  when "user"
    unless (is_retire = last_word == nil)
      puts "me: その言葉は#{last_word[0]}回目に#{last_word[1]}が使用しています"
    end

    puts "me: わたしの勝ちです!"
  when "draw"
    puts "me: 申し訳ありません、システム内辞書が尽きたのでこれ以上ゲームを続けられません"
    puts "me: 引き分けです"
  end

  sleep 1
  puts "me: 今回のしりとりでは#{$used_words.size}個の単語を使用しました"
  exit
end


# computerとuserのメイン処理
def computer_main(word)
  judge_whether_computer_loses(word)

  selected_word = select_computer_word(word)
  puts "me: #{selected_word}"

  tarn_pass("わたし", selected_word)
  selected_word
end

def user_main(word)
  print "you: "
  input_word = gets.chomp

  judge_whether_user_loses(word, input_word)
  judge_whether_word_is_suitable(word, input_word)

  tarn_pass("あなた", input_word)
  input_word
rescue RuntimeError
  puts "!! error: その単語は登録されていないか、しりとりになっていません !!"
  puts "!! error: 他の単語を入力してください !!"
  retry
end

word_proces.rb

word_proces.rb
$user_dictionary     =
  File.open(File.expand_path('../dictionary.txt', __FILE__)) do |f|
    f.read.split("\n").group_by { |x| x.chr.downcase }
  end

# computerの辞書は各アルファベットにつきランダムで8個
$computer_dictionary =
  $user_dictionary.each_with_object({}) do |(k, v), ary|
    ary[k] = v.sample(8)
  end

$used_words = {}
$game_count = 1

# computerの単語処理
def judge_whether_computer_loses(word)
  if ($computer_dictionary[word[-1]] - $used_words.keys) == []
    exit_game("computer")
  end
end

def select_computer_word(word)
  $computer_dictionary[word[-1]].shift
end


# userの単語処理
# 以前使った単語か空行だと負け、辞書がなくなってたら引き分け
def judge_whether_user_loses(compter_word, user_word)
  if $used_words.key?(user_word) || user_word == ""
    exit_game("user", $used_words[user_word])

  elsif ($user_dictionary[compter_word[-1]] - $used_words.keys) == []
    exit_game("draw")
  end
end

def judge_whether_word_is_suitable(computer_word, user_word)
  dictionary_has_user_word =
    $user_dictionary[computer_word[-1]].find { |x| x.downcase == user_word.downcase }

  raise unless dictionary_has_user_word
end


# 全体の単語処理
def tarn_pass(passer, word)
  $used_words[word] = [$game_count, passer]
  $game_count += 1
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsバージョンアップ備忘録 5.1→5.2編

初めまして。株式会社iCAREのサーバーサイドエンジニアのyotubaです。
前回の記事ではRails5.0から5.1へのバージョンアップについて書きましたが、無事にリリースが出来ました。
今回の記事では、Railsの5.2へのバージョンアップについて備忘録を書きたいと思います。

現在弊社のCarelyというサービスは、長いこと、Railsの5.0.7.2で稼働していて、現在はRails5.1.7まで上がりましたが、今回5.2系へバージョンアップを弊社技術顧問のwillnetさんと連携してやらせて頂いています。

今回の記事では、5.2系へバージョンアップを現在しようとしている際にぶつかった問題を共有させて頂きたいと思います。

5.2系へ上げた際に落ちたテストからわかる変更点

バージョンアップする際には、まず、こちらのRailsガイドのリリースノートに書いてあるものなので、そちらを参考にして頂くのが一番良いと思います。

まず

Railsのバージョンをあげる前に関連するgemをあげるべきだと色々な記事で書かれていますが、これを身をもって知ることになりました。
なんとbundle update railsでバージョンをあげようとすると、gemの依存関係にひっかかってしまって上がらないのです!(数年アップデートが止まってしまっていました・・・)
具体的には様々なgemの依存関係が
(3.0 < hoge)
とか
(5.1 > hoge)
のようにエラー文として吐き出されます。

Rails以外のgemを全て上げていきたいところですが、量が膨大なこともあり、上のエラー文を読み解いて必要なgemを上げていきます。

具体的には(5.1 > hoge)となっているようなgemはrailsや関連gemの5.1以下を指定していますので、アップデートする必要があります。

つぎに

またgemの話になります。上記を解決してバージョンアップしたPRを作ることができました。
ただ、gemは大体の場合、RubyやRailsのどのバージョンをサポートしているかが書いてあります。
新しいRubyやRailsが出ると、gemを作ってくれてる人たちが新しいRubyやRailsで動くように修正してくれるわけですね。(更新が止まってしまっているgemを使わない方がいいというのは、こういった対応がされなくなるためでもあります)

gemのバージョンが古いままだと、Rails5.2をサポートしていなかったりします。gemのバージョンで何を変えたかはchangelogというファイルに書いてあり、そこにsupport rails 5.2などと書いてある場合は、それ未満のバージョンのままrails5.2にあげると問題が起きます。(起きたから修正したバージョンが出たわけです)

なので、bundle outdatedなどのコマンドで古くなっているgemを確認し、1個づつ上げていく必要があります。
これが非常に大変です。。。

具体的にはRansackPublicActivityなどがひっかかりました。

本題

gemの話が続いてしまったので、Railsのバージョンアップにより動かなくなってしまったコードについて解説します。

ActiveModel::Dirtyのchanged?の挙動

DEPRECATION WARNING: The behavior of `attribute_changed?` inside of after callbacks will be changing in the next version of Rails. 

というDEPRECATION WARNINGが5.1で出ます。
これは何かというと、

class Model < AR::Base
  after_save :callback

  def callback
    p changed?
  end
end

model = Model.new
model.body = '変更した'
model.save
#=> trueが出力される
class Model < AR::Base
  def callback
    changed?
  end
end

model = Model.new
model.body = '変更した'
model.save
p model.callback #=> falseが出力される

上記二つのコードが5.1までは別の挙動をしていましたが、わかりづらいということで、5.2で両者の挙動は後者の挙動で統一されました。

こちらの記事にまとめて頂いている方がいますが、5.1の挙動を維持できるメソッドへ変更して解決しました。。

callbackの際にfalseの場合、callbackチェーンを終了させない設定

callback処理でfalseをreturnしても、callback chainを終了させない設定を行うinitializerが、こちらもdeprecatedになり、削除して対応しました。

こちらのブログに書いて頂いている方がいますので確認ください。

5.2からは、callback chainを終了させたい場合は、明示的にthrow :abortを使う必要があります。

モデルのclass_nameに渡すのをStringにする

このようなエラーが出ました。

A class was passed to `:class_name` but we are expecting a string. (ArgumentError)

こちらでマージされたものですが、単純な話で、モデルのclass_nameにStringを渡す必要があるので修正しました。

モデルのCallbackの際に、if/unlessにStringを渡せなくなった。

このようなエラーが出ました。

ArgumentError: Passing string to be evaluated in :if and :unless conditional options is not supported. Pass a symbol for an instance method, or a lambda, proc or block, instead.

☓ if: 'first_name.blank?'
○ if: proc { |s| s.first_name.blank? }

Procで書くやり方に修正しました。

終わりに

今回もいかがでしたでしょうか。
実際に動いているプロダクトで起きた問題に絞って記事を書かせてもらいました。
長年gemのアップデートを放置していると大変なことになるというのが教訓です。。。みなさん気をつけてくださいね!

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

assetsディレクトリの役割

前提

rubyのバージョン  ruby 2.6.5
Railsのバージョン Rails 6.0.0

結論(内容)

複数のディレクトリやファイルに分かれたassetsディレクトリ内のファイルを1つに連結圧縮する機能

image.png

Ruby on Rails内で使用するファイル、CSSや(Javascript)、画像ファイルを開発作業がしやすいようにファイル分割してコーディングができるようにしつつ、最終的に1つのファイルに連結・圧縮する仕組み

assets内では内容の種類ごとにディレクトリ、ファイルに分割されている理由は開発者にとって何がどこに記載されているか認識させるため

※(JavaScript)
Rails5前とRails6を境にjavascriptのディレクトリの位置が変更

Rails6 app直下
Rails5 assets直下

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

学び直し rubyがミニツク編 Part2

今日の教科書
Rubyがミニツク基礎知識

基礎知識

メモ

変数

  • 変数は宣言なしでも定義できる
  • ローカル変数は小文字のアルファベットか"_"が最初に来る

ifによる分岐

if <条件式> then
  <文>
end

elsifはそれまでの<条件式>の結果が偽であり、そのelsifに指定された<条件式>の結果が真の時に処理を実行します。elseはすべての<条件式>が偽の時に処理を実行します。

  • Rubyにおける真の値は、「false」と「nil」以外のすべてになる、「0」は真
  • <条件式>がfalseやnilの時は<文>は実行されない
  • 他のプログラミング言語と違い、elsifと書く
  • elsifやelseはなくても構わない。また、ifのthenは省略して書くこともできる
  • ifは入れ子にすることもできる

疑似変数:https://docs.ruby-lang.org/ja/latest/doc/spec=2fvariables.html#pseudo

whileでの繰り返し

while <条件式> do
  <文>
end

メソッド定義

def <メソッド名>
  <文>
end
  • <メソッド名>には小文字のアルファベットと"_"が使える,メソッド名だけで呼び出せる
  • return文でメソッドの実行を中断して途中で戻り値を返す
def example
  val = 1
  while val < 10
    if val == 5
      puts("valが5になった")
    end
    val += 1
  end
end

コマンドラインオプションでの文字コード指定
・プログラムテキストで使っている文字コードとRubyが解釈しようとしている文字コードが対応していないことがある

  • UTF-8のときは-Ku
  • Shift_JISのときは-Ks
  • EUC-JPのときは-Ke
  • NONE(ASCII)のときは-Kn 無指定時はこちら

演算子・計算順序

  • ==演算子は比較しているオブジェクトが同じであるかを判定してから、結果をtrueもしくはfalseで返す
  • 文字列オブジェクトの「*」演算子は数値の引数を取ることができる。*演算子を使用すると、文字列オブジェクトの内容を数値オブジェクトの回数だけ繰り返した新しい文字列オブジェクトを作成して返す
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptのディレクトリ packsとchannelsの役割

前提

rubyのバージョン ruby-2.6.5
Railsのバージョン Rails:6.0.0

結論(内容)

packs
js全体に指示をかけるディレクトリ
読んで字のごとくパッケージのように全体に指示

channels
特定のjsファイルにおける指示
細かい指示

image.png

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

【Ruby初学者】日付/曜日をたった4行で出す方法

某スクールのRuby問題を解いた備忘録。

test.rb
now = Time.new
puts "現在は西暦#{now.year}#{now.month}#{now.day}日"
week = ["日","月","火","水","木","金","土"]
puts week[now.wday] + '曜日です' 

と、記述しLinuxで実行すると

test.rb
% ruby test.rb
現在は西暦2021128
木曜日です

となる。

ちなみに、railsのアプリケーションで表示するときには、
コントーラーファイルで、インスタンス変数を定義して、

controller.rb
def index
   wd = ["日", "月", "火", "水", "木", "金", "土"]
   time = Time.now
   @date = time.strftime("%Y/%m/%d(#{wd[time.wday]})")
   ampm = time.hour < 12 ? "AM" : "PM"
   @time =time.strftime("#{ampm} %I:%M")
end

ビューファイルに、コードを埋め込む。

view.rb
  <div class = "date"><%= @date %></div>
  <div class = "time"><%= @time %></div>

ブラウザで表示されるのは、こんな感じ。

Qiita

参考記事

[Ruby入門] 14. 日付と時刻を扱う(全パターン網羅)/@prgseekさま
https://qiita.com/prgseek/items/c0fc2ffc8e1736348486

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

「is not defined」 jQuery導入時のエラーの解消例

前提

・jQueryをインストールしていること
(インストールついての詳細はjQueryの公式ページにて確認。)

・筆者が使用しているバージョンの詳細
jQueryのバージョン "jquery": "^3.5.1"
rubyのバージョン ruby-2.6.5
Railsのバージョン Rails:6.0.0

・表題のエラーが起きる理由はケースバイケースで、今回のケースはあくまでも例の1つ。

「is not defined」のエラー例とその解消の詳細がわかりやすく書いてあるサイト↓
https://for-someone.com/blog/4792/

結論

自身の記述内容のjQueryのバージョンと、インストールしていたjQueryのバージョンに差異が、「is not defined」のエラーの原因。
→インストールしたバージョンのjQueryのscriptをviewのhead内にコピペで置き換えることでエラー解消できる。

エラー画面

image.png

エラーが起こるまでの経緯

フォロー機能を実装するにあたり、jQueryを使用するため公式ページに沿ってjQueryをインストールした。

コーディング完了し、挙動確認する際、フォーローのカウントが実装できていることを確認

しかし、フォローボタンをクリックするタイミングで色が変色するよう発火の記述をしていたが変色しない。

ブラウザを更新するとボタンの色が変色する

コンソールを開き、挙動確認すると、「is not defined」のエラーが起こる

image.png
↓(ブラウザ更新しないと変色できない)
image.png

エラー解消方法

①jQueryのバージョンの確認はpackage.jsonで確認
image.png

image.png

②「jQueryのバージョン script」ブラウザ上で検索

③サイト内の適したjQueryのバージョンのscriptをコピー

④自身のviewのhead内にペースト
(筆者はapp/view/layouts/application.html.erbのhead内に記述)

⑤挙動確認→OK

まとめ

インストールしたバージョンのjQueryのscriptをviewのhead内にコピペで置き換えることでエラー解消

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

absメソッドは、どんな時に使うのか。

某スクールのRuby問題を解いた備忘録。
(自身が情弱知識なので、備忘録は小学生もわかるように書いてみた。)

プログラムを作っているときは、どのくらいの「差」があるのかを数で出したい時がある。
(数で出さなきゃいけない)
例…
⚫︎「車に乗れる人数」と「乗りたい人の数」 → みんな乗れる?乗れない?
⚫︎「今持ってるお金」と「必要なお金」 → お金足りる?足りない?
⚫︎「入院したい人」と「入院できる人の数」 → 入院できる?できない?

プログラムに組んで、簡単に教えてくれる便利な機能をつけたいな。

そんな時、「absメソッド」を使う。

qiita.rb
ty(car, capacity)
  capacity = car[:capacity] - capacity

  if capacity == 0
    puts "みんな乗れます。"
  elsif capacity > 0
    puts "あと#{capacity}人乗れます。"
  else
    puts "#{capacity.abs}人乗れません。"
  end
end

こんな風に書いた時に、
absメソッドは、負の数の場合は、符号を取って正の数にした数値が取得できます。
たとえば、

5 -7 = -2

になりますが、absメソッドを用いると符号が取られ
「2」に変換されます。

まとめ

0より小さい数になった数を見せたいんだけど、マイナスの記号はいらないから、マイナスを取った数を表してくれるメソッド。
大人の言い方で、「絶対値を求める」メソッド。

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

ActiveRecordのメソッド

ポートフォリオにてログイン機能を実装している。
その中でActiveRecordのコマンドを打つことがあるが、いまいちActiveRecordがわかっていない。

RailsでModelを作成するとはActiveRecord::Baseというクラスを継承していて、このクラスの中にどのようなメソッドがあるのか?

自分用にまとめておく。

ActiveRecord

ActiveRecordはRuby on Railsで使われているOR Mapper(オブジェクト リレーショナルデーターベース マッパー)
のこと。

モデルとテーブルをつなぎ合わせて、SQL文ではなく短いコードでデーターベースの参照したり、編集したりすることができる。

$ rails generate model User name:string email:string

このコマンドを実行するとmodelが作成される。

app/models/user.rb
class User < ApplicationRecord
end

冒頭で述べたように
Ruby on Railsでmodelを作成するとActiveRecord::Baseを継承している。

このモデルクラスであるUserを例にしてメソッドについてまとめる。

ActiveRecord::Baseのメソッドを何があるか

User.new( name: "takahasi", email: "takahasi@example.com" )
#Userモデルに新規ユーザーを作成

User.save
#新規ユーザーをDBへ保存

User.create( name: "takahasi", email: "takahasi@example.com" )
#newとsave行う

User.find(1)
#DBからidが1のユーザーを取得する

User.find_by( email: "takahasi@example.com" )
#DBから条件に該当する一番目のユーザーを取得する。

User.where(email: "takahasi@example.com")
#DBから条件に該当するすべてのユーザーを取得する

#find_byは条件に合うデーターを一つだけ、whereはすべてのデーターを取得する。

User.update
#ユーザーを更新する

#使い方
user = User.find_by(name: "takahasi")
user.name = "sato"
user.save

#上をこのように書ける
user = User.find_by(name: "takahasi")
user.update(name: "sato")


User.destroy
#ユーザーの削除

User.limit(6)
#DBからidの若い順に6つ取得する

User.order("created_at DESC")
#作成日時の新しい順にユーザーを並び替えて取得
#3人ユーザーがいたら、3.2.1の順で並び替え

User.order("created_at ASC")
#作成日時の古い順に取得
#3人ユーザーがいたら、1.2.3で並び替え

ORMapper、DBの扱いは必須なので、もっと理解したい。

参考

https://qiita.com/penguin_note/items/adb0b9bf7c13c1b1d44d

https://qiita.com/tsuchinoko_run/items/f3926caaec461cfa1ca3

https://railsguides.jp/active_record_basics.html

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

enum(列拳型)の使い方をまとめました。(Rails)

enumとは

enumはRubyではなくRailsに導入された便利な型です。
enumを使うことで文字列と値を紐付けができます。
データベースに格納される値が決まるため
定義していないものはデータベースに保存できなくなります。

使用例

order.rb
enum address:[:"自宅", :"登録済みの住所から", :"新しいお届け先"]
enum payment_method:{ "ポイント支払い": 0, "コンビニ支払い": 1, "クレジットカード": 2, "電子マネー": 3, "着払い": 4 }

この記述をモデルに書くこと
書き方は2種類あり、
addressの方は0から順番に番号が振り分けられる。
payment_methodの方は番号を指定して決められます。(0から指定する必要があります)
次に、Viewは、

order/new.html.erb
<%= form_with model: @orders, url: public_orders_confirm_path, method: :get,local: true do |f| %>
 <%= f.label :address, "お届け先の住所" %>

 <%= f.radio_button :address, 0, checked: "checked" %>
 <%= f.label :address, "自宅" %>

 <%= f.radio_button :address, 1 %>
 <%= f.label :address, "登録済みの住所から" %>

 <%= f.radio_button :address, 2 %>
 <%= f.label :address, "新しいお届け先" %>

 <%= f.label :payment_method, "支払い方法" %>
 <%= f.select :payment_method,
     [
      ['ポイント支払い'], 
      ['コンビニ支払い'],
      ['クレジットカード'], 
      ['電子マネー'], 
      ['着払い']
     ],prompt: "クレジットカード" %>
  <%= f.submit "更新", class: "btn btn-sm btn-success" %>
<% end %>

ラジオボタンとセレクターの2種類で実装してます。

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