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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

class User
  attr_reader :first_name, last_name, :age

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

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

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

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

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

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

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

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

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

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

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

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

class User
  attr_reader :first_name, :last_name, :age

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

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

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

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

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

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

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

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

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

①設計

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

ポイント

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

②カプセル化

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

③継承

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

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

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

まとめ

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

 

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

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])
image.png

画像の通り、正常に動作しています。

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/ --delete

bundle 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.css

app/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

これで、アプリ製作の下準備は完了です!

参考

現場で使える Ruby on Rails 5速習実践ガイド

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

Ruby on RailsとSass

初めに

RailsではCssの他にSassを使うことができます。Sassとは何か。そしてCssとは何が違うのかについて調べてみたら、Cssよりも遥かに効率的かつ視覚的に見やすくコードを書くことができるなどのメリットがあることが分かってきました。こちらの記事を参考にCssではなく、Sassという書き方を知っていただき活用いただければと思います。

目次

  • Sassとは
  • RailsにおけるSassの導入方法
  • Sassのメリットについて
  • 参考サイト

Sassとは

スクリーンショット 2021-01-28 21.17.27.png

Sassはアメリカのハンプトン・キャトリンとネイサン・バイゼンバウムによって開発されたスタイルシート言語です。 Syntactically Awesome StyleSheetの略であり、その意味はSyntactically-構文的に、Awesome-素晴らしいStyleSheet-スタイルを適用するシートという意味になります。ここでポイントになるのが、Syntactically-構文的にというところにありますが、Cssの書き方を画期的に改変したものになります。

RailsにおけるSassの導入方法

SassはCssの拡張言語であり、利用するにはSassで書いた後にCssにコンパイルする必要があります。コンパイルの仕方についてはいろいろありますが、Vscodeを利用する場合にはextensions(拡張機能)の一つである「Live Sass Compiler」を使ってCssファイルに変えます。以下、ダウンロードサイトになりますので、ご覧ください。スクリーンショット 2021-01-28 21.29.39.png
ダウンロードはこちら
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ではコンパイルが無しに使えるので是非試してみてはいかがでしょうか。

参考サイト

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

f.collection_selectにclassを設定する

やりたいこと

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

失敗例

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

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

成功例

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

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

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

Railsで再帰的に自分自身を参照する外部キー制約を設定する方法

答えは全て↓にあります。
アプリケーションのみで参照をコントロールするver
DBにも外部キー制約を付与するver

このユースケースに特化した情報があまりない中で、非常に良記事なのですが、Google検索で引っかかりにくかったので、
どうせまた調べる未来の自分がまたアクセスしやすいように備忘録としてこの記事を作成しました。

未来の自分がこのユースケースで調べるときの検索ワード
「Rails 外部キー 再帰」
「Rails 外部キー 自分自身」
「Rails 外部キー 自分」
「Rails 外部キー 自身」
「Rails 外部キー 自分 参照」
「Rails 外部キー制約 再帰」
「Rails 外部キー 再帰 マイグレーション」

鬼門

  • 再帰関数がヒットすること
  • 再起するSQL文の作成(テーブル作成部分は前提の上で)
  • rails以外の用途
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ruby 3.0 × railsでminitest起動するとerrorになる場合

gem 'rexml'

をインストール

理由

ruby 3.0.0からrexmlがdefault gemではなくなったため。

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

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

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

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

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

gem 'nokogiri'

で、

bundle install

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

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

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

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

which ruby 
/usr/bin/ruby

本当だ。。

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

rbenv global 2.6.3

そうすると、

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

いいじゃないですか✨

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

gem install bundler

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

try passing them all to `bundle update`

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

You have requested:
  listen = 3.1.5

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

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

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

bundle update listen

したら、

You have requested:
  spring = 2.1.0

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

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

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

bundle update spring

すると

You have requested:
  listen = 3.1.5

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

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

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

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

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

すると、、

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

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

brew install postgresql

そうして、

bundle install

成功!

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

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

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

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

rails webpacker:install 

でけたでけた

さあ、rails sしてみるぜ。

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

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

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

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

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

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

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

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

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

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

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

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

ActiveRecordとは

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

種類と書き方

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

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

Title.all

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

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

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

Title.new

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

Title.save

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

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

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

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

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

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

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

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

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

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

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

目標

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

開発環境

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

前提条件

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

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

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

Geolocation APIを有効化

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

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

実装

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

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

コントローラーの編集

gon.studiosを定義します。

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

ビューの編集

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

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

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

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

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


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

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

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

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

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

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

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

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

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

おわりに

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

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

10.png

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

11.png

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

12.png

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

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

参考記事

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

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

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

4.2.1 文字列

4.2.1 - 1

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

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

4.2.1 - 2

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

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

4.2.1 - 3

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

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

4.2.1 - 4

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

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

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

4.2.2 - 1

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

> "racecar".length
 => 7 

4.2.2 - 2

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

> "racecar".reverse
 => "racecar" 

4.2.2 - 3

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

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

4.2.2 - 3

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

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

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

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

4.2.3 メソッドの定義

4.2.3 - 1

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

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

4.2.3 - 2

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

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

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

4.2.3 - 3

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

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

4.3.1 配列と範囲演算子

4.3.1 - 1

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

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

4.3.1 - 2

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

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

4.3.1 - 3

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

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

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

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

4.3.1 - 4

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

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

> a[-7]
 => "t" 

4.3.2 ブロック

4.3.2 - 1

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

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

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

4.3.2 - 2

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

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

4.3.2 - 3

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

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

4.3.2 - 4

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

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

4.3.3 ハッシュとシンボル

4.3.3 - 1

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

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

4.3.3 - 2

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

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

4.3.3 - 3

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

4.3.3 - 4

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

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

4.4.1 コンストラクタ

4.4.1 - 1

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

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

リテラルコンストラクタ

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

4.4.1 - 2

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

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

4.4.1 - 3

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

> a == b
 => true

4.4.2 クラス継承

4.4.2 - 1

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

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

# Hash, Symbolクラスも同様

4.4.2 - 1

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

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

4.4.3 組み込みクラスの変更

4.4.3 - 1

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

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

4.4.3 - 2

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

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

4.4.3 - 3

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

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

4.4.4 コントローラクラス

4.4.4 - 1

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

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

4.4.4 - 2

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

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

4.4.5 ユーザークラス

4.4.5 - 1

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

class User
  attr_accessor :first_name, :last_name, :email

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

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

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

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

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

4.4.5 - 2

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

> user.alphabetical_name
 => "toon, mou"

4.4.5 - 3

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

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

memo

unless文

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

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

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

配列と範囲オブジェクト

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

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

さいごに

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

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

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

はじめに

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

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

環境

rails 5.2.4

前提

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

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

ランキング機能実装

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

解説

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

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

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

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

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

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

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

1位は、

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

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

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

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

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

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

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

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

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

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

【Rails】星★を用いた評価機能の実装

はじめに

Railsを用いたオリジナルアプリケーションを開発していた時の事。
投稿する際に、星を用いた評価機能があるとかっこいいと思いトライしてみたのですが
とても苦戦したので、振り返ることができるよう書き留めておきます。

実現したい事

  • 投稿ページで★を用いた評価をする。
    raty_post.gif

  • 一覧表示ページで、投稿時に設定された★の数を表示。
    rate_view.png

postsテーブルの作成

postsテーブルにrateカラムを作成します。
今回、1.53.5 のように、0.5刻みで評価値を設定したいので、カラムの型はfloat型とします。

jQueryの導入

まずはGemfileに以下の記述を加え、忘れずにbundle installします。

Gemfile
gem 'jquery-rails'

次にapplication.jsに次のコードを追記します。

application.js
window.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.js
window.jQuery = window.$ = require('jquery')

require("../jquery_raty") //追加

requireする際のjquery_raty.jsのパスはファイルの配置位置によって人それぞれなので注意です。

画像

下記のGitHubから画像を持ってきます。
star-half.pngstar-off.pngstar-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はお好みで設定してみてください。

まとめ

星★を用いた評価機能を実装するだけで、見た目がガラッと変わり感動しました。
苦戦はしましたが、他にも実装したい機能あればどんどん挑戦していきたいです。

参考

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

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

開発環境

Mac OS Catalina 10.15.7
ruby 2.6系
rails 6.0系

前提

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

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

users テーブル

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

Association

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

postsテーブル

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

Association

  • belongs_to :user
  • has_many :comments

commentsテーブル

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

Association

  • belongs_to :user
  • belongs_to :post

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

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

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

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

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

form_withをremote: trueにする

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

変更前

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

変更後

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

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

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

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

変更前

comments_controller.rb
class CommentsController < ApplicationController

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

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

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

end


変更後

comments_controller.rb
class CommentsController < ApplicationController

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

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

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

end

create.js.erbを編集する

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

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

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

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

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

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

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

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

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

目標

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

開発環境

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

前提条件

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

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

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

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

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

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

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

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

Gemを導入する

Gemfile
gem 'gon'
gem 'geocoder'

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

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

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

モデルの編集

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

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

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

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

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

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

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

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

$ bin/rails g geocoder:config

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

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

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

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

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

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

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

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

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

コントローラーの編集

studios_controller.rbを編集

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

ビューを編集

application.html.erbを編集

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

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

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

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

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


<script>
  let map;

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

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

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

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

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

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

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

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

ハマったエラー

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

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

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

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

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

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

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

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

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

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

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

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

参考記事

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

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

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

目標

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

開発環境

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

前提条件

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

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

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

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

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

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

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

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

Gemを導入する

Gemfile
gem 'gon'
gem 'geocoder'

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

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

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

モデルの編集

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

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

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

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

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

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

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

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

$ bin/rails g geocoder:config

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

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

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

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

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

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

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

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

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

コントローラーの編集

studios_controller.rbを編集

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

ビューを編集

application.html.erbを編集

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

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

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

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

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


<script>
  let map;

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

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

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

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

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

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

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

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

ハマったエラー

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

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

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

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

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

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

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

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

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

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

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

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

参考記事

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

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

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

はじめに

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

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

開発環境

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

解決方法

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

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

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

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

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

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

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

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

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

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

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

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

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

検索機能を実装する

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

まずは方針を決めます

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

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

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

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

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

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

⑥検索した時のviewの実装

SearchFormクラスの作成

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

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

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

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

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

解説していきます。

  include ActiveModel::Model

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

  include ActiveModel::Attributes

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

ルーティングの設定

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

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

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


collectionとmemberの違い

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

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

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

検索するメソッドを作成

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

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

解説していきます。

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

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

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

SearchFormクラスの実装

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

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

        private

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

解説していきます。

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

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

POSIX文字クラスって何?

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


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

  scope = Post.distinct

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

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

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

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

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

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

Image from Gyazo

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

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

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

controllerの設定

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

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

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

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

解説していきます。

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

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

  before_action :set_search_posts_form

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

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

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

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

viewの実装

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

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

解説します。

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

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

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

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

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

参照

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

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

【銀座Railsとは?】

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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で共有できる
Image from Gyazo
各動画は/(スラッシュ)以降の文字列により個別に分類されている
なので上記の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.rb
def 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にある「ツイートを埋め込む」から作ることができる

Image from Gyazo

ツイートを埋め込むを押すと以下のコードが取得できる

<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&amp;ref_src=twsrc%5Etfw">#夢をえがき計画をたて行動する</a> <a href="https://t.co/YlwaMKkiRp">pic.twitter.com/YlwaMKkiRp</a></p>&mdash; セサミストリート公式 (@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"

と書くことができる

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

validateに条件分岐を設定する

validateに条件分岐を設定する

 validateにifやunlessの条件分岐を設定する際の書き方。

validate :判定に使用するメソッド, if: -> { 条件 }
# 具体例
validate :hoge, if: -> { Rails.env.production? }

動作確認

 以下で選択したレコードに対してバリデーションの判定を行う。

バリデーションを設定したクラス.find(動作確認に使用するレコード).valid?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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

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

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

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

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

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

まず

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

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

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

つぎに

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

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

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

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

本題

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

ActiveModel::Dirtyのchanged?の挙動

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

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

class Model < AR::Base
  after_save :callback

  def callback
    p changed?
  end
end

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

終わりに

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

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

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

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

基礎知識

メモ

変数

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

ifによる分岐

if <条件式> then
  <文>
end

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

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

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

whileでの繰り返し

while <条件式> do
  <文>
end

メソッド定義

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

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

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

演算子・計算順序

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

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

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

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

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

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

となる。

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

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

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

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

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

Qiita

参考記事

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

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

Ruby on Railsでアプリを作ってみよう⑧

データを作って保存する処理を行っていきます。
テーブルにデータを保存するため、createアクションを使用します。

まずはルーティングを設定していきましょう

config/routes.rb
Rails.application.routes.draw do
  get 'posts', to: 'posts#index'
  get 'posts/new', to: 'posts#new'
  post 'posts', to: 'posts#create' #クリエイトアクションへのルーティング設定
end

rails routes コマンドでルーティング確認

image.png
ルーティングが設定されました。

コントローラーにアクションを設定しよう

app/controllers/posts_controller.rb
class 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' %>

image.png

ブラウザからフォームを送信した時の流れを見てみましょう。
サーバーに情報を渡すためにフォームにパラメーターというものを含ませることができます。

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

これで終了です。
最後に動作確認をしましょう。
mubi.gif

以上です。

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

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

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

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

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

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

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

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

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

5 -7 = -2

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

まとめ

0より小さい数になった数を見せたいんだけど、マイナスの記号はいらないから、マイナスを取った数を表してくれるメソッド。
大人の言い方で、「絶対値を求める」メソッド。

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

ActiveRecordのメソッド

ポートフォリオにてログイン機能を実装している。
その中でActiveRecordのコマンドを打つことがあるが、いまいちActiveRecordがわかっていない。

RailsでModelを作成するとはActiveRecord::Baseというクラスを継承していて、このクラスの中にどのようなメソッドがあるのか?

自分用にまとめておく。

ActiveRecord

ActiveRecordはRuby on Railsで使われているOR Mapper(オブジェクト リレーショナルデーターベース マッパー)
のこと。

モデルとテーブルをつなぎ合わせて、SQL文ではなく短いコードでデーターベースの参照したり、編集したりすることができる。

$ rails generate model User name:string email:string

このコマンドを実行するとmodelが作成される。

app/models/user.rb
class User < ApplicationRecord
end

冒頭で述べたように
Ruby on Railsでmodelを作成するとActiveRecord::Baseを継承している。

このモデルクラスであるUserを例にしてメソッドについてまとめる。

ActiveRecord::Baseのメソッドを何があるか

User.new( name: "takahasi", email: "takahasi@example.com" )
#Userモデルに新規ユーザーを作成

User.save
#新規ユーザーをDBへ保存

User.create( name: "takahasi", email: "takahasi@example.com" )
#newとsave行う

User.find(1)
#DBからidが1のユーザーを取得する

User.find_by( email: "takahasi@example.com" )
#DBから条件に該当する一番目のユーザーを取得する。

User.where(email: "takahasi@example.com")
#DBから条件に該当するすべてのユーザーを取得する

#find_byは条件に合うデーターを一つだけ、whereはすべてのデーターを取得する。

User.update
#ユーザーを更新する

#使い方
user = User.find_by(name: "takahasi")
user.name = "sato"
user.save

#上をこのように書ける
user = User.find_by(name: "takahasi")
user.update(name: "sato")


User.destroy
#ユーザーの削除

User.limit(6)
#DBからidの若い順に6つ取得する

User.order("created_at DESC")
#作成日時の新しい順にユーザーを並び替えて取得
#3人ユーザーがいたら、3.2.1の順で並び替え

User.order("created_at ASC")
#作成日時の古い順に取得
#3人ユーザーがいたら、1.2.3で並び替え

ORMapper、DBの扱いは必須なので、もっと理解したい。

参考

https://qiita.com/penguin_note/items/adb0b9bf7c13c1b1d44d

https://qiita.com/tsuchinoko_run/items/f3926caaec461cfa1ca3

https://railsguides.jp/active_record_basics.html

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

enum(列拳型)の使い方をまとめました。(Rails)

enumとは

enumはRubyではなくRailsに導入された便利な型です。
enumを使うことで文字列と値を紐付けができます。
データベースに格納される値が決まるため
定義していないものはデータベースに保存できなくなります。

使用例

order.rb
enum address:[:"自宅", :"登録済みの住所から", :"新しいお届け先"]
enum payment_method:{ "ポイント支払い": 0, "コンビニ支払い": 1, "クレジットカード": 2, "電子マネー": 3, "着払い": 4 }

この記述をモデルに書くこと
書き方は2種類あり、
addressの方は0から順番に番号が振り分けられる。
payment_methodの方は番号を指定して決められます。(0から指定する必要があります)
次に、Viewは、

order/new.html.erb
<%= form_with model: @orders, url: public_orders_confirm_path, method: :get,local: true do |f| %>
 <%= f.label :address, "お届け先の住所" %>

 <%= f.radio_button :address, 0, checked: "checked" %>
 <%= f.label :address, "自宅" %>

 <%= f.radio_button :address, 1 %>
 <%= f.label :address, "登録済みの住所から" %>

 <%= f.radio_button :address, 2 %>
 <%= f.label :address, "新しいお届け先" %>

 <%= f.label :payment_method, "支払い方法" %>
 <%= f.select :payment_method,
     [
      ['ポイント支払い'], 
      ['コンビニ支払い'],
      ['クレジットカード'], 
      ['電子マネー'], 
      ['着払い']
     ],prompt: "クレジットカード" %>
  <%= f.submit "更新", class: "btn btn-sm btn-success" %>
<% end %>

ラジオボタンとセレクターの2種類で実装してます。

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

GraphQL-RubyでEnumを活用しよう

はじめに

graphql-rubyでは、EnumType(列挙型)を定義することができます。
本記事では、EnumTypeの利用方法やメリットについて説明します。

EnumTypeとは

Railsのenum

Railsでは、ActiveRecordでenumを定義できます。
dbに整数型のカラムに対応した文字列を定義したものです。
例えば、あるUserテーブルに権限を整数型で指定したカラムがあるとすると以下のように書くことができます。

usr.rb
enum auth: { read: 0, write: 10, admin: 20 }

上記のように定義すると、どの数字がどの権限に対応しているかを意識することなく、記述することができます。

GraphQLのEnumType

graphql-rubyのobjectTypeを何も考えずに定義すると以下のようになります。

user_object.rb
module ObjectTypes
  class UserType < ObjectTypes::BaseObject
    field :name, String, null: false
    field :auth, String, null: false # 文字列として定義される
  end
end

graphql側はAcitiveRecrdでenumが定義されていることは知る由もないので、railsのenumに定義していない文字列でも受け入れてしまいます。そこで、graphql側にもrailsのenumに相当するEnumTypeを定義する必要があります。
まず、graphq/types以下にbase_enum.rbがあることを確認しましょう。デフォルトで入っているかと思います。
そしてbase_enum.rbを継承するauth_enum.rbを作成します。

types/auth_enum.rb
module Types
  class AuthEnum < Types::BaseEnum
    value "read"
    value "write"
    value "admin"
  end
end

これでEnumTypeが定義できます。
EnumTypeが定義できたので、ObjectTypeにもEnumを適用します。

user_object.rb
module 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を定義するとフロントエンドの人が喜ぶと思うのでぜひ定義してあげて下さい。

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

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なリクエストであるが故に様々な障壁があります。

Untitled_Portfolio_Site_-_Cacoo.png

  • 本記事に記載する内容はバックエンド側がメインです。(フロントエンド側の実装は需要があれば追々記載予定)
  • 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.rb
require '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 だったため、そのオリジンのみ許可しています。
ワイルドカード * とすることもできますが、セキュリティリスクを最小限とするためにも、許可するオリジンは最小限としたほうが良いです。

headersmethods は必要に応じて見直してください。
credentials は レスポンスに Access-Control-Allow-Credentials ヘッダを付与するため、 trueとする必要があります。

参考: RailsでAPIにCORSを設定する

RailsにてCookie, CookieStoreを使えるようにする

APIモードのRailsでは、RackにてCookieやCookieStoreが有効化されていません。
次のように記述することで、これらを有効化します。

config/application.rb
module 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 にします。

Gemfile
gem "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.rb
module 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認証ができるようになりました。

プロトコルもフレームワークもブラウザも、セキュリティ強化の一環で制約をかける方向に進化しているが故、一昔前に比べると随分と考慮すべきことが増えたなあという印象です。

参考

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

【Docker】Rails6を構築+便利コマンド

前提知識

Dockerに関する詳しい解説は下記URLを参照
・【図解】Dockerの全体像を理解する

Docker

仮想環境を構築するための道具

イメージ

Dockerコンテナを実行する際に必要なもの

Dockerfile

イメージを作成するためのファイル

コンテナ

仮想環境そのもの

Docker Compose

複数のアプリケーションをまとめて操作できる仕組み(例:DBとWEB、WEBはRailsのこと)

本編

1.アプリ名でフォルダを作成

Console
yuki@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  src

3.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 install

FROM:使用するイメージとバージョン
RUN:コマンドの実行
WORKDIR:作業ディレクトリの設定
COPY:コピー元とコピー先のファイルまたはディレクトリを指定

詳しくは下記URLのコードリストを参照
・Dockerfileの書き方と使い方

3.Gemfileを編集

src/Gemfile
source 'https://rubygems.org'
gem 'rails', '~> 6.1.0'

4.docker-compose.ymlを編集

docker-compose.ymlを設定することで一つのコマンドでDBとWEBが両方起動するようになる。

docker-compose.yml
version: "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:
      - db

version:docker-composeのバージョンを指定
depends_on:依存関係を示していて、起動順を制御できる。ここでは「db→web」へと起動する。
service:Docker composeでは、アプリケーションを動かすための各要素をserviceと呼ぶ。通常はweb(rails)とdb(mysql)と名付ける。

4.Railsアプリケーションを作成

docker-compose run web〜はコンテナ側で作動させるコマンド

Console
yuki@yuki docker-test % docker-compose run web rails new . --force --database=mysql

上記のコマンドによりGemfileやDockerファイルが更新される。その様な場合はイメージをbuildし直す必要がある。

Console
yuki@yuki docker-test % docker-compose build

5.データベースを設定

database.yml
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  #追記
  password: password
  #データベースの接続先をDBに変更
  host: db

6.コンテナにDBを作成

Console
yuki@yuki docker-test % docker-compose run web rails db:create   

7.Railsを起動

Console
yuki@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.yml
version: '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
   #省略
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む