- 投稿日:2021-01-28T23:54:19+09:00
クラスと使う場合と使わない場合の比較
クラスを使う場合と使わない場合の比較
今回は、「クラスを使うプログラミングと、使わないプログラミングの違い」についてまとめます。
例えば、ユーザを表すデータをプログラム上で処理したいとします。ユーザはデータとして氏名(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、年齢:30Userクラスを導入すると、タイプミスをしたときにエラーが発生します。
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つ重要な考え方があります。①設計
②カプセル化
③継承
④ポリモーフィズム①設計
物と物の関係性とかを考えて作っていく、このプロセスのこと。
どれだけ効率よく無駄なく簡単に出来るか設計していくか考えるのですが、この設計をしていく中で重要な概念が「カプセル化」「継承」「ポリモーフィズム」です。ポイント
・物の振る舞いや定義が明確になっているか
・利用する人がわかりやすいような形になっているか
・利用者が増えても拡張性の高い物となっているか
・違う物との関係性でバランスが崩れないか②カプセル化
他のプログラムから干渉されないように作る考え方
③継承
同じようなプログラムは共通化して使う考え方
④ポリモーフィズム(多態性)
汎用的な形にできるようにする考え方
まとめ
「カプセル化」「継承」「ポリモーフィズム」の考え方をベースに設計していき、実際にコードを進めていく、コードの開発をしていくというのがオブジェクト指向という考え方になります。
- 投稿日:2021-01-28T23:50:12+09:00
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 lsCONTAINER 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.rbrequire 'sinatra' get '/' do 'Hello world!' endmyapp.rbは上記のように記述します。
myapp.rbをコンテナ内にコピーします。
~/sinatra_test$ docker container cp myapp.rb sinatra_test:/ここからはコンテナ内で作業をしていきます。
$ docker container exec -it sinatra_test bashroot@d4f1f93f0c61:/#コンテナ内に入れました。まずはコピーしたmyapp.rbが存在するか見てみます。
root@d4f1f93f0c61:/# lsbin 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 -vruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-linux]Sinatraをインストールします。
root@d4f1f93f0c61:/# gem install sinatraFetching 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 webrickFetching webrick-1.7.0.gem Successfully installed webrick-1.7.0 1 gem installedwebrickがインストールされたのでもう一度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を揃えたほうがいいことがわかりました。
この記事が少しでも皆さんのお役に立てれば幸いです。
参考サイト
- 投稿日:2021-01-28T23:23:11+09:00
Fakerについて
目次
①Fakcerとは
②Fakerの使い方①Fakcerとは
ダミーデータを自動で作成してくれるもの。
②Fakerの使い方
1.Gemfileにfakerを記述
Gemfilegem 'faker'ターミナルbundle install2.seeds.rbにダミーデータを作成できるように記述する(こちらを参考に)
seeds.rb50.times do |n| email = Faker::Internet.email password = "password" User.create!(name: name,email: email, password: password,password_confirmation: password) end3.ターミナルにてデータを生成する
ターミナルrails db:seed以上
◯補足
seeds.rbとは、初期データを生成してくれるファイルのこと。このファイルにデータを生成するコードを書いておくだけで、アプリにデータを備えさせることができる。
- 投稿日:2021-01-28T22:43:29+09:00
RailsでjQueryを利用する方法 (Webpacker使用)
環境
Rails 6.0.3
Ruby 2.6.6導入
WebpackのProvidePluginを利用することでreqireを明示することなくどこでもjQueryを使用することが可能になります。
jQueryをインストール
$ yarn add jquery
plugin用のファイルの作成
config/webpack/plugins/provide.jsconst webpack = require('webpack') module.exports = new webpack.ProvidePlugin({ $: 'jquery', jquery: 'jquery' });webpackの設定リストに先ほどのプラグインを追加する
特定の環境でしか使用しないのであればenviroment.jsではなくtest.js,development.jsなどに記述する。
config/webpack/environment.jsconst { 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が使用できていることがわかります。
- 投稿日:2021-01-28T21:59:34+09:00
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から一気に取得している
- 投稿日:2021-01-28T21:18:40+09:00
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メソッドをつかう。
- 投稿日:2021-01-28T20:18:46+09:00
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"}
- 投稿日:2021-01-28T19:33:29+09:00
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 installTraceback (most recent call last):
2: from /Users/irieryoutaira/.rbenv/versions/2.6.3/bin/bundle:23:in<main>'
activate_bin_path'
1: from /Users/irieryoutaira/.rbenv/versions/2.6.3/lib/ruby/2.6.0/rubygems.rb:302:in
/Users/irieryoutaira/.rbenv/versions/2.6.3/lib/ruby/2.6.0/rubygems.rb:283:infind_spec_for_exe': Could not find 'bundler' (2.2.6) required by your /Users/irieryoutaira/ruby/environment/toy_app/Gemfile.lock. (Gem::GemNotFoundException)
bundle update --bundler
To update to the latest version installed on your system, run.
gem install bundler:2.2.6`
To install the missing version, runエラー。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/19895https://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
- 投稿日:2021-01-28T18:27:45+09:00
ActiveRecordにはどんな種類があるのか?書き方は?
ActiveRecordとは
人間で言う「脳みそ」のようなものです。勉強し始めた頃、私はこのように覚えました(笑)データベースへ指示を送るメソッドの総称のことをいい、一つ一つのコードはコントローラーへ記述します。少し難しく言うと、ruby on railsでよく使われるORMの1つです。ORM(Object Relational Mapping)とはオブジェクト指向の言語でオブジェクトとして使うために変換してくれるものです。ActiveRecordのメソッドを使うことで、データベースのレコードからデータを取り出してくれたり、レコードへ新しいデータを追加することができます。よく使うActiveRecordのメソッドの種類と書き方をまとめてみました。
間違いやもっと良い書き方があれば、教えてください!種類と書き方
今回は「title」と言う名前のモデルがあるとして、コードの書き方を説明します。
all 全てのデータを受け取る
Title.allfind ある1つのデータを受け取る
Title.find(取得したいテーブルの数字) ex) Title.find(3) #Titleモデルのtitlesテーブルの3番目の値を受け取るnew インスタンスを生成する
Title.newsave インスタンスを保存する
Title.saveorder 並び順には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 ガイド
- 投稿日:2021-01-28T18:17:17+09:00
【Rails】Google Maps API を利用して現在地から店舗検索を実装する
目標
開発環境
- 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.erbdef 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で実装しているので、ブラウザのデバッグツールからエラー内容が確認できます。
該当ページで、デバッグツールを開きます。
エラーがある際は、右上に赤いバツ印があるのでそれをクリックすると、下のコンソールにエラーが表示されます。
さらに、コンソールの右上部分をクリック
すると、今度は右上に具体的なエラー箇所が表示されます。経験則ですが、具体的なソースコードを見てもわけわからんコードが羅列してあるようなときは、APIが起因(APIが正常に起動してないとか)であることが多かった印象です。
そんなときは、もう一度ターミナルからログを確認したり、Google Maps APIの設定を見直してみると良いかもしれません。
(筆者はそこでめちゃくちゃ苦戦しました。)参考記事
【Rails】Geolocation APIを用いて位置情報を取得する方法
【Ruby on Rails】Googlemapの複数ピン立て、吹き出し、リンク
【Rails】Google Maps APIを利用して登録した住所をMap表示する
- 投稿日:2021-01-28T17:57:02+09:00
【Railsチュートリアル】第4章 Rails風味のRuby 演習と解答
4.2.1 文字列
4.2.1 - 1
city変数に適当な市区町村を、prefecture変数に適当な都道府県を代入してください。
> city = "たこ焼きが" => "大阪府" > prefecture = "おいしい県" => "大阪市"4.2.1 - 2
先ほど作った変数と式展開を使って、「東京都 新宿区」のような住所の文字列を作ってみましょう。出力にはputsを使ってください。
> puts "#{city} #{prefecture}" たこ焼きが おいしい県 => nil4.2.1 - 3
上記の文字列の間にある半角スペースをタブに置き換えてみてください。(ヒント: 改行文字と同じで、タブも特殊文字です)
> puts city + " " + prefecture たこ焼きが おいしい県 => nil4.2.1 - 4
タブに置き換えた文字列を、ダブルクォートからシングルクォートに置き換えてみるとどうなるでしょうか?
> puts city + ' ' + prefecture たこ焼きが おいしい県 => nil4.2.2 オブジェクトとメッセージ受け渡し
4.2.2 - 1
"racecar" の文字列の長さはいくつですか? lengthメソッドを使って調べてみてください。
> "racecar".length => 74.2.2 - 2
reverseメソッドを使って、"racecar"の文字列を逆から読むとどうなるか調べてみてください。
> "racecar".reverse => "racecar"4.2.2 - 3
変数sに "racecar" を代入してください。その後、比較演算子(==)を使って変数sとs.reverseの値が同じであるかどうか、調べてみてください。
> s = "racecar" => "racecar" > s == s.reverse => true4.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 => nilpalindrome = 回文
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_tester4.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. => nil4.2.3 - 3
palindrome_tester("racecar")に対してnil?メソッドを呼び出し、戻り値がnilであるかどうかを確認してみてください(つまりnil?を呼び出した結果がtrueであることを確認してください)。このメソッドチェーンは、nil?メソッドがリスト 4.10の戻り値を受け取り、その結果を返しているという意味になります。
> palindrome_tester("racecar").nil? It's a palindrome! => true4.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! => nil4.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 > end4.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] => true4.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..104.4.1 - 3
比較演算子==を使って、上記2つの課題で作ったそれぞれのオブジェクトが同じであることを確認してみてください。
> a == b => true4.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? => true4.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? => true4.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 => nil4.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 => truememo
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とドットインストールで学習してきましたが、ここにきて学習の点と点がつながった繋がった感じがして、とても楽しかったです。すべて理解できたとは言えませんが、少しずつ進んでいきたいと思います!
- 投稿日:2021-01-28T17:49:57+09:00
Ruby復習②
Ruby復習①からの続きです。
クラスの定義
Rubyファイルclass Menu #クラスは大文字で始める endattr_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.rbclass Menu . . endindex.rbrequire "./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.rbrequire "./menu" class Food < Menu end #Menuが親クラス、Foodは子クラスsuper
オーバーライドしたメソッドの中で、superとすることで、
親クラスの同名のメソッドを呼び出すことができるmenu.rbclass Menu attr_accessor :name attr_accessor :price def initialize(name:, price:) self.name = name self.price = price end endfood.rbclass Food < Menu attr_accessor :calorie def initialize(name:, price:, calorie:) super(name: name, price: price) #親クラスの同名メソッドを呼び出す self.calorie = calorie end . . endDateクラス
Dateクラスの読み込み
Dateクラスは日付を扱う
Rubyファイルrequire "date" . .Dateクラスのインスタンス
Rubyファイルrequire "date" date1 = Date.new(2021, 1, 28) #引数に「年・月・日」を渡して、Dateインスタンスを生成する puts date1コンソール2021-1-28Dateクラスのインスタンスメソッド
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?コンソールtrueri コマンド
ri コマンドで知りたい命令、オブジェクトのドキュメントを見ることができる
print, put, p
Rubyファイルprint "hello world" #渡したオブジェクトを文字列にして表示させるための命令 puts "hello world" #printの動作+ 改行が付く p "hello world" #デバッグ用コンソールhello worldhello world "hello world"定数の命名ルール
定数は英大文字始まりでないといけない。
慣習的に全部大文字にすることが多い。
定数は書き換えると警告は出るが、値は書き換えられてしまうので、注意するRuby復習以上
- 投稿日:2021-01-28T17:49:35+09:00
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 条件式 処理 endRubyファイルscore = 90 if score > 80 puts "よくできました" endコンソールよくできました条件式の出力
Rubyファイルscore = 90 puts score > 80コンソールtrue比較演算子
Rubyファイルa == b #aとbが等しいとき、true a != b #aとbが異なるとき、 trueelse
Rubyファイルif 条件式 処理 else 処理 endRubyファイルscore = 100 if score == 100 puts "満点です" else puts "満点ではありません" endelsif
Rubyファイルif 条件式1 処理 elsif 条件式2 処理 else 処理 endRubyファイル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 |変数名| #実行したい処理 endRubyファイル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 Satoeach
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 メソッド名 処理 endRubyファイルdef introduce puts "こんにちは" puts "私はたろうです endメソッドの呼び出し
Rubyファイルdef introduce puts "こんにちは" puts "私はたろうです end introduce #introduceメソッドを呼び出しているコンソールこんにちは 私はたろうです引数を受け取るメソッドの定義
Rubyファイルdef メソッド名(引数名) 処理 endRubyファイル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復習②へ
- 投稿日:2021-01-28T16:41:14+09:00
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になり...
と繰り返し処理が行われます。最後の説明が分かりにくかったら申し訳ないです。
間違いがありましたらご指摘お願い致します。
解説は以上となります。ここまで見て頂きありがとうございました。
- 投稿日:2021-01-28T16:25:08+09:00
railsでコメント投稿機能を非同期化する(jQueryなし)
開発環境
Mac OS Catalina 10.15.7
ruby 2.6系
rails 6.0系前提
同期でのコメント投稿機能は実装済みとする
JavaScriptのフレームワークは使っていません各テーブルとアソシエーションは以下の通り
users テーブル
Column Type Options nickname string null: false 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: commentform_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.rbclass 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.rbclass 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 endcreate.js.erbを編集する
commentsディレクトリにcreate.js.erbファイルを作り、以下のように編集します。
create.js.erbvar 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に保存したコメントを代入する記述を書き(上記commentscontroller.rb参照)、create.js.erbでlocalsオプションを使って、データを渡してあげましょう。これでコメント投稿の非同期化は完成です。
しかし、このままだと、コメントの投稿フォームに、投稿したコメントが残ってしまうので、最後にコメントの中身を削除するために、投稿フォームをIDで取得して、空にする記述を書いています。
長くなりましたが、以上です。
参考になれば幸いです。
- 投稿日:2021-01-28T15:44:15+09:00
【Rails】Google Maps APIを利用して登録した住所をMap表示する
目標
アプリケーションで登録した住所を元にgoogle mapを表示させる機能実装についてまとめます。
入力フォームに入力した住所から、詳細ページにてマップを表示、ピンを立てて位置表示させるまで実装します。
開発環境
- Ruby: 2.6.4
- Rails: 5.2.4
- OS: macOS Catalina
前提条件
- 既に、住所を登録するためのテーブルおよびカラムは作成済みであること
私は、音楽スタジオの口コミレビューサイトを作成しました。デモでは、スタジオを新規作成し、その詳細ページにマップ表示されるといったものです。
本記事で登場する変数では下記を使用します。それぞれご自身の環境に合わせて書き換えてください。
テーブル名:studios
カラム名:addressGoogle 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を導入する
Gemfilegem 'gon' gem 'geocoder'
gem 'gon'
↪コントローラーで定義したインスタンス変数を、viewのjavascript内で使用できるようにする
gem 'geocoder'
↪住所から緯度、経度を算出するマップは、緯度と経度の情報を元に表示します。
geocoderは、登録した住所から緯度、経度を自動で算出してくれるいいヤツです。記述したら
bundle install
してください。モデルの編集
次に、geocoderを使うために適用するモデルに以下の記述をします。
今回はスタジオの位置情報を表示したいので/app/models/studio.rb
に記述しました。studio.rbclass 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.rbGeocoder.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.rbdef 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にアクセスができなくなります。こんな感じで、設定していましたが
制限なしに変更しました。
すると、登録した住所から緯度、経度を算出できるようになりました。
セキュリティ的には、設定したほうがいいんでしょうけどね
今回は、動いたので良しとしました笑同じ様なエラーにハマっている方の手助けになればと思います。
参考記事
【Rails】Google Mapの表示方法
【Rails】Geocoding APIを用いて高精度で緯度経度を算出し、Google Mapに表示する方法
Rails 登録した住所をGoogle Mapで表示させる
Rails5でGoogleMapを表示してみるまで
- 投稿日:2021-01-28T15:44:15+09:00
[Rails]Google Maps APIを利用して登録した住所をMap表示する
目標
アプリケーションで登録した住所を元にgoogle mapを表示させる機能実装についてまとめます。
入力フォームに入力した住所から、詳細ページにてマップを表示、ピンを立てて位置表示させるまで実装します。
開発環境
- Ruby: 2.6.4
- Rails: 5.2.4
- OS: macOS Catalina
前提条件
- 既に、住所を登録するためのテーブルおよびカラムは作成済みであること
私は、音楽スタジオの口コミレビューサイトを作成しました。デモでは、スタジオを新規作成し、その詳細ページにマップ表示されるといったものです。
本記事で登場する変数では下記を使用します。それぞれご自身の環境に合わせて書き換えてください。
テーブル名:studios
カラム名:addressGoogle 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を導入する
Gemfilegem 'gon' gem 'geocoder'
gem 'gon'
↪コントローラーで定義したインスタンス変数を、viewのjavascript内で使用できるようにする
gem 'geocoder'
↪住所から緯度、経度を算出するマップは、緯度と経度の情報を元に表示します。
geocoderは、登録した住所から緯度、経度を自動で算出してくれるいいヤツです。記述したら
bundle install
してください。モデルの編集
次に、geocoderを使うために適用するモデルに以下の記述をします。
今回はスタジオの位置情報を表示したいので/app/models/studio.rb
に記述しました。studio.rbclass 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.rbGeocoder.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.rbdef 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にアクセスができなくなります。こんな感じで、設定していましたが
制限なしに変更しました。
すると、登録した住所から緯度、経度を算出できるようになりました。
セキュリティ的には、設定したほうがいいんでしょうけどね
今回は、動いたので良しとしました笑同じ様なエラーにハマっている方の手助けになればと思います。
参考記事
【Rails】Google Mapの表示方法
【Rails】Geocoding APIを用いて高精度で緯度経度を算出し、Google Mapに表示する方法
Rails 登録した住所をGoogle Mapで表示させる
Rails5でGoogleMapを表示してみるまで
- 投稿日:2021-01-28T15:29:08+09:00
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のstack
がruby 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
をターミナルで実行し、デプロイをしましょう。
- 投稿日:2021-01-28T15:09:50+09:00
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.rbclass SearchForm include ActiveModel::Model include ActiveModel::Attributes attribute :post_content, :string attribute :comment_content, :string attribute :name, :string end解説していきます。
include ActiveModel::ModelActiveModelのModelモジュールを導入しています。これで、ActiveRecordの機能が使えるようになります。
include ActiveModel::AttributesActiveModelのAttributesモジュールを導入すると、attributeメソッドが使えるようになります。attributeメソッドは、属性名と型を定義することができるメソッドです。今回は、post_content、comment_content、nameをstring型の属性として定義しています。
ルーティングの設定
posts_controllerのsearchアクションへ接続するためのルーティングを設定していきます。
config/routes.rbRails.application.routes.draw do resources :posts, shallow: true do collection do get :search end end endcollectionを使って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.rbscope :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.rbclass 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.distinctdistinctによって、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" = 'ボール')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.rbclass 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.rbclass PostsController < ApplicationController def search @posts = @search_form.search.includes(:user).page(params[:page]) end endapplication_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_formapp/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これで検索機能が完成するはずです!
参照
- Railsガイド Active Model の基礎
- Let'sプログラミング ルーティングにアクションを追加
- Qiita ActiveModel::Attributesを使う
- Qiita Rails - LIKE句を使った文字のあいまい検索(特定の文字を含む語句を曖昧検索したい場合)
- 働くエンジニアマガジン【SQL】LIKE句の基本的な使い方~複数検索する場合の方法まで解説
- Qiita Railsのモデルのscopeを理解しよう
- Qiita [memo]Railsのモデルで使うクラスメソッドとscopeの違いを理解する
- Ruby 2.7.0 リファレンスマニュアル instance method String#strip
- Ruby 2.7.0 リファレンスマニュアル 正規表現 POSIX文字クラス
- TechRacho [連載:正規表現] Unicode文字プロパティについて (3) 文字プロパティとは
- Qiita Rails distinctメソッドについて
- Qiita Rails 5 の or を色々試してみた
- Qiita RailsのStrong Parametersを調べる
- pikawaka 【Rails】form_withの使い方を徹底解説!
- Railsドキュメント モデルなどからフォームタグを生成
- 投稿日:2021-01-28T14:06:33+09:00
【銀座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の使用やデータマート構築の検討少しでもご興味がありましたら、ぜひカジュアルに情報交換をさせてください!
- 投稿日:2021-01-28T13:38:08+09:00
英単語しりとりプログラムを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.rbrequire_relative('./lib/main_proces') first_person, first_char = bigin_game loop_game(first_person, first_char)main_proces.rb
main_proces.rbrequire_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 endword_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
- 投稿日:2021-01-28T13:01:17+09:00
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個づつ上げていく必要があります。
これが非常に大変です。。。具体的にはRansackやPublicActivityなどがひっかかりました。
本題
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のアップデートを放置していると大変なことになるというのが教訓です。。。みなさん気をつけてくださいね!
- 投稿日:2021-01-28T12:49:04+09:00
assetsディレクトリの役割
前提
rubyのバージョン ruby 2.6.5
Railsのバージョン Rails 6.0.0結論(内容)
複数のディレクトリやファイルに分かれたassetsディレクトリ内のファイルを1つに連結圧縮する機能
Ruby on Rails内で使用するファイル、CSSや(Javascript)、画像ファイルを開発作業がしやすいようにファイル分割してコーディングができるようにしつつ、最終的に1つのファイルに連結・圧縮する仕組み
assets内では内容の種類ごとにディレクトリ、ファイルに分割されている理由は開発者にとって何がどこに記載されているか認識させるため
※(JavaScript)
Rails5前とRails6を境にjavascriptのディレクトリの位置が変更Rails6 app直下
Rails5 assets直下
- 投稿日:2021-01-28T12:36:59+09:00
学び直し rubyがミニツク編 Part2
今日の教科書
Rubyがミニツク基礎知識基礎知識
メモ
- 変数は宣言なしでも定義できる
- ローカル変数は小文字のアルファベットか"_"が最初に来る
if <条件式> then <文> endelsifはそれまでの<条件式>の結果が偽であり、その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 <条件式> do <文> enddef <メソッド名> <文> 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で返す
- 文字列オブジェクトの「*」演算子は数値の引数を取ることができる。*演算子を使用すると、文字列オブジェクトの内容を数値オブジェクトの回数だけ繰り返した新しい文字列オブジェクトを作成して返す
- 投稿日:2021-01-28T12:33:19+09:00
JavaScriptのディレクトリ packsとchannelsの役割
- 投稿日:2021-01-28T12:19:07+09:00
【Ruby初学者】日付/曜日をたった4行で出す方法
某スクールのRuby問題を解いた備忘録。
test.rbnow = Time.new puts "現在は西暦#{now.year}年#{now.month}月#{now.day}日" week = ["日","月","火","水","木","金","土"] puts week[now.wday] + '曜日です'と、記述しLinuxで実行すると
test.rb% ruby test.rb 現在は西暦2021年1月28日 木曜日ですとなる。
ちなみに、railsのアプリケーションで表示するときには、
コントーラーファイルで、インスタンス変数を定義して、controller.rbdef 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>ブラウザで表示されるのは、こんな感じ。
参考記事
[Ruby入門] 14. 日付と時刻を扱う(全パターン網羅)/@prgseekさま
https://qiita.com/prgseek/items/c0fc2ffc8e1736348486
- 投稿日:2021-01-28T12:06:15+09:00
「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内にコピペで置き換えることでエラー解消できる。エラー画面
エラーが起こるまでの経緯
フォロー機能を実装するにあたり、jQueryを使用するため公式ページに沿ってjQueryをインストールした。
↓
コーディング完了し、挙動確認する際、フォーローのカウントが実装できていることを確認
↓
しかし、フォローボタンをクリックするタイミングで色が変色するよう発火の記述をしていたが変色しない。
↓
ブラウザを更新するとボタンの色が変色する
↓
コンソールを開き、挙動確認すると、「is not defined」のエラーが起こるエラー解消方法
①jQueryのバージョンの確認はpackage.jsonで確認
②「jQueryのバージョン script」ブラウザ上で検索
③サイト内の適したjQueryのバージョンのscriptをコピー
④自身のviewのhead内にペースト
(筆者はapp/view/layouts/application.html.erbのhead内に記述)⑤挙動確認→OK
まとめ
インストールしたバージョンのjQueryのscriptをviewのhead内にコピペで置き換えることでエラー解消
- 投稿日:2021-01-28T11:01:53+09:00
absメソッドは、どんな時に使うのか。
某スクールのRuby問題を解いた備忘録。
(自身が情弱知識なので、備忘録は小学生もわかるように書いてみた。)プログラムを作っているときは、どのくらいの「差」があるのかを数で出したい時がある。
(数で出さなきゃいけない)
例…
⚫︎「車に乗れる人数」と「乗りたい人の数」 → みんな乗れる?乗れない?
⚫︎「今持ってるお金」と「必要なお金」 → お金足りる?足りない?
⚫︎「入院したい人」と「入院できる人の数」 → 入院できる?できない?プログラムに組んで、簡単に教えてくれる便利な機能をつけたいな。
そんな時、「absメソッド」を使う。
qiita.rbty(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より小さい数になった数を見せたいんだけど、マイナスの記号はいらないから、マイナスを取った数を表してくれるメソッド。
大人の言い方で、「絶対値を求める」メソッド。
- 投稿日:2021-01-28T10:22:17+09:00
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.rbclass 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
- 投稿日:2021-01-28T10:04:43+09:00
enum(列拳型)の使い方をまとめました。(Rails)
enumとは
enumはRubyではなくRailsに導入された便利な型です。
enumを使うことで文字列と値を紐付けができます。
データベースに格納される値が決まるため
定義していないものはデータベースに保存できなくなります。使用例
order.rbenum 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種類で実装してます。