- 投稿日: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:39:54+09:00
Railsアプリ作成のための下準備
Railsのインストール
Railsのインストールをおこないます。参考文献に従い、バージョンは5.2.1を選択。
$ gem install rails -v 5.2.1無事インストールできました!!
アプリの雛形作成
アプリの雛形を作成します。
$ rails new tweet_app -d mysqlアプリケーションディレクトリに移動して…
% cd test_appデータベースを作成します。MySQLを起動した状態で次のコマンドを入力。
$ bin/rails db:createこれでDBが作成される…と思ったら、エラーが。
Mysql2::Error::ConnectionError: Access denied for user 'root'@'localhost' (using password: NO)数時間対処法を探しましたが、一向に解決しないので急遽PostgreSQLを使うことに。
Homebrewでインストールし、先程の手順まで戻ります。
$ bin/rails db:create今度は無事にDBが作成されました!
続いてサーバーを起動します。$ bin/rails sサーバーを起動させたら、ブラウザでhttp://localhost:3000 にアクセスします。
(サーバーを終了させるには[Control + C])
画像の通り、正常に動作しています。
Slimの導入
Railsを用いた開発では、アプリの画面を「ERB」というテンプレートエンジン(最終的なHTMLを生成する仕組み)を使用しています。
その他に、ERBよりも簡潔な書き方ができる「Slim」があります。今回はこちらを使用することにします。
Slimの導入には2つのgemを使用します。1.slim-rails: Slimのジェネレータを提供する
2.html2slim: ERB形式のファイルをslim形式に変換するアプリのGemfileに定義し、
gem 'slim-rails' gem 'html2slim'bundleコマンドでインストール。
$ bundleこれで、生成されるテンプレートファイルはSlim形式になりました。
現時点でapp/views/layoutsディレクトリ内にERB形式のファイルが3つあるので、以下のコマンドでSlim形式にします。$ bundle exec erb2slim app/views/layouts/ --deletebundle exec [コマンド]で、Bundler管管理下のgemを利用できる状態でコマンドを実行します。
BootstrapとSassの導入
自力でCSSを組むのは大変なので、フロントエンドフレームワークのBootstrapを導入します。
Gemfileに以下を追記し、bundleコマンドでインストール。gem 'bootstrap'CSSにもSlimと同じように効率的な書き方の形式「Sass」があります。
今回はSassの提供する、「SCSS」記法でCSSを買いていきます。まず、app/assets/stylesheets/application.cssを削除し、
$ rm app/assets/stylesheets/application.cssapp/assets/stylesheets/application.scssを作成し、以下をファイルに記述。
@import "bootstrap";これで、画面をBootstrapが反映された状態にすることができます。
次に、見栄えを具体的に改善するため、app/views/layouts/application.html.slim
ファイルを以下のように編集します。doctype html html head title | TweetApp = csrf_meta_tags = csp_meta_tag = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' = javascript_include_tag 'application', 'data-turbolinks-track': 'reload' body .app-title.navbar-expand-md.navbar-light.bg-light .navar-brand Tweet_app .container = yieldエラーメッセージの日本語化
Railsのエラーメッセージは英語ですが、日本語にすることができます。
日本語翻訳ファイルがGitHub上にあるので、ダウンロードしましょう。$ wget https://raw.githubusercontent.com/svenfuchs/rails-i18n/master/rails/locale/ja.yml --output-document=config/locales/ja.yml続いて、デフォルトで日本語コンテンツを使うようにアプリの設定を変更します。
config/initializers/locale.rbを作成し以下を記入。Rails.application.config.i8m.default_locale = :jaこれで、アプリ製作の下準備は完了です!
参考
- 投稿日:2021-01-28T22:42:02+09:00
Ruby on RailsとSass
初めに
RailsではCssの他にSassを使うことができます。Sassとは何か。そしてCssとは何が違うのかについて調べてみたら、Cssよりも遥かに効率的かつ視覚的に見やすくコードを書くことができるなどのメリットがあることが分かってきました。こちらの記事を参考にCssではなく、Sassという書き方を知っていただき活用いただければと思います。
目次
- Sassとは
- RailsにおけるSassの導入方法
- Sassのメリットについて
- 参考サイト
Sassとは
Sassはアメリカのハンプトン・キャトリンとネイサン・バイゼンバウムによって開発されたスタイルシート言語です。 Syntactically Awesome StyleSheetの略であり、その意味はSyntactically-構文的に、Awesome-素晴らしいStyleSheet-スタイルを適用するシートという意味になります。ここでポイントになるのが、Syntactically-構文的にというところにありますが、Cssの書き方を画期的に改変したものになります。
RailsにおけるSassの導入方法
SassはCssの拡張言語であり、利用するにはSassで書いた後にCssにコンパイルする必要があります。コンパイルの仕方についてはいろいろありますが、Vscodeを利用する場合にはextensions(拡張機能)の一つである「Live Sass Compiler」を使ってCssファイルに変えます。以下、ダウンロードサイトになりますので、ご覧ください。
ダウンロードはこちら
https://marketplace.visualstudio.com/items?itemName=ritwickdey.live-sass
Railsではsass-railsというgemを導入する必要がありましたが、Rails3.1以降からは基本的にRails newによってアプリを新規作成する際にコンパイラーもインストールされるので何もしなくても使えます。Sassを使うメリットについて
Sassで書いてもCssにコンパイルされるのに、何故CssではなくわざわざSassで書く必要があるでしょうか?それには以下のようなメリットがあるからです。
1. 構造的にコードが見やすくなります。
Cssで書いてもコードは同じですが、Sassで書くとCssの記述をネスト化することができます。例えば、以下のようなコードがあったとします。
styleを適用するHtmlファイル
<div id="news"> <h2>お知らせ</h2> <ul id="news-list"> <li> <a href="#">2021.11.11 <span class="news-title"></span> </a> </li> </ul> </div>①Cssで書いた場合
#news { float: right; width: 100%; } #news h2 { font-size: 12px; font-weight: 500; margin-bottom: 5px; } #news-list { background-color: #f3f5f4; border: 1px solid #ccc; padding: 15px; } #news-list li { margin-bottom: 12px; font-size: 32px; } #news-list li a { color: #c8a8a8; text-decoration: none; } #news-list li:hover { opacity: 0.3; }②Sassで書いた場合
#news { float: right; width: 100%; & h2{ font-size: 12px; font-weight: 500; margin-bottom: 5px; } } #news-list { background-color: #f3f5f4; border: 1px solid #ccc; padding: 15px; & li { margin-bottom: 12px; font-size: 32px; &:hover { opacity: 0.3; } & a { color: #c8a8a8; text-decoration: none; } } }①と②を見比べてみると分かる様に、②のSassで書いた方がネストされていて構造的に分かりやすいですよね。一眼で見ても適用するスタイルの記述がどの行にあるのか探す手間がなくなります。コードが長くなればなるほど、このようなメリットは大きく感じるでしょう。
2. Sassでは変数を使うことができます。
変数と聞くとRubyやPhpなどのプログラミング言語でよく使われていますが、Sassでも変数を使うことが可能です。どういう場面で変数を使うかなんですが、具体的には以下の様なケースが挙げられます。
①CSSの場合
.main-content { color: #c8a8a8; } .header-nav { background-color: #c8a8a8; } .footer { color: #c8a8a8; }このように同じ#c8a8a8;の記述を3回繰り返しています。もし適用する色を変える場合、合計3箇所修正しなければなりません。打ち間違いも起こるし、繰り返し作業になるので非効率的です。
②Sassの場合
$maincolor: #c8a8a8; .main-content { color: $maincolor; } .header-nav { background-color: $maincolor; } .footer { color: $maincolor; }この場合、\$maincolorと言う変数に#c8a8a8というcolor属性を代入していますが、この$maincolorのcolorを修正するだけで済みます。どちらが効率的なのかは一目瞭然と言えるでしょう。この他にも
✔︎①関数使うことができる
✔︎②演算が可能
✔︎③外部ファイルをインポートして使える
✔︎④継承ができるなどのメリットがあります。Railsではコンパイルが無しに使えるので是非試してみてはいかがでしょうか。
参考サイト
- 投稿日: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-28T20:11:11+09:00
Railsで再帰的に自分自身を参照する外部キー制約を設定する方法
答えは全て↓にあります。
アプリケーションのみで参照をコントロールするver
DBにも外部キー制約を付与するverこのユースケースに特化した情報があまりない中で、非常に良記事なのですが、Google検索で引っかかりにくかったので、
どうせまた調べる未来の自分がまたアクセスしやすいように備忘録としてこの記事を作成しました。未来の自分がこのユースケースで調べるときの検索ワード
「Rails 外部キー 再帰」
「Rails 外部キー 自分自身」
「Rails 外部キー 自分」
「Rails 外部キー 自身」
「Rails 外部キー 自分 参照」
「Rails 外部キー制約 再帰」
「Rails 外部キー 再帰 マイグレーション」鬼門
- 再帰関数がヒットすること
- 再起するSQL文の作成(テーブル作成部分は前提の上で)
- rails以外の用途
- 投稿日:2021-01-28T20:07:59+09:00
ruby 3.0 × railsでminitest起動するとerrorになる場合
- 投稿日: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-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:28:12+09:00
【Rails】星★を用いた評価機能の実装
はじめに
Railsを用いたオリジナルアプリケーションを開発していた時の事。
投稿する際に、星を用いた評価機能があるとかっこいいと思いトライしてみたのですが
とても苦戦したので、振り返ることができるよう書き留めておきます。実現したい事
postsテーブルの作成
postsテーブルにrateカラムを作成します。
今回、1.5
や3.5
のように、0.5
刻みで評価値を設定したいので、カラムの型はfloat型
とします。jQueryの導入
まずはGemfileに以下の記述を加え、忘れずに
bundle install
します。Gemfilegem 'jquery-rails'次に
application.js
に次のコードを追記します。application.jswindow.jQuery = window.$ = require('jquery')この時
package.json
の編集も必要らしいのですが、ターミナルで次のコマンドを実行すればオッケーらしいです。yarn add jquery(jQueryの導入に関しては
yarn add jquery
コマンドが何をしてくれているのかあまり分かっていないので、分かる人からしたら効率の悪い導入方法だと思われてしまうかもしれません。)jsコードと画像の読み込み
jsコード
下記のGitHubからコードをコピーして、
jquery_raty.js
などと適当に名前をつけてapplication.js
の近くに配置します。https://github.com/wbotelhos/raty/blob/master/lib/jquery.raty.js
次に
application.js
からrequire
します。application.jswindow.jQuery = window.$ = require('jquery') require("../jquery_raty") //追加
require
する際のjquery_raty.js
のパスはファイルの配置位置によって人それぞれなので注意です。画像
下記のGitHubから画像を持ってきます。
star-half.png
、star-off.png
、star-on.png
の3つの画像をapp/assets/images
配下に保存します。https://github.com/wbotelhos/raty/tree/master/lib/images
星を用いた評価の入力と保存
new.html.erb<div class="" id="star"> <%= f.label :rate,'評価 :', class:"" %> <%= f.hidden_field :rate, id: :review_star %> </div> <script> $('#star').raty({ size : 36, starOff: '<%= asset_path('star-off.png') %>', starOn : '<%= asset_path('star-on.png') %>', starHalf: '<%= asset_path('star-half.png') %>', scoreName: 'post[rate]', half: true, }); </script>大きさの調整は
class=""
で任意に設定してください。
また、下記1行目でPostモデルのrateカラムに値を保存。2行目の記述で0.5
刻みの星による評価を許容しています。scoreName: 'post[rate]' half: true,評価の表示
index.html.erb<div class=""> <div id="star-rate-<%= post.id %>"></div> <script> $('#star-rate-<%= post.id %>').raty({ size: 36, starOff: '<%= asset_path('star-off.png') %>', starOn : '<%= asset_path('star-on.png') %>', starHalf: '<%= asset_path('star-half.png') %>', half: true, readOnly: true, score: <%= post.rate %>, }); </script> </div> <div class=""> <%= post.rate %> </div>省略していますが、
post
に情報を入れてeach
で回しています。
class
はお好みで設定してみてください。まとめ
星★を用いた評価機能を実装するだけで、見た目がガラッと変わり感動しました。
苦戦はしましたが、他にも実装したい機能あればどんどん挑戦していきたいです。参考
- 投稿日: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-28T14:05:50+09:00
YouTubeとTwitterをローカルで埋め込む
YouTubeを指定したURLの動画をローカルで埋め込む
まずYouTubeの埋め込みにはiframeを使う
admin/view/shared/_embed_youtube.html.slim.embed-youtube = content_tag 'iframe', nil, width: width, height: height, src: "https://www.youtube.com/embed/任意のYouTubeのID", \ frameborder: 0, gesture: 'media', allow: 'encrypted-media', allowfullscreen: trueこれでYouTubeを埋め込むことができる
あとは「任意のYouTubeのID」を挿入した値によって変えればいいYouTubeの補足説明
YouTubeは共有ボタンのURLで共有できる
各動画は/(スラッシュ)以降の文字列により個別に分類されている
なので上記のURLを取得後、スラッシュ以下の文字列を取得し「任意のYouTubeのID」に代入すると任意のYouTubeが表示されるようになる実装
まずURLを取得するための記入する場所は
_edit_embed.html.slim.box-body = f.input :embed_type, collect: Embed.embed_types_i18n.invert, include_blank: false = f.input :identifier(embedテーブルにはembed_typeとidentifierカラムがある)
identifierにURLを記入できるようにし、identifierにYouTubeのURLを格納する。
(https://youtu.be/y4LofvDYReIの場合)identifier == https://youtu.be/y4LofvDYReI となる
ここからスラッシュ以下の文字列を取得するメソッドを作成
embed.rbdef youtube_identifier identifier.split('/').last if youtube? endこれで/から以下の部分を取得できるようになる
最後にYouTube埋め込み部分に
admin/view/shared/_embed_youtube.html.slim.embed-youtube = content_tag 'iframe', nil, width: width, height: height, src: "https://www.youtube.com/embed/#{embed.youtube_identifier}", \ frameborder: 0, gesture: 'media', allow: 'encrypted-media', allowfullscreen: trueこれで指定した動画の埋め込みを作ることができる。
Twitter埋め込み方法
Twitterの埋め込みはTwitterにある「ツイートを埋め込む」から作ることができる
ツイートを埋め込むを押すと以下のコードが取得できる
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">みんなの夢はなに?その夢を実現するために、ロールモデルを見つけて、インタビューしよう!<a href="https://twitter.com/hashtag/%E5%A4%A2%E3%82%92%E3%81%88%E3%81%8C%E3%81%8D%E8%A8%88%E7%94%BB%E3%82%92%E3%81%9F%E3%81%A6%E8%A1%8C%E5%8B%95%E3%81%99%E3%82%8B?src=hash&ref_src=twsrc%5Etfw">#夢をえがき計画をたて行動する</a> <a href="https://t.co/YlwaMKkiRp">pic.twitter.com/YlwaMKkiRp</a></p>— セサミストリート公式 (@sesamejapan) <a href="https://twitter.com/sesamejapan/status/1354383650763116547?ref_src=twsrc%5Etfw">January 27, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>ここからいらないところを抜けば(slimで)
blockquote.twitter-tweet a href="エルモツイートのURL" script async="" charset="utf-8" src="https://platform.twitter.com/widgets.js"となる
ここの「エルモツイートのURL」を任意のURLに変えると任意のTwitterが表示できるようになる。
YouTubeと同じく任意のURLをidentifierで置くなら
任意のツイートのURLは[embed.identifier]となる。よってblockquote.twitter-tweet a href="#{embed.identifier}" script async="" charset="utf-8" src="https://platform.twitter.com/widgets.js"と書くことができる
- 投稿日:2021-01-28T13:33:15+09:00
validateに条件分岐を設定する
- 投稿日: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: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: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-28T11:41:13+09:00
Ruby on Railsでアプリを作ってみよう⑧
データを作って保存する処理を行っていきます。
テーブルにデータを保存するため、createアクションを使用します。まずはルーティングを設定していきましょう
config/routes.rbRails.application.routes.draw do get 'posts', to: 'posts#index' get 'posts/new', to: 'posts#new' post 'posts', to: 'posts#create' #クリエイトアクションへのルーティング設定 endrails routes コマンドでルーティング確認
コントローラーにアクションを設定しよう
app/controllers/posts_controller.rbclass PostsController < ApplicationController def index @posts = Post.all end def new end def create end endビューファイルを作りましょう
app/views/postsディレクトリに、create.html.erbを作り
トップページに戻るリンクを記述しましょう。<h1>投稿完了</h1> <%= link_to 'トップページに戻る', '/posts' %>ブラウザからフォームを送信した時の流れを見てみましょう。
サーバーに情報を渡すためにフォームにパラメーターというものを含ませることができます。params
送られてきた情報をハッシュのような形で格納したものです。
今回はtext_fieldの後に記載されたキーでparamsに格納されています
フォームで入力された情報の値は、params[:キー名]として取り出すことができます。『comment』というキーで『ありがとう』というデータを送信すると
paramsに{comment: ありがとう}といったハッシュ構造で格納されます。
コントローラーでparams[:comment]とすると『ありがとう』というデータが取れます。createメソッドを使ってアクションに保存の処理を書く
createメソッドはActiveRecordメソッドのひとつでデータを作って保存できます。
モデル.create(カラム名: 値)今回はpostsテーブルのcommentというカラム名に、params[:content]の情報を保存します
Post.create(content: params[:content])postsコントローラーに追記しましょう。
class PostsController < ApplicationController def index @posts = Post.all end def new end def create Post.create(comment: params[:comment]) end end以上です。
- 投稿日: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種類で実装してます。
- 投稿日:2021-01-28T09:47:15+09:00
GraphQL-RubyでEnumを活用しよう
はじめに
graphql-rubyでは、EnumType(列挙型)を定義することができます。
本記事では、EnumTypeの利用方法やメリットについて説明します。EnumTypeとは
Railsのenum
Railsでは、ActiveRecordでenumを定義できます。
dbに整数型のカラムに対応した文字列を定義したものです。
例えば、あるUserテーブルに権限を整数型で指定したカラムがあるとすると以下のように書くことができます。usr.rbenum auth: { read: 0, write: 10, admin: 20 }上記のように定義すると、どの数字がどの権限に対応しているかを意識することなく、記述することができます。
GraphQLのEnumType
graphql-rubyのobjectTypeを何も考えずに定義すると以下のようになります。
user_object.rbmodule ObjectTypes class UserType < ObjectTypes::BaseObject field :name, String, null: false field :auth, String, null: false # 文字列として定義される end endgraphql側はAcitiveRecrdでenumが定義されていることは知る由もないので、railsのenumに定義していない文字列でも受け入れてしまいます。そこで、graphql側にもrailsのenumに相当するEnumTypeを定義する必要があります。
まず、graphq/types以下にbase_enum.rbがあることを確認しましょう。デフォルトで入っているかと思います。
そしてbase_enum.rbを継承するauth_enum.rbを作成します。types/auth_enum.rbmodule Types class AuthEnum < Types::BaseEnum value "read" value "write" value "admin" end endこれでEnumTypeが定義できます。
EnumTypeが定義できたので、ObjectTypeにもEnumを適用します。user_object.rbmodule ObjectTypes class UserType < ObjectTypes::BaseObject field :name, String, null: false field :auth, Types::AuthType, null: false # Enumを指定 end end無事にgraphql側にもenumを定義することができました。もちろん、ObjectTypeだけでなくInputTypeにも定義できます。
なぜenumを定義するか
実は、わざわざEnumTypeを定義しなくても実装としては問題ありません。定義されていないenumがフロントエンドから代入された場合には、ActiveRecordが例外を吐いてくれるので、DBに不正な値が入る心配はありません。
EnumTypeを定義するのは、フロントエンドにどのような値が入るのかを前もって知らせるためです。
特に最近のフロントエンドの多くはTypescriptを使用していると思いますので、事前にenumの値で型を作ることができ、より扱いやすいはずです。特にgraphqlのスキーマから自動で型を生成している場合にはとても便利です。enumを定義するとフロントエンドの人が喜ぶと思うのでぜひ定義してあげて下さい。
- 投稿日:2021-01-28T07:01:22+09:00
Rails 6.1対応版: APIモードのRailsに対してCrossOriginなSPAからSession認証する方法
本記事では、APIモードのRailsに対してCrossOriginなSPAからSession(Cookie)認証する方法を解説します。
モダンなフロントエンド開発だと、Auth0やFirebaseを使った認証例が多く見られますが、バックエンドにRailsを使った認証例はまだまだ少ないと感じています。
JWT認証ではなくCookie認証となると、その数はさらに少ないようです。実際にやってみるといろいろな障壁があることがわかったので、やるべきことをまとめることにしました。
背景
Next.jsアプリをVercelに、データ永続化とユーザ認証を目的としたAPIモードのRailsアプリをHerokuにデプロイしています。
この状況でNext.jsアプリからRailsに対して認証を試みると、CrossOriginなリクエストであるが故に様々な障壁があります。
- 本記事に記載する内容はバックエンド側がメインです。(フロントエンド側の実装は需要があれば追々記載予定)
- Railsの認証はdeviseを使用しています
- シンプルなCookieを使ったSession認証なので、本記事に記載している内容はdeviseを使っていなくても同様に実践できる想定です。
- Next.jsではCSRのみを使用しています(SSR, SSG, ISRは使用していない)
- Next.jsに特化した記載は無いので、本記事に記載している内容はNext.jsを使っていなくても同様に実践できる想定です。
やるべきこと
クライアントからのリクエスト時にCookieを送信する
RailsのSessionストレージにCookieを使う前提なので、フロントエンドからのリクエスト時にCookieを送信するよう設定します。
axiosを使っている場合は、以下のようにwithCredentials: true
を指定することで、Cookieを送信できます。// 一部のリクエストでCookieを送信する import axios from 'axios' const res = await axios.get( currentUserPath, { withCredentials: true } // このオプションを追加する ) // 全てのリクエストでCookieを送信する import axiosBase from 'axios' const axios = axiosBase.create({ baseURL: process.env.NEXT_PUBLIC_API_SERVER, headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest', }, withCredentials: true, // このオプションを追加する }) const res = await axios.get(currentUserPath)RailsにてCORS設定する
Gemfileに
rack-cors
を追加します。
rails new
したタイミングでコメントアウトされていることが多いので、コメントを外してください。Gemfile# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible gem 'rack-cors' # コメントを外すGemfileを更新したら
bundle install
しておきましょう。$ bundle install開発環境用のCORS設定を記述します。
config/environments/development.rbrequire 'active_support/core_ext/integer/time' Rails.application.configure do # ・・・省略・・・ # cors config.middleware.insert_before 0, Rack::Cors do allow do origins 'http://localhost:3001' # リクエスト元となるOriginsを記載する resource '*', headers: :any, methods: %i[get post put patch delete options head], credentials: true # trueとすること end end end
origins
にはCORSを許可するオリジン(≒URL)を記載してください。
今回はNext.jsの開発環境サーバがhttp://localhost:3001
だったため、そのオリジンのみ許可しています。
ワイルドカード*
とすることもできますが、セキュリティリスクを最小限とするためにも、許可するオリジンは最小限としたほうが良いです。
headers
やmethods
は必要に応じて見直してください。
credentials
は レスポンスにAccess-Control-Allow-Credentials
ヘッダを付与するため、true
とする必要があります。RailsにてCookie, CookieStoreを使えるようにする
APIモードのRailsでは、RackにてCookieやCookieStoreが有効化されていません。
次のように記述することで、これらを有効化します。config/application.rbmodule SampleApplication class Application < Rails::Application # ・・・省略・・・ # Cookies config.middleware.use ActionDispatch::Cookies # 追加する config.middleware.use ActionDispatch::Session::CookieStore # 追加する end end参考: Rails の API モードでセッションやクッキーを使えるようにする
CookieのSameSite属性をNone, Secure属性をtrueにする
RailsからのレスポンスヘッダーのSameSite属性はデフォルト
Lax
となっています。
CrossOriginでCookieをやり取りするためにはこれをNone
とする必要があります。
rails_same_site_cookie
というgemをインストールすることで、SameSite属性をNone
かつ Secure属性をtrue
にします。Gemfilegem "rails_same_site_cookie", "~> 0.1.8" # 追加するGemfileを更新したら
bundle install
しておきましょう。$ bundle install参考: 【Rails】SameSiteとSecure属性の付与〜Railsのセキュリティ対策〜
しかし、私の環境ではこのGemをインストールしただけではSameSite属性が
None
となりませんでした。
これはRails 6.1で追加された下記オプションが影響しているようです。rails6.1で加わった新しいアプリケーション設定であるnew_framework_defaults_6_1.rbの各項目をさらっと解説
このため、
application.rb
に設定を追記することで、SameSite属性をNone
とします。config/application.rbmodule SampleApplication class Application < Rails::Application # ・・・省略・・・ # Cookies config.middleware.use ActionDispatch::Cookies config.middleware.use ActionDispatch::Session::CookieStore config.action_dispatch.cookies_same_site_protection = :none # 追加する end end先の手順で追加した
rails_same_site_cookie
はSecure属性をtrue
とする役割を担っているので、アンインストールせずに残しておきます。まとめ
以上の方法でCrossOriginなSPAからAPIモードのRailsに対してSession認証ができるようになりました。
プロトコルもフレームワークもブラウザも、セキュリティ強化の一環で制約をかける方向に進化しているが故、一昔前に比べると随分と考慮すべきことが増えたなあという印象です。
参考
- 投稿日:2021-01-28T04:06:30+09:00
【Docker】Rails6を構築+便利コマンド
前提知識
Dockerに関する詳しい解説は下記URLを参照
・【図解】Dockerの全体像を理解するDocker
仮想環境を構築するための道具
イメージ
Dockerコンテナを実行する際に必要なもの
Dockerfile
イメージを作成するためのファイル
コンテナ
仮想環境そのもの
Docker Compose
複数のアプリケーションをまとめて操作できる仕組み(例:DBとWEB、WEBはRailsのこと)
本編
1.アプリ名でフォルダを作成
Consoleyuki@yuki docker-test % pwd /Users/yuki/workspace/docker-test
2.フォルダ直下にsrcファイル、docker-compose.yml、Dockerfileを作成。srcファイル下にGemfileを作成
yuki@yuki docker-test % ls Dockerfile docker-compose.yml src3.Dockerfileを編集
Dockerfile#Rubyのバージョン指定 FROM ruby:2.7 # node.jsやyarnなどのJavascript関連をインストール # 「-qqオプション」はエラー以外何も吐かないことを意味する。 # 「-y」オプションは全部yesで実行することを意味する。 # apt-getはパッケージを管理するコマンド。yumコマンドのようなもの。 RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \ && apt-get update -qq \ && apt-get install -y nodejs yarn #作業ディレクトリーの場所を指定 WORKDIR /app #ローカルのソースコードをapp配下にコピー COPY ./src /app #ruby関連のライブラリ(gem)をインストール RUN bundle config --local set path 'vendor/bundle' \ && bundle installFROM:使用するイメージとバージョン
RUN:コマンドの実行
WORKDIR:作業ディレクトリの設定
COPY:コピー元とコピー先のファイルまたはディレクトリを指定詳しくは下記URLのコードリストを参照
・Dockerfileの書き方と使い方3.Gemfileを編集
src/Gemfilesource 'https://rubygems.org' gem 'rails', '~> 6.1.0'4.docker-compose.ymlを編集
docker-compose.ymlを設定することで一つのコマンドでDBとWEBが両方起動するようになる。
docker-compose.ymlversion: "3" services: db: image: mysql:5.7 #左側のローカルのディレクトリを右側のdocker側のディレクトリに同期する volumes: - ./src/db/mysql_data:/var/lib/mysql #environmentは環境変数の設定 #MYSQL_ROOT_PASSWORDという環境変数にpasswordというパスワードを入れる。Mysqlはパスをードを入れないとエラーになる。 environment: MYSQL_ROOT_PASSWORD: password web: build: . command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" #ローカルのsrcをdockerコンテナ側のappと同期。ローカルのファイルを更新したらコンテナ側も更新されるように設定。同期することによりコンテナを削除してもデータはローカルに記録されているので、再度buildしてもデータが消えない。 volumes: - ./src:/app #ローカルの3000ポートをコンテナ側の3000ポートと同期 ports: - "3000:3000" environment: RAILS_ENV: development #mysqlへ簡単に接続するための設定 depends_on: - dbversion:docker-composeのバージョンを指定
depends_on:依存関係を示していて、起動順を制御できる。ここでは「db→web」へと起動する。
service:Docker composeでは、アプリケーションを動かすための各要素をserviceと呼ぶ。通常はweb(rails)とdb(mysql)と名付ける。4.Railsアプリケーションを作成
docker-compose run web〜はコンテナ側で作動させるコマンド
Consoleyuki@yuki docker-test % docker-compose run web rails new . --force --database=mysql上記のコマンドによりGemfileやDockerファイルが更新される。その様な場合はイメージをbuildし直す必要がある。
Consoleyuki@yuki docker-test % docker-compose build5.データベースを設定
database.ymldefault: &default adapter: mysql2 encoding: utf8mb4 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root #追記 password: password #データベースの接続先をDBに変更 host: db6.コンテナにDBを作成
Consoleyuki@yuki docker-test % docker-compose run web rails db:create7.Railsを起動
Consoleyuki@yuki docker-test % docker-compose up
「you're on rails」画面ができてたら完成。
今後dockerファイルやGemfileを編集した際、情報を反映させるため一度イメージを再構築する必要がある。そのような場合はdocker-compose buildを実行する。Dockerでよく使うコマンド一覧
console#イメージのビルド docker-compose build #コンテナの作成と起動(ーdはバックグラウンドで行うためのオプション) docker-compose up -d #コンテナを停止・削除 docker-compose down #コンテナの一覧を表示 docker-compose ps #ログを表示 docker-compose logs #コンテナを作成してコマンド実行 #runコマンドではイメージの構築から、コンテナの構築・起動までしてくれる。 docker-compose run <サービス><コマンド> #起動中のコンテナにコマンド実行 docker-compose exec <サービス><コマンド> #コンテナの中に入る(exitで抜ける) docker-compose exec web /bin/bash参考
・Docker ComposeでRailsを構築しよう
・docker-compose upしたときに「A server is already running.」って言われないようにする・mysql8を使いたい場合は下記コードを参照
database.ymlversion: '3' services: db: image: mysql:8.0 command: --default-authentication-plugin=mysql_native_password volumes: - ./src/db/mysql_data:/var/lib/mysql environment: MYSQL_ROOT_PASSWORD: password #省略