20210121のRubyに関する記事は21件です。

初めての投稿

今日から本格的にプログラミングの勉強を始めました!
分からない事だらけですが挫けず継続します

初日 ゼロからわかるRuby超入門 P.100まで!

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

slickを用いた画像スライド

[概要]

1.結論
2.なぜslickを使うのか
3.完成形
4.どのように使うのか
5.ここから学んだこと

1.結論

slickの公式サイトからダウンロードして実装する

2.なぜslickを使うのか

簡単に実装できるからです。具体的にいうと公式サイトからファイルをダウンロードをして、必要なものを自分のフォルダの中に挿入して、あとは公式のところから載っているものから自分が使用たいデザインを選んで、実装していくだけだからです。

3.完成形

d98aa278e3b051dd8838dcc30161447f.gif

4.どのように使うのか
1)まず表示させたいビューファイルに画像をいくつか記述します。

index.html.erb
    <div class="slider"> 
      <div><%= image_tag 'S__19701764.jpg' %></div>
      <div><%= image_tag 'S__19701765.jpg' %></div>
      <div><%= image_tag 'S__19701762.jpg' %></div>
    </div>

2)次にslickの公式サイトに飛び(https://kenwheeler.github.io/slick/)get it nowのボタンを押してDownload Nowのボタンを押してダウンロードする。

414a731f179e11742d52ccd935624bc2.gif

3)ダウンロードしたファイルを解凍し、ファイルを自分のフォルダに追加する。
ダウンロードして解凍したファイルを開きslickのファイルを開く。
その中のファイルのajax-loader.gif, fonts, slick-theme.css, slick.css を自分のアプリのCSSファイルに挿入

Image from Gyazo

4)jsフォルダに上記の画像のslick.jsを追加

5)application.html.erbに追加したファイルを記述
一番上にあるajax.googleapis.comはJQueryの読み込みをしている。

application.html.erb
    <head>
     <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> 
    <script type="text/javascript" src="//cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick.min.js"></script>
    <link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick.css"/>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.9.0/slick-theme.min.css">
    </head>

6)最後に最初のビューファイルに記述した画像の記述の下にjavaScritを記述
この記述は自分がデザインしたい記述をそのままコピーして記述する。私はこのデザインをしたかったのでこの記述をコピーしました!!

Image from Gyazo

index.html.erb
    <div class="slider"> 
      <div><%= image_tag 'S__19701764.jpg' %></div>
      <div><%= image_tag 'S__19701765.jpg' %></div>
      <div><%= image_tag 'S__19701762.jpg' %></div>
    </div>
     <script>
      $('.slider').slick({
      autoplay:true,
      autoplaySpeed:3000,
      dots:true,
    });

7)完成!!
これだけで3のような完成形が作られます!!
私はindex.html.erbに記述しましたが、application.html.erbや他のhtmlファイルに記述することもできます。

5.ここから学んだこと

最初はうまく写真が反映されなかったりjsが動いてなかったりして苦労しましたが、しっかりと検証ツールを使いどこが間違っているのかを確認することができました!!slick自体の導入はシンプルで簡単なので是非試してみてください!!
 このほかにもたくさんのデザインと自分の記述次第で好きなようにアレンジできるので試してみてください!!

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

#Rails redirect_toではhttpリクエストをgetからdeleteに変更できない!?

Rails redirect_toではhttpリクエストをgetからdeleteに変更できない!?

rails 5.2.2
を使ってます。

今回はユーザーの退会ボタンを押したら退会処理がなされてかつ、ログアウトされ、ログイン画面に飛ばすような設計にしてそれを実装しようとしていたのですが、そこで問題が発生しました!!

それは退会処理後のログアウトを呼び出そうとして
redirect_to user_sessions_path
を実行したところ、「httpリクエストがgetですよ」とのことでdeleteに修正しようとしたが
残念
redirect_to user_sessions_path, method: :delete
ではできなかった
これ以上解決方法が浮かばなかったのでいろいろ調べたところ
deleteになってしまった場合はstatusコードを編集することでgetにできるらしいが
getからdeleteは無理なようだ・・・
postは指定できないことは知っていたがまだredirect_toに弊害があったとは・・・

皆さんもきおつけてくださいね
僕はこれに2時間近く囚われてしまい悔しいです笑笑

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

【Rails】【Controllerから外部APIを叩く】 OpenWeatherMap APIで天気情報を取得

はじめに

外部APIを叩くという経験が初めてだったので、どんな大変な作業が待っているのだろうと心構えをしていたのですが、驚いたことに
実は難易度が低くて、高機能が実装できるハイコスパな方法でした。
今回実装した機能の概要は以下の通りです。メモ程度ですが参考程度に載せておきます。

API概要.jpg

前提

  • deviseでユーザーに関する機能作成済み
  • food_record(料理記録)モデルを作成済み
  • new, createなどの基本的な機能は既に実装済み

環境構築

  • Ruby2.7
  • Ruby on Rails6

API_KEYの取得

OpenWeatherMapで会員登録して、API_KEYを取得します。こちらは、多くの記事が出回っているので、割愛させて頂きます。
https://qiita.com/matsubishi5/items/fcd77eacb0ed111299e2
↑私はこちらを参照しました。

http://api.openweathermap.org/data/2.5/weather?q=Tokyo&appid=
末尾に取得したAPI_KEYを貼り付けて、ブラウザで実行してみてください。東京の今の天気が返ってくるはずです。これだけで、APIってこんなものかと実感できますし、疎通確認の意味でもやっておいた方が無難です。
ちなみに、会員登録からAPI_KEYが使えるようになるまで数時間程度かかります。(いくぞうは、3時間くらいでした)

必要なライブラリをインストール & API_KEYを格納

Gemfile.
#API_KEYを環境変数として管理する(Keyを外部流出させないための措置)
gem 'dotenv-rails'
#アプリケーション内でHTTPリクエストを投げたい場合に使うクラス
gem 'httpclient'

Gemfile記載後、bundle installします。



..env.
OPEN_WEATHER_MAP_API = 'API_KEY貼り付け'
URI = 'https://api.openweathermap.org/data/2.5/weather'

.envには、前もって取得したAPI_KEYをコピペします。

.gitignore.
/.env

ここで絶対に忘れずに.gitignoreを作ってください。.envをgithubにpushした場合、全世界にAPI_KEYを晒すことになってしまいます。(私は1回やらかしました、、、)

APIを叩く処理

controllerでAPIを叩きます。
- Api::OpenWeatherMap::Requestという処理がありますが、こちらで、下記モジュール(request.rb)を呼び出しています。
- コードがブサイクでごめんなさい!!!!でもちゃんと動きます。

food_records_controller.rb
def create

    #料理記録
    @food_record = current_user.food_records.build(food_record_params)
    @food_record.food_date = Time.zone.today

    #天気API
    #リクエストを出す
    open_weather = Api::OpenWeatherMap::Request.new(current_user.location_id)
    #戻り値を受け取る
    response = open_weather.request
    if @food_record.valid?
      #APIが正常に動作した場合
      if response['cod'] == 200
        params_weather = Api::OpenWeatherMap::Request.attributes_for(response)
        @food_record.update(params_weather)
        flash[:notice] = "登録に成功しました"
      else
        #APIが正常に動作しない場合も料理情報は記録する
        flash[:notice] = "天気情報の取得に失敗しましたが、登録に成功しました"
      end
      redirect_to root_url
    else
      render 'new'
    end
  end



- 処理本体は、モジュールに書きました。
- 今回、取得する天気の地域としてlocation_idを使用していますが、'id: location_id'をq: 'Tokyo'というように変えても上手くいきます。

lib/api/open_weather_map/request.rb
module Api
  module OpenWeatherMap
    class Request
      attr_accessor :query

      def initialize(location_id)
        @query = {
          id: location_id,
          units: 'metric',
          appid:
          'OPEN_WEATHER_MAP_API']
        }
      end

      def request
        client = HTTPClient.new
        request = client.get(ENV['URI'], query)
        JSON.parse(request.body)
      end

      # 取得したjsonをparamsに変換
      def self.attributes_for(attrs)
        {
          weather_main: attrs['weather'][0]['main'],
          weather_description: attrs['weather'][0]['description'],
          weather_icon: attrs['weather'][0]['icon'],
          weather_id: attrs['weather'][0]['id'],
          temp: attrs['main']['temp'],
          temp_max: attrs['main']['temp_max'],
          temp_min: attrs['main']['temp_min'],
          humidity: attrs['main']['humidity'],
          pressure: attrs['main']['pressure']
        }
      end
    end
  end
end



lib以下はデフォルトではtask以外読み込まれないので、config/application.rbに、以下の設定が必要です

config/application.rb
config.paths.add 'lib', eager_load: true

完成

スクリーンショット 2021-01-21 21.32.30.png

<%= image_tag "http://openweathermap.org/img/wn/#{@food_record.weather_icon.chop}d@2x.png" %>
アイコンはこのように表示させています。chop処理をしているのは、夜に天気を取得すると、太陽が黒色になってしまうからです。
{@food_record.weather_icon.chop}d このようにchopで末尾のnを削って、d「昼間(おそらくdaytime)」を付け足しています。

補足

  • 今回はlocation_idを使用しました。私は、下記のCSVを読み込ませて、cityテーブルに初期データとして投入して使用しています。
  • 都市名, location_id, 緯度, 経度 の順になっています。
  • 説明は割愛しますが、下記のようにすれば簡単に実装できます。
db/csv/cities.csv
札幌,2128295,141.346939,43.064171
青森,2130658,140.740005,40.82444
盛岡,2111834,141.152496,39.703609
仙台,2111149,140.871933,38.26889
秋田,2113126,140.116669,39.716671
山形,2110556,140.363327,38.240559
福島,2112923,140.467773,37.75
水戸,2111901,140.446671,36.341389
宇都宮,1849053,139.883606,36.56583
前橋,1857843,139.060837,36.391109
さいたま,1853226,35.857208
千葉,2113015,140.123337,35.604721
東京,1850147,139.691711,35.689499
横浜,1848354,139.642502,35.447781
新潟,1855431,139.023605,37.902222
富山,1849876,137.211395,36.695278
金沢,1860243,136.625565,36.59444
福井,1863983,136.225174,35.850101
山梨,1848649,138.608002,35.61602
長野,1856210,138.040771,36.13464
岐阜,1863640,137.053986,35.78091
静岡,1851715,138.325424,35.025219
名古屋,1856057,136.906403,35.181469
津,1849796,136.508606,34.730282
大津,1853574,135.868332,35.00444
京都,1857910,135.753845,35.021069
大阪,1853909,135.502182,34.693741
神戸,1859171,135.182999,34.691299
奈良,1855612,135.804855,34.685051
和歌山,1926004,135.167496,34.226109
鳥取,1849890,133.850815,35.367859
松江,1857550,133.050568,35.472221
岡山,1854383,133.934998,34.661671
広島,1862415,132.459366,34.396271
山口,1848689,131.47139,34.185829
徳島,1850158,134.559433,34.06583
高松,1851100,134.043335,34.340279
松山,1926099,132.765747,33.839161
高知,1859146,133.531113,33.559719
福岡,1863967,130.41806,33.606392
佐賀,1853303,130.298798,33.249321
長崎,1856177,129.873611,32.74472
熊本,1858421,130.741669,32.789719
大分,1854487,131.612503,33.23806
宮崎,1856717,131.423889,31.91111
鹿児島,1860827,130.558136,31.560181
那覇,1856035,127.681107,26.2125

db/seeds.rb
require "csv"
CSV.foreach('db/csv/cities.csv') do |row|
  City.create(
    name: row[0],
    location_id: row[1],
    lon: row[2],
    lat: row[3]
  )
end



上記ファイルを作って、cityテーブルも作成済みならば、下記コマンドを実行します。

rails db:seed

最後に

少しでも参考になればLGMTよろしくお願いします。
また、説明不十分な点や、説明が間違っている箇所が御座いましたら、コメントいただけると幸いです。

Twitterもやっているので、よかったらフォローお願いします!!
https://twitter.com/engineer_ikuzou

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

RailsでPostgreSQLを使うぞ!「FATAL: role "postgres" does not existの解消」にむけて

はじめに

Railsチュートリアなんとか一通り終え(理解度としては3割程度なので終えたと言えるかどうかはおいといて...)、
自身のローカル環境にてRuby, Railsの環境を構築する際に発生した問題に関して、自身のメモのため、初学者の方で同様の問題が発生した方向けに投稿します!

開発環境

  • macOS Catalina 10.15.7
  • MacBook Pro (13-inch, 2020, Two Thunderbolt 3 ports)
  • プロセッサ 1.4 GHz クアッドコアIntel Core i5
  • メモリ 8GB

  • Ruby 2.5.7

  • Rails 5.2.3

  • PostgreSQL 13.1

導入までの一連の流れに関して

一連の環境構築までの流れに関しては下記記事が大変参考になると思いますので、ぜひ見てください。
今回はPostgreSQLの導入部にフォーカスして記事を作りたいと思います!

【完全版】MacでRails環境構築する手順の全て
https://qiita.com/kodai_0122/items/56168eaec28eb7b1b93b

エラー FATAL: role "postgres" does not exist の発生

ここからが本題です!
一通り上記記事を参考に環境構築を終え、railsを起動しようとしたところ、

$ rails s

下記のようなエラーが発生し、railsをが起動できない状況に...

スクリーンショット 2021-01-20 22.14.39(2).png

ん?そもそもロールってなに?
詳しくは下記が参考になると思いますが、

PostgreSQL 11.5文書
https://www.postgresql.jp/document/11/html/role-attributes.html

「PostgreSQLは、ロールという概念を使用してデータベースへの接続承認を管理する」らしい。
つまり、role postgres does not existというのは、ロール(データベースへアクセスする権限みたいなもの?)として、postgreがないよっていことで解釈しました。

そこで実際に下記コマンドを使用して、データベースのロール状態を確認すると...

$ psql postgres
psql (11.4)
Type "help" for help.

postgres=# \du         # 現在のロール確認(ちなみにバッククオート\は「option+¥」キーですよ)
Role name   |                         Attributes                         | Member of 
---------------+------------------------------------------------------------+-----------
 araishuntarou | Superuser, Create role, Create DB, Replication, Bypass RLS | {}
               |                                                        | {}

やはりRole nameにpostgresがない!!

では、ここのroleにpostgresを追加すれば全てが解決できるぞと思い、
コマンド入力! そしてすかさず確認!

$ createuser postgres 
$ psql postgres
psql (11.4)
Type "help" for help.

postgres=# \du         # 現在のロール確認(ちなみにバッククオート\は「option+¥」キーですよ)
Role name   |                         Attributes                         | Member of 
---------------+------------------------------------------------------------+-----------
 araishuntarou | Superuser, Create role, Create DB, Replication, Bypass RLS | {}
 postgres      |                                                            | {}

やったー!roleにpostgresが追加できてる。うれしー。これで解決と思いました...。

第二の関門 PG::InsufficientPrivilege: ERROR: permission denied to create database

やっとの思いで、railの起動まで辿り着きましたが、

$ rails s

なんとまたエラーが発生!!心折れます....。
PG::InsufficientPrivilege: ERROR: permission denied to create database
う~ん、データベースを作る権限がないということかな?

言われてみれば確かに、postgresのAttributesに何の記述もされていないぞ。

Role name   |                         Attributes                         | Member of 
---------------+------------------------------------------------------------+-----------
 araishuntarou | Superuser, Create role, Create DB, Replication, Bypass RLS | {}
 postgres      |                                                            | {}

そこで、postgres にsuperuserの権限を与えようと下記コマンドを実施!
superuserは「ログイン以外に何でもできる権限をロールに与える」ため、危険な場合もあるとのこと。
(ここら辺はあとで勉強しよう... 本来は目的に応じた権限のみを与えた方がいいのかも)

参考
https://eng-entrance.com/postgresql-role

$ psql postgres
psql (11.4)
Type "help" for help.

postgres=# DROP ROLE postgres ;                   # 一度postgresを削除(役割だけを追加する方法が不明..)
postgres=# CREATE ROLE postgres SUPERUSER ;      # superuserの権限を持ったpostgresを作成!
postgres=# \du                                    # 現在のロール確認

Role name   |                         Attributes                         | Member of 
---------------+------------------------------------------------------------+--------- 
araishuntarou | Superuser, Create role, Create DB, Replication, Bypass RLS | {}
 postgres     | Superuser                                                  | {}

やっとここまできたか...

最後の関門 ActiveRecord::PendingMigrationError

datebaseをmigrateしてないですよという意味だと捉え、

$ rails db:create   #データベース自体(テーブルを保管しておく全体のシステム)を作る

からの

$ rails db:migrate    #データベースの中にテーブルを作ったり、カラムを変更したりするときに実行する

を実行!!

最後に

$ rails s

出来たー!!!!

スクリーンショット 2021-01-21 18.33.39(2).png

最後に

稚拙な文章にもかかわらず最後までお読みいただきまして誠にありがとうございました。
私も完全には理解できていないところが多く、これかわも勉強しなければなりませんが、
少同じ問題で困っている方々の助けに慣れれば幸いです!

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

Rubyの最新のバージョンをインストールしようとしたらrbenvとanyenvにぶつかった

出発点

「プロを目指す人のためのRuby入門」が、「Macに標準搭載されているRubyは最新ではないので、アップデートした方がいいよ」と仰るので素直に従うことに。irbで日本語が化けてしまう症状にもこれが効くそうです。

Rubyの最新版のインストールにはrbenvというのがイケてるらしい。
そのrbenvをインストールするには、anyenvがいいらしい。

言語のインストールってサクッと終わる話かと思いきや、意外に階層が..。
そしてややハマったのでメモ...。

rbenvとは?
冷静になると、Ruby environmentと読めます。Rubyの環境設定ツール。

anyenvとは?
同じ容量で、Any environmentと読めます。rbenvだけでなく他の言語の環境設定ツールもまとめて管理してくれるツールだそうな。

始める前に作業環境の確認

MacBook Pro 13 2020
macOS Catalina 1O.15.7

1. anyenvをインストール

Rubyをインストールするrbenvをインストールするanyenvをインストールします。Homebrew、GitHubの2パターンがあります。

Homebrewの場合

1) anyenvをインストール

$ brew install anyenv

2) シェルにanyenvをセットアップ

シェルとは?
CUIとかCLIとか呼ばれるやつ。Macではターミナルとも呼び、bashだのzshだのがある。

$ anyenv init

initとは?
initialize(初期化)の意味。

3) ターミナルを再起動

エラーメッセージが出なければOK。

Gitの場合

自分はこれはやっていないので、こちらのGitHubをご参照。

4)manifest directoryを初期化する

manifest directoryとは?(調査中。汗)

下記メッセージが出たら、yと回答しEnter。

$ anyenv install --init
Manifest directory doesn't exist: /Users/kakudaisuke/.config/anyenv/anyenv-install
Do you want to checkout ? [y/N]: y

これで、anyenvのインストールは終了!

2. rbenvをインストール

1) 下記コマンドでanyenvでインストールできるリストが見られます。

そこにrbenvも出てくるはず。

$ anyenv install -l
  Renv
  crenv
  denv
  erlenv
  exenv
  goenv
  hsenv
  jenv
  jlenv
  luaenv
  nodenv
  phpenv
  plenv
  pyenv
  rbenv
  sbtenv
  scalaenv
  swiftenv
  tfenv

2) rbenvをインストール

Rubyをインストールするrbenvをanyenvでインストールします。

$ anyenv install rbenv
/var/folders/zq/lzp3d6kd2h1_00b25knb8tyr0000gn/T/rbenv.20210114135021.64582 ~
Cloning https://github.com/rbenv/rbenv.git master to rbenv...
Cloning into 'rbenv'...
remote: Enumerating objects: 10, done.
remote: Counting objects: 100% (10/10), done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 2878 (delta 3), reused 2 (delta 0), pack-reused 2868
Receiving objects: 100% (2878/2878), 562.74 KiB | 748.00 KiB/s, done.
Resolving deltas: 100% (1795/1795), done.
~
~/.anyenv/envs/rbenv/plugins ~
Cloning https://github.com/rbenv/ruby-build.git master to ruby-build...
Cloning into 'ruby-build'...
remote: Enumerating objects: 7, done.
remote: Counting objects: 100% (7/7), done.
remote: Compressing objects: 100% (6/6), done.
remote: Total 11314 (delta 0), reused 2 (delta 0), pack-reused 11307
Receiving objects: 100% (11314/11314), 2.40 MiB | 1.83 MiB/s, done.
Resolving deltas: 100% (7467/7467), done.
~

Install rbenv succeeded!

成功したようです。

3) シェル(ターミナル)を再起動

これをしないとせっかくインストールした内容がシェルに設定されないようです。

$ exec $SHELL -l

これでrbenvのインストールが完了しました!
下記コマンドでバージョンが表示されればOKです。

rbenv -v

4) commande not found: rbenvの場合

3)でcommande not found: rbenvになった場合は、パスが通っていないことが原因と考えられますので、こちらのQiita記事を参照すると良いです。

パスが通っていないとは?
シェル様(ターミナルの主)に新しくインストールしたソフトの「転入届」が未提出である状態。

3. Rubyをインストール

いよいよ、rbenvを使用して自分の欲しいバージョンを指定してインストールしましょう。

rbenv install 2.3.5

irbで日本語もきちんと表示されるようになりました。
めでたしめでたし。

最後に

教えてくれた先輩方に大感謝。
もしご指摘などあれば、バシバシお願い申し上げます!

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

Formオブジェクトを用いて作成したデータを、編集・更新する方法

要点

  • 初心者がアプリを作るときの参考に
  • formオブジェクトを用いた編集・更新機能は複雑なので詳しく知りたいorおさらいしたい
  • 下記のエラーを解消する方法
param is missing or the value is empty: 'Formオブジェクト名'

はじめに

メルカリのようなフリマアプリを作成中で、Formオブジェクトを用いて商品にタグ付けして編集・更新(edit・update)する機能を実装まで行いました
各モデルとコントローラーは以下のようになります

  • Item/商品
  • Tag/タグ
  • TagItemRelation/商品とタグの中間テーブル
  • TagsItem/ ItemとTagを同時に保存するためのFormオブジェクト
/app/model/item.rb
class Item < ApplicationRecord
  has_many :tag_item_relations, foreign_key: :item_id, dependent: :destroy
  has_many :tags, through: :tag_item_relations
end
/app/model/tag.rb
class Tag < ApplicationRecord
  has_many :tag_item_relations
  has_many :items, through: :tag_item_relations
  validates :tag_name, uniqueness: true
end
/app/model/tag_item_relation.rb
class TagItemRelation < ApplicationRecord
  belongs_to :item
  belongs_to :tag
end
/app/form/tags_item.rb
class TagsItem
  include ActiveModel::Model
  attr_accessor :item_name,
                :tag_name

  with_options presence: true do
    validates :item_name
  end

  def save
    item = Item.create(item_name: item_name)
    tag = Tag.where(tag_name: tag_name).first_or_initialize
    tag.save
    TagItemRelation.create(item_id: item.id, tag_id: tag.id)
  end
end

コントローラー

/app/controller/items_controller.rb
class ItemsController < ApplicationController
   def index
     @items = Item.all.order('created_at ASC')
   end

   def edit
    @item = current_user.items.find(params[:id])
    @tegs_item = TagsItem.new(item: @item)
   end

   def update
    @item = current_user.items.find(params[:id])
    @tags_item = TagsItem.new(update_items_params, item: @item)
     if @tags_item.valid?
       @tags_item.save
       redirect_to root_path
     else
       render :edit
     end
   end

  private

  def update_items_params
    params.require(:item).permit(
      :item_name,
      :tag_name
    )
  end
end

しかし実際に商品を編集・更新をしてみると、、、

param is missing or the value is empty: 'tags_item'

のエラーが出てしまい、商品の更新ができませんでした

調べたこと

エラー内容を読んでみると生成したFormオブジェクトの'tag_item_relation'が空になっているとのこと
そして、binding.pryなどを使って調べてみると、そもそもこの記述では'tags_item'編集・更新では機能していないことが分かります
さらに原因を調べてみると新規投稿(new・create)と編集更新(edit・update)の機能を仕分けしていなかったことだと分かりました

Formオブジェクトで編集更新

今回のエラーの原因はFormオブジェクトに記載したsaveが新規投稿と編集更新で仕分けされていなかったことだと分かりました

/app/form/tags_item.rb
class TagsItem
  include ActiveModel::Model
  attr_accessor :item_name,
                :tag_name

  with_options presence: true do
    validates :item_name
  end

  def save
    item = Item.create(item_name: item_name)  #ここがcreateアクションのみ
    tag = Tag.where(tag_name: tag_name).first_or_initialize
    tag.save
    TagItemRelation.create(item_id: item.id, tag_id: tag.id)
  end
end

この記述を

/app/form/tags_item.rb
class TagsItem
  include ActiveModel::Model
  attr_accessor :item_name,
                :tag_name

  with_options presence: true do
    validates :item_name
  end

  # itemがすでに保存されているものか、新規のものかで、PUTとPATCHを分ける
  delegate :persisted?, to: :item


  # initializeでFormオブジェクトの値を初期化し、更新の際はdefault_attributesを呼び出す設定
  def initialize(attributes = nil, item: Item.new)
    @item = item
    attributes ||= default_attributes
    super(attributes)
  end

  def save
    return if invalid?

    ActiveRecord::Base.transaction do
      # mapメソッドを使いsplit_tag_namesをtagの情報に変換
      tags = split_tag_names.map { |tag_name| Tag.find_or_create_by!(tag_name: tag_name) }
      item.update!(item_name: item_name, tags: tags)
    end
  rescue ActiveRecord::RecordInvalid
    false
  end

  #  formを飛ばす場所を(#createか#updateか)を判別して、切り替えている
  def to_model
    item
  end

  private

  attr_reader :item

  def default_attributes
    {
      item_name: item.item_name,
      tag_name: item.tags.pluck(:tag_name).join(',')
    }
  end

  def split_tag_names
    tag_name.split(',')
  end
end

に変更することで無事にタグ付け機能の編集更新を行うことができました

参考にしたサイト

Railsのデザインパターン: Formオブジェクト

formオブジェクトパターンで、タグ付け機能を実装

関連記事

Formオブジェクトを用いて作成したデータを、特定のデータのみ削除する方法←(前回記事)

railsでタグ機能を実装する

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

Railsによるfollow機能の実装

はじめに

Railsを使ってfollow機能を実装する中で気づいたことなどを自分用のメモとして手順と共に残しておこうと思い記事を書きました。間違っているところなどありましたらご指摘いただけると助かります!

Follow機能実装にあたって行う手順

  1. Followモデルを作り、dbにfollowsテーブルを追加
  2. 各モデルで関連付けを実装、フォロワー・フォローしている人を簡単に取得できるメソッドを定義する。
  3. 各controller, viewでそれらを表示

おおまかにはこんな流れです。基本的にはRailsチュートリアル第4版を参考にしています。

Followsモデルを追加

terminal
bin/rails g model Follow follower_id:integer followed_id:integer

上のように、rails g のコマンドでモデルを作ります。今回は、あるユーザーから見て、フォローされている人のidを格納するfollower_idカラム、そのユーザー自身のidを格納するfollowed_idカラム、を定義しました。
そうすると、migrationファイルが生成されるので、

terminal
bin/rails db:migrate

を実行します。これでデータベースへの反映は完了です。

各モデルでの関連付けの実装

今回は、Followモデルには User モデルのidのみを格納するので、Userモデルとの関連付けのみを行います。下のコードは、あるユーザーがフォローしているユーザーを取得するために行う関連付けの例です。

User.rb
has_many :active_follows, class_name: "Follow",
                          foreign_key: "follower_id",
                          dependent: :destroy
has_many :following, through: :active_follows, source: :followed
Follow.rb
belongs_to :follower, class_name: "User"

関連付けについては以下のような規則で行います。
1. model同士の関係性を1対1なのか、1対多なのか考える。
2. 1対1の場合はbelongs_to, 1対多の場合はhas_many, で関連付けする。

今回であれば、Userモデルから見た場合、Followという行為は何回でも行えるものなので、1ユーザーに対して多数のFollowの情報が存在します。よって、1対多の関係です。
反対に、Followモデルから見た場合、一つのFollowレコードに対して、フォローしているユーザーは一人、フォローされているユーザーも一人です。よって、1対1の関係です。(ただし、Followモデルの中のfollower_idが取得したいのか、followed_idが取得したいのか、ということは関連付けを一つ行っただけではわかりません。なので、今回はフォローする側、される側、の二つに分けて関連付けを行います。初め僕はここがよくわからなかったので注釈としておきます)
また、関連付けでは、次の項目を指定することができます。

  1. 関連付けをするメソッドの名前
  2. 関連づけるクラスの名前
  3. 参照する外部キーの名前
  4. 関連付け先のモデルとの依存関係

これは、以下のように対応しています。

User.rb
has_many :active_follows, #ここ1
      class_name: "Follow", #ここ2
      foreign_key: "follower_id" #ここ3
      dependent: :destroy #ここ4

1で、Userモデルのインスタンスに対して使う関連付け名(要するにメソッド名)を定義します。
2で、そのメソッドを用いたときどのクラスを参照するか指定します。今回はFollowモデルとの関連付けなのでFollowを指定しています。
3で、Followモデルのどのカラムを参照するか指定します。今回はあるユーザーがフォローしているユーザーを取得したいので、外部キーとしてfollower_idを指定します。
4で、そのモデルのインスタンスが削除された場合、紐づいているモデルの対応するカラムも削除するかどうかを指定します。ユーザーを削除した場合、フォロー情報も消去したいのでdependent: :destroyを指定します。

ここまでUserモデル側での関連付けについて説明しましたが、Followモデル側では次のコードのみで済みます。

Follow.rb
belongs_to : follower, class_name: "User"

これは、Railsの自動的な推測によります。こちらでは、1と2の指定のみで、誰がフォローしているかの情報をfollowerというメソッドで取り出せるようになります。class名を指定すると、そのクラスの主キーがfollower_idと一致するユーザーを取得してくれます。

最後に、今のままではUserインスタンスに対してactive_followsを用いてもFollowテーブルの情報が返ってくるのみです。フォローしているユーザーまで返してくれると便利なので、following関連付けを追加します。それが以下の部分です。

User.rb
has_many :following, through: active_follows, source: :followed

これは、followingという名前の関連付け(メソッド)を実行すれば、active_followsというメソッドを実行して、さらにそれにより得られたFollowテーブルのデータたちのfollowed_id というカラムのidを持つユーザーを取得してね、という意味になります。sourceに指定したfollowedの _id の部分はRailsによって補完されます。

以上で、Userモデルのインスタンスからフォロワーを取得してくる関連付けが定義できました!長々とわかりづらく書いてしまい申し訳ありません・・・

各controller,viewでの表示

これは他のところでも用いている方法と全く同じなので割愛します。取得した@userなどのインスタンス変数に対して先ほど定義したfollowingメソッドを実行し、レイアウトを整えるのみです。

終わりに

チュートリアル1周目にはあまり理解できていなかった部分を、ポートフォリオを作っている最中にしっくりきたので言葉に起こしておきました。自分なりの理解なのでわかりづらいところ、間違っているところなどあると思います。参考にされる際はその点御留意くださりますと幸いです。

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

if文とunless文の使い分け

エラーばかり投稿してもつまらないので、今回はif文とunless文について学んだことを書いていきたいと思います。

if文とは

ruby
if A = B
#処理
end

AはBであるという内容が、
正しければ処理を実行し、
正しくなければ処理を実行しません。

unless文とは

ruby
unless A = B
#処理
end

AはBであるという内容が、
正しければ実行せず、
正しくなければ実行します。

=は等しいかどうかを比較するときに使います。
if文ではAとBが等しいときtrueとなり、処理を実行し、
逆にunless文ではAとB等しくない時trueとなり、処理を実行するというわけです。

それでは比較演算子の1つである!=を使うとどうなるでしょうか?

比較演算子の !=とは

=とは逆で等しくないことを比較するときに使う比較演算子です。
要するに

ruby
if A != B
#処理
end

AはBではないという内容が
正しければ処理を実行し、
正しくなければ処理を実行しません。

ruby
unless A != B
#処理
end

AはBではないという内容が、
正しければ実行せず、
正しくなければ実行します。

何やらこんがらがってきました。
文字に書くと難しくなってきましたが、要するに

if A = B  と unless A != Bは同じ
if A != B と unless A = B は同じ
という解釈で大丈夫そうです。

どちらを使えばいいか

可読性も考え、基本的にはif文を使用した方が良いみたいです。

英語におけるifの否定(if not)unlessの違い

英語が得意だ!という人からすると信じられないかもしれませんね。
理由は、例えば
A if he isn't B.という英文と
A unless he is B.という英文は全く違う意味だからです

英語の授業ではないので英文の例文や意味は記述しません。
ただ、プログラミングにおけるifとunlessは難しく考えず、逆の意味程度にとどめておいた方が良いかもしれません。

終わり

英語が得意ではなかったですが、ifとunlessの使い方を疑問に思い、調べたり聞いたりした結果でした。
調べる前までは、
肯定したければif 否定したければunless
という考え方でした。

これからは基本的にはif文を使っていきたいと思います。

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

コロナ禍での未経験からエンジニア転職に成功した上での感想

前置き

あくまで一個人の感想であり、特定の人物や団体に誹謗中傷はやめてください。
年齢は20代前半
高卒
文系です。数学への苦手意識は高い。
転職は初

今の未経験からのエンジニア転職の状況について

正直かなり厳しい状況です。現在、未経験からのエンジニア転職はスクール卒のRubyを少しかじった未経験で溢れかえっており、経験者のエンジニアが欲しい企業と未経験から転職したいエンジニアの需要と供給が合致していません。正直、企業は未経験に技術は求めていないので、高度な技術を使用したポートフォリオは必要ありません。(Docker,CIなど)
そもそもポートフォリオをみていない可能性があります。なぜなら面接でポートフォリオの話題がでることがほとんどありません。なので、未経験が転職するのに必要なのは高度な技術を使用しすごいポートフォリオを作るより、面接対策を徹底したほうが転職できる可能性が高まると今回の転職活動で感じました。現在スクール卒のエンジニアが応募しすぎて、またスクール卒かよと思われるので、スクール卒を隠して独学で勉強しましたと言うと差別化を図れるかもしれません。(他の同期も同じ意見の方が多いです。)

自分の転職活動について

自分は8月に退職してあるスクールに入校しました。そこから3カ月おなじみのRubyOnRailsを使い11月にポートフォリオが完成した為、転職活動を始めました。11-1月の間でwantedlyで140件程応募しましたがほとんど書類選考は通りません。11月にいきなりReact/GOを使用している自社の書類選考が通りましたが3次面接で落ちました。
また振り出しに戻り12月からまた応募をどんどん出していき、12月もRailsを使用している自社の書類選考が通りましたが最終でおちました。
そして、また振り出しに戻り1月からまた応募を出してくことでJavaの企業に就職が決まりました。

自分がしたこと

ポートフォリオが完成した後rspec、factorybot、Capybaraでテストを書いて、リファクタリングをしました。

これは11月に受けた企業がテストを重視する企業だったので行いました。CapybaraでE2Eテストを書いたり、N+1問題をなおしたり、クラスメソッドやインスタンスメソッドを使用してDRYコードを目指したり、Viewでロジックをなるべく書かないなど意識しました。自分としてもテストやDRYコードを意識してリファクタリングを行った方がいいと思います。なぜなら、もし面接する企業がgithubのコードをみてくれた時に他の人との差別化も図れますし、現職のエンジニアの方に聞いても、高度な技術のポートフォリオを作っているがテストも書いておらずコードも読みにくい人よりかは、テストやDRYコードを意識してリファクタリングを行っているひとの方が評価できると述べていました。

SQL

Rails以外の言語を触ってみてわかるのですが、RailsはSELECT文をほとんど書かなくていい為、SQL文など意識したことはありませんでしたが、N+1問題などを解決する時に必要だなと感じ勉強しました。
使用した参考書: すっきりわかるSQL入門
2周ぐらいしてほとんど理解しました。

フロントも勉強してみようと思い、JavascriptのES2015の書き方を学習したあとreactでtodoアプリ、twitterクローンを作成しました。

SQLも学習し、バックのRailsしか触ったことがなかったので、学習しました。やっぱりrailsは簡単だなと感じました。正直フロントの方がおもしろかったです。
フロントを自分は学習しましたがおすすめはしません。

QiitaでアウトプットやTwitterで100daysOfCodeを行った

Qiitaで自分が覚えておきたいことなどアウトプットしました。Twitterで100daysOfCodeを行い100日間コードを毎日書き続けると言うものでしたが、熱がでたこともあり、毎日は無理だなと感じ自分ルールで毎日はやめました。Qiitaに投稿したり、100daysOfCodeを行っていると面接の自己紹介のときアピールすることができます。100daysOfCodeを行っていると自然と勉強しないといけないと思うようになるのでおすすめです。

Rubyでビンゴを作成

好きな言語でビンゴのプログラムを作成するものがありました。
標準出力と標準入力すら知らない自分には最初まったくわかりませんでしたが、4日間ほど、毎日寝て食べる以外は全て、そのプログラミングにフルコミットしました、自分はrailsしか触っていなかったので、Rubyだけでやると色々勉強できてかなり実力がついたと思いました。プログラミングの勉強を初めて一番楽しかったです、パズルを解いてるかのような感覚で没頭しました。二重配列や標準出力と標準入力をわからない人は挑戦してみるといいかもしれません。

おすすめのルート

独学やスクール卒でも共通でポートフォリオが完成した後は面接対策をメインに置きながら、テストやDRYコードを意識してリファクタリングしつつ、SQLなどを学び、2カ月以内に転職できたら理想です。
しかし現実はそう甘くないとおもいます。現在すぐに転職できたと言っている人は、相当スペックが高い人、運がいい人、Web制作の会社に就職した人だと思います。

スクール

正直スクール入ろうか検討している方は深く考えた方がいいです。スクールの転職保証などあてになりません。SESを否定するわけではありませんが、SES企業しか紹介してもらえないのが現実です。あと、関西ではrailsを使用している企業が少ないです。求人をみても関西はPHP/Javaの二強です。なので関西で就職したくてスクール卒の方はRailsの企業に就職するのは厳しいと考えた方がいいです。

最後に

本当に転職活動しんどかったです。Wantedy、Greenなどで応募しまくりましたが、ほとんど書類選考で落ちます。自分を否定されてるようで辛かったです。転職期間は毎週面接を受けました。11、12といいところまで行った企業も落ちて振り出しに戻った為、年末年始は久しぶりに勉強をやめてやけ酒しました。
今も未経験から転職活動をしている方たちにこの記事が参考になれば幸いです。
未経験から転職活動は辛くて、厳しいですが頑張りましょう。心の底から未経験の仲間を応援しています。

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

コロナ渦での未経験からエンジニア転職に成功した上での感想

前置き

あくまで一個人の感想であり、特定の人物や団体に誹謗中傷はやめてください。
年齢は20代前半
高卒
文系です。数学への苦手意識は高い。
転職は初

今の未経験からのエンジニア転職の状況について

正直かなり厳しい状況です。現在、未経験からのエンジニア転職はスクール卒のRubyを少しかじった未経験で溢れかえっており、経験者のエンジニアが欲しい企業と未経験から転職したいエンジニアの需要と供給が合致していません。正直、企業は未経験に技術は求めていないので、高度な技術を使用したポートフォリオは必要ありません。(Docker,CIなど)
そもそもポートフォリオをみていない可能性があります。なぜなら面接でポートフォリオの話題がでることがほとんどありません。なので、未経験が転職するのに必要なのは高度な技術を使用しすごいポートフォリオを作るより、面接対策を徹底したほうが転職できる可能性が高まると今回の転職活動で感じました。現在スクール卒のエンジニアが応募しすぎて、またスクール卒かよと思われるので、スクール卒を隠して独学で勉強しましたと言うと差別化を図れるかもしれません。(他の同期も同じ意見の方が多いです。)

自分の転職活動について

自分は8月に退職してあるスクールに入校しました。そこから3カ月おなじみのRubyOnRailsを使い11月にポートフォリオが完成した為、転職活動を始めました。11-1月の間でwantedlyで140件程応募しましたがほとんど書類選考は通りません。11月にいきなりReact/GOを使用している自社の書類選考が通りましたが3次面接で落ちました。
また振り出しに戻り12月からまた応募をどんどん出していき、12月もRailsを使用している自社の書類選考が通りましたが最終でおちました。
そして、また振り出しに戻り1月からまた応募を出してくことでJavaの企業に就職が決まりました。

自分がしたこと

ポートフォリオが完成した後rspec、factorybot、Capybaraでテストを書いて、リファクタリングをしました。

これは11月に受けた企業がテストを重視する企業だったので行いました。CapybaraでE2Eテストを書いたり、N+1問題をなおしたり、クラスメソッドやインスタンスメソッドを使用してDRYコードを目指したり、Viewでロジックをなるべく書かないなど意識しました。自分としてもテストやDRYコードを意識してリファクタリングを行った方がいいと思います。なぜなら、もし面接する企業がgithubのコードをみてくれた時に他の人との差別化も図れますし、現職のエンジニアの方に聞いても、高度な技術のポートフォリオを作っているがテストも書いておらずコードも読みにくい人よりかは、テストやDRYコードを意識してリファクタリングを行っているひとの方が評価できると述べていました。

SQL

Rails以外の言語を触ってみてわかるのですが、RailsはSELECT文をほとんど書かなくていい為、SQL文など意識したことはありませんでしたが、N+1問題などを解決する時に必要だなと感じ勉強しました。
使用した参考書: すっきりわかるSQL入門
2周ぐらいしてほとんど理解しました。

フロントも勉強してみようと思い、JavascriptのES2015の書き方を学習したあとreactでtodoアプリ、twitterクローンを作成しました。

SQLも学習し、バックのRailsしか触ったことがなかったので、学習しました。やっぱりrailsは簡単だなと感じました。正直フロントの方がおもしろかったです。
フロントを自分は学習しましたがおすすめはしません。

QiitaでアウトプットやTwitterで100daysOfCodeを行った

Qiitaで自分が覚えておきたいことなどアウトプットしました。Twitterで100daysOfCodeを行い100日間コードを毎日書き続けると言うものでしたが、熱がでたこともあり、毎日は無理だなと感じ自分ルールで毎日はやめました。Qiitaに投稿したり、100daysOfCodeを行っていると面接の自己紹介のときアピールすることができます。100daysOfCodeを行っていると自然と勉強しないといけないと思うようになるのでおすすめです。

Rubyでビンゴを作成

好きな言語でビンゴのプログラムを作成するものがありました。
標準出力と標準入力すら知らない自分には最初まったくわかりませんでしたが、4日間ほど、毎日寝て食べる以外は全て、そのプログラミングにフルコミットしました、自分はrailsしか触っていなかったので、Rubyだけでやると色々勉強できてかなり実力がついたと思いました。プログラミングの勉強を初めて一番楽しかったです、パズルを解いてるかのような感覚で没頭しました。二重配列や標準出力と標準入力をわからない人は挑戦してみるといいかもしれません。

おすすめのルート

独学やスクール卒でも共通でポートフォリオが完成した後は面接対策をメインに置きながら、テストやDRYコードを意識してリファクタリングしつつ、SQLなどを学び、2カ月以内に転職できたら理想です。
しかし現実はそう甘くないとおもいます。現在すぐに転職できたと言っている人は、相当スペックが高い人、運がいい人、Web制作の会社に就職した人だと思います。

スクール

正直スクール入ろうか検討している方は深く考えた方がいいです。スクールの転職保証などあてになりません。SESを否定するわけではありませんが、SES企業しか紹介してもらえないのが現実です。あと、関西ではrailsを使用している企業が少ないです。求人をみても関西はPHP/Javaの二強です。なので関西で就職したくてスクール卒の方はRailsの企業に就職するのは厳しいと考えた方がいいです。

最後に

本当に転職活動しんどかったです。Wantedy、Greenなどで応募しまくりましたが、ほとんど書類選考で落ちます。自分を否定されてるようで辛かったです。転職期間は毎週面接を受けました。11、12といいところまで行った企業も落ちて振り出しに戻った為、年末年始は久しぶりに勉強をやめてやけ酒しました。
今も未経験から転職活動をしている方たちにこの記事が参考になれば幸いです。
未経験から転職活動は辛くて、厳しいですが頑張りましょう。心の底から未経験の仲間を応援しています。

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

[Rails] GoogleMapを使って、マーカーが立っている位置をDBに保存する!

前提

・Railsのアプリケーションであること
・記事作成機能が存在すること
・GoogleMapを自分のアプリケーション上で表示することができていること
※下記記事などが非常に参考になると思います。

https://qiita.com/tiara/items/4a1c98418917a0e74cbb#%E5%9C%B0%E5%90%8D%E3%81%A7map%E3%82%92%E7%A7%BB%E5%8B%95%E3%81%99%E3%82%8B

https://qiita.com/nagaseToya/items/e49977efb686ed05eadb

目標

記事作成の際、マップにマーカーを1つだけ立てられるようにし、その位置情報をDBに保存すること。
マーカーの立て方としては以下を想定。

・マップをクリック
・位置を検索

それではやっていきましょう!

gemのインストール ※いらない方はスキップしてください

Gemfile
gem 'geocoder'
gem 'gon'

※この記事においてはこの2つのgemは使用しません。しかし、これらのgemが入っているという前提で進めていきます。

geocoderは便利なメソッドが豊富です。
RailsでGoogleMapを使用のであれば入れておいたほうが良いです。
gonはController内でセットした変数をJavascript内で使う事ができます。
DBに保存した位置情報を記事詳細画面で表示させる際に非常に便利です。

geocoder
https://github.com/alexreisner/geocoder

gon
https://github.com/gazay/gon

これらを記述した後、bundle install します。

マップの情報保存用テーブル作成

指定した緯度、経度を保存するテーブルを作成します。
まず、マイグレーションファイルを作成しましょう。
マイグレーションファイルはこんな感じにします。
私の場合は、記事と地図を関連付けさせたいのでこのようになっています。
マップ単品の方は
t.references :article, null: false, foreign_key: true
の部分はいらないかと思います。

timestamp_xxx.rb(マイグレーションファイル)
class CreateMaps < ActiveRecord::Migration[6.0]
  def change
    create_table :maps do |t|

    t.references :article, null: false, foreign_key: true
    t.string :address
    t.float :latitude
    t.float :longitude

    t.timestamps
    end
  end
end

その後、rails db:migrate します。

モデルの関連付け

関連付けさせなくて良い方はスキップしても大丈夫です。

map.rb
class Map < ApplicationRecord
  belongs_to :article
  geocoded_by :address
  after_validation :geocode
end
article.rb
has_one :map, dependent: :destroy

マップの表示

表示だけならこれだけでできると思います。

〇〇.html.erb
<div id='map'></div>   
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=<%= ENV['GOOGLE_MAP_API'] %>&callback=initMap" async defer></script>   
<script>
let map

function initMap (){
  myLatLng = {lat: 35.68090045006303, lng: 139.76690798417752}
  map = new google.maps.Map(document.getElementById('map'), {
  center: myLatLng,
  zoom: 12
  });
</script>

マップのクリックしたところにマーカーを立てることができるようにする

記事作成と同時にクリックした箇所の経度、緯度を保存できるようにします。
また、マーカを削除できるボタンも追加しました。

_article_form.html.erb
<div id="article_form" class="field">
  <%= f.label :title, "タイトル" %>
  <%= f.text_area :title %>
  マップ
  <div id='map'></div>
  <%= f.fields_for :map, @article.build_map do |map| %>   
    <%= map.hidden_field :latitude %>
    <%= map.hidden_field :longitude %>
  <% end %>
  <%= f.label :content, "本文" %>
  <%= f.rich_text_area :content,placeholder:"入力してください" %>
  <%= f.label :tag_list %>
  <%= f.file_field :image, accept: "image/jpeg,image/gif,image/png" %>
</div>
<div id="article_button"><%= f.submit "投稿!",class:"btn"%></div>
<% end %>
<button id="del" class="btn" onclick="deleteMarker();">マーカー削除</button>


<script>

var marker
var myLatLng
var map
var map_lat
var map_lng

function initMap(){
   myLatLng = {lat: 35.68090045006303, lng: 139.76690798417752}
   marker = new google.maps.Marker();
   map_lat = document.getElementById('article_map_latitude')
   map_lng = document.getElementById('article_map_longitude')
   map = new google.maps.Map(document.getElementById('map'), {
  center: myLatLng,
  zoom: 8
  });


 google.maps.event.addListener(map, 'click', mylistener);

    //クリックしたときの処理
  function mylistener(event){
    //markerの位置を設定
    //event.latLng.lat()でクリックしたところの緯度を取得
    marker.setPosition(new google.maps.LatLng(event.latLng.lat(), event.latLng.lng()));
    //marker設置
    marker.setMap(map);    
    map_lat.value = event.latLng.lat();
    map_lng.value = event.latLng.lng();

  }
}
function deleteMarker(){
  marker.setMap(null);
  map_lat.value = "";
  map_lng.value = "";
}



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

下記のコードによってarticleと関連付けたmapにデータを渡すことができます。

<%= f.fields_for :map, @article.build_map do |map| %>
<%= map.hidden_field :latitude %>
<%= map.hidden_field :longitude %>
<% end %>

また、これらに値を入れるコードはこの部分です。

map_lat = document.getElementById('article_map_latitude')
map_lng = document.getElementById('article_map_longitude')
//↑map.hidden_fieldによって生成された<input>のidです。
//人によって違うかもしれないのでブラウザの開発者モードで確認してください。

//〜〜省略〜〜

map_lat.value = event.latLng.lat();
map_lng.value = event.latLng.lng();

そして、値をコントローラに渡すため、StrongParameterの設定も必要です。
binding.pryなどで、どのようなparamsが渡ってきているか確認しておいたほうが良いかと思います。

article_controller.rb
    def article_params
      params.require(:article)
            .permit(:content, :title,
             map_attributes: [:id, :latitude, :longitude])
    end

これを保存するコードはこちらです。

article_controller.rb
def create
  @article = current_user.articles.build(article_params)
  if @article.save
    latitude = params[:article][:map][:latitude]
    longitude = params[:article][:map][:longitude]
    unless latitude.empty && longitude.empty?
      @map = @article.build_map(
        latitude: latitude,
        longitude: longitude
      )
      @map.save
    end
#〜〜〜〜〜〜省略〜〜〜〜〜〜〜〜〜〜〜
end

位置検索でもマーカーを立てることができるようにする

この場合、GoogleMapAPIだけでなく、Geocoding APIも必要になってきます。
紆余曲折あり、最終的に完成したのがこちらです。

<div class="row">
  <div class="col s12 m10 push-m1">
    <div class="card">
      <div class="card-content article-card">        
        <%= form_with(model:@article,local: true,class:"margin-30") do |f|%>
        <%= render 'shared/error_messages',object: f.object %>        
          <div id="article_form" class="field">
            <div class="input-field">              
              <%= f.text_field :title,class: "materialize-textarea" %>
              <%= f.label :title, "タイトル" %>
            </div>
            <div class="input-field">              
              <%= f.text_field :tag_list, value: @article.tag_list.join(','), class: "materialize-textarea" %>
              <%= f.label :tag_list,"タグ ※コンマ区切りで記入してください" %>
            </div>

        <!-- 追加したイベント -->    

            <div class="input-field">
              <input type="text" id="address" placeholder="地名、施設名などを入力するか、地図をクリックしてマーカーを立ててください">
              <label>場所</label>
              <a class="btn" onclick="codeAddress()">地図検索</a>
              <a id="del" class="btn marker-delete right" onclick="deleteMarker()">
              <i class="fas fa-map-marker-alt fas-2x" style="color:red"></i>削除
              </a>
            </div>

        <!-- ここまで -->

            <div id='map' class="margin-t-b-14"></div>          
            <%= f.fields_for :map, @article.build_map do |map| %>   
            <%= map.hidden_field :latitude %>
            <%= map.hidden_field :longitude %>
            <% end %>            
              <%= f.rich_text_area :content,placeholder:"入力してください",class: "materialize-textarea" %>
              <%= f.label :content, "本文" %>
            <%= f.label :thumbnail, "サムネイル" %>
            <%= f.file_field :thumbnail, accept: "image/jpeg,image/gif,image/png",class:"file_field" %>
            <div id="article_button"><%= f.submit "投稿!",class:"btn col s6 m6 article-form-btn push-s3 push-m3"%></div>
          </div>
        <% end %> 
      </div>
    </div>
  </div>
</div>





<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=<%= ENV['GOOGLE_MAP_API'] %>&callback=initMap" async defer></script>
<script>
var marker
var myLatLng
var map_lat
var map_lng
var geocoder
var map_result
let map

function initMap (){
  myLatLng = {lat: 35.68090045006303, lng: 139.76690798417752}
  map_lat = document.getElementById('article_map_latitude')
  map_lng = document.getElementById('article_map_longitude')
  marker = new google.maps.Marker();

  map = new google.maps.Map(document.getElementById('map'), {
  center: myLatLng,
  zoom: 12
  });

google.maps.event.addListener(map, 'click', mylistener);

     //クリックしたときの処理
function mylistener(event){
    marker.setPosition(new google.maps.LatLng(event.latLng.lat(), event.latLng.lng()));
    marker.setMap(map);    
    console.log(event.latLng.lat(), event.latLng.lng());
    map_lat.value = event.latLng.lat();
    map_lng.value = event.latLng.lng();
  }
}

  function deleteMarker(){
    marker.setMap(null);
    map_lat.value = "";
    map_lng.value = "";
}

//追加した関数

function codeAddress(){
  geocoder = new google.maps.Geocoder()
  let inputAddress = document.getElementById('address').value;
  geocoder.geocode( { 
    'address': inputAddress,
     'region': 'jp'
    }, function(results, status) {
    if (status == 'OK') {
      map.setCenter(results[0].geometry.location);
      map_result = results[0].geometry.location;
      map_lat.value = map_result.lat();
      map_lng.value = map_result.lng();
      marker.setPosition(new google.maps.LatLng(map_result.lat(), map_result.lng()));
      marker.setMap(map);
      console.log(map_lat.value,map_lng.value);
    } else {
      alert('該当する結果がありませんでした');
      initMap();
    }
  });   
}
</script>

随分長いコードになってしまいました。
しかし、実際は関数とイベントが1つ増えただけなのでその部分を見てもらえればわかるかと思います!
これによって、

・地図をクリック
・位置を検索
することで地図上にマーカーを立て、その位置情報をDBに保存することができるようになりました!
これらを利用する方法についてはまた後日記事を書こうかと思います。

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

[Rails6] GoogleMapを使って、マーカーが立っている位置をDBに保存する!

前提

・Railsのアプリケーションであること
※私のRailsのバージョンは6.0.3です
・記事作成機能が存在すること
・GoogleMapを自分のアプリケーション上で表示することができていること
※下記記事などが非常に参考になると思います。

https://qiita.com/tiara/items/4a1c98418917a0e74cbb#%E5%9C%B0%E5%90%8D%E3%81%A7map%E3%82%92%E7%A7%BB%E5%8B%95%E3%81%99%E3%82%8B

https://qiita.com/nagaseToya/items/e49977efb686ed05eadb

目標

記事作成の際、マップにマーカーを1つだけ立てられるようにし、その位置情報をDBに保存すること。
マーカーの立て方としては以下を想定。

・マップをクリック
・位置を検索(GeocodingAPIを使用)

※記事最下部に完成版のgifあり

それではやっていきましょう!

gemのインストール ※いらない方はスキップしてください

Gemfile
gem 'geocoder'
gem 'gon'

※この記事においてはこの2つのgemは使用しません(次の記事で使用予定)。しかし、これらのgemが入っているという前提で進めていきます。

geocoderは便利なメソッドが豊富です。
RailsでGoogleMapを使用のであれば入れておいたほうが良いです。
gonはController内でセットした変数をJavascript内で使う事ができます。
DBに保存した位置情報を記事詳細画面で表示させる際に非常に便利です。

geocoder
https://github.com/alexreisner/geocoder

gon
https://github.com/gazay/gon

これらを記述した後、bundle install します。

マップの情報保存用テーブル作成

指定した緯度、経度を保存するテーブルを作成します。
まず、マイグレーションファイルを作成しましょう。
私の場合は、記事と地図を関連付けさせたいのでこのようになっています。
マップ単品の方は
t.references :article, null: false, foreign_key: true
の部分はいらないかと思います。

timestamp_xxx.rb(マイグレーションファイル)
class CreateMaps < ActiveRecord::Migration[6.0]
  def change
    create_table :maps do |t|

    t.references :article, null: false, foreign_key: true
    t.string :address
    t.float :latitude
    t.float :longitude

    t.timestamps
    end
  end
end

その後、rails db:migrate します。

モデルの関連付け

関連付けさせなくて良い方はスキップしても大丈夫です。

map.rb
class Map < ApplicationRecord
  belongs_to :article
  geocoded_by :address
  after_validation :geocode
end
article.rb
has_one :map, dependent: :destroy

マップの表示

表示だけならこれだけでできると思います。

〇〇.html.erb
<div id='map'></div>   
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=<%= ENV['GOOGLE_MAP_API'] %>&callback=initMap" async defer></script>   
<script>
let map

function initMap (){
  myLatLng = {lat: 35.68090045006303, lng: 139.76690798417752}
  map = new google.maps.Map(document.getElementById('map'), {
  center: myLatLng,
  zoom: 12
  });
</script>

マップ上でクリックしたところにマーカーを立てることができるようにする

記事作成の際に、マップ上でクリックした箇所にマーカーを立てられるようにします。
また、マーカーが立っている箇所の経度、緯度を保存できるようにします。
ついでにマーカーを削除できるボタンも追加しました。

_article_form.html.erb
<div id="article_form" class="field">
  <%= f.label :title, "タイトル" %>
  <%= f.text_area :title %>
  マップ
  <div id='map'></div>
  <%= f.fields_for :map, @article.build_map do |map| %>   
    <%= map.hidden_field :latitude %>
    <%= map.hidden_field :longitude %>
  <% end %>
  <%= f.label :content, "本文" %>
  <%= f.rich_text_area :content,placeholder:"入力してください" %>
  <%= f.label :tag_list %>
  <%= f.file_field :image, accept: "image/jpeg,image/gif,image/png" %>
</div>
<div id="article_button"><%= f.submit "投稿!",class:"btn"%></div>
<% end %>
<button id="del" class="btn" onclick="deleteMarker();">マーカー削除</button>

<script>
var marker
var myLatLng
var map
var map_lat
var map_lng

function initMap(){
   myLatLng = {lat: 35.68090045006303, lng: 139.76690798417752}
   marker = new google.maps.Marker();
   map_lat = document.getElementById('article_map_latitude')
   map_lng = document.getElementById('article_map_longitude')
   map = new google.maps.Map(document.getElementById('map'), {
  center: myLatLng,
  zoom: 8
  });

 google.maps.event.addListener(map, 'click', mylistener);

    //クリックしたときの処理
  function mylistener(event){
    //markerの位置を設定
    //event.latLng.lat()でクリックしたところの緯度を取得
    marker.setPosition(new google.maps.LatLng(event.latLng.lat(), event.latLng.lng()));
    //marker設置
    marker.setMap(map);    
    map_lat.value = event.latLng.lat();
    map_lng.value = event.latLng.lng();
  }
}
function deleteMarker(){
  marker.setMap(null);
  map_lat.value = "";
  map_lng.value = "";
}



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

下記のコードによってarticleと関連付けたmapにデータを渡すことができます。

<%= f.fields_for :map, @article.build_map do |map| %>
<%= map.hidden_field :latitude %>
<%= map.hidden_field :longitude %>
<% end %>

また、これらに値を入れるコードはこの部分です。

map_lat = document.getElementById('article_map_latitude')
map_lng = document.getElementById('article_map_longitude')
//↑map.hidden_fieldによって生成された<input>のidです。
//人によって違うかもしれないのでブラウザの開発者モードで確認してください。

//〜〜省略〜〜

map_lat.value = event.latLng.lat();
map_lng.value = event.latLng.lng();

そして、値をコントローラに渡すため、StrongParameterの設定も必要です。
binding.pryなどで、どのようなparamsが渡ってきているか確認しておいたほうが良いかと思います。

article_controller.rb
    def article_params
      params.require(:article)
            .permit(:content, :title,
             map_attributes: [:id, :latitude, :longitude])
    end

これを保存するコードはこちらです。

article_controller.rb
def create
  @article = current_user.articles.build(article_params)
  if @article.save
    latitude = params[:article][:map][:latitude]
    longitude = params[:article][:map][:longitude]
    unless latitude.empty && longitude.empty?
      @map = @article.build_map(
        latitude: latitude,
        longitude: longitude
      )
      @map.save
    end
#〜〜〜〜〜〜省略〜〜〜〜〜〜〜〜〜〜〜
end

位置検索でもマーカーを立てることができるようにする

この場合、GoogleMapAPIだけでなく、Geocoding APIも必要になってきます。
紆余曲折あり、最終的に完成したのがこちらです。

<div class="row">
  <div class="col s12 m10 push-m1">
    <div class="card">
      <div class="card-content article-card">        
        <%= form_with(model:@article,local: true,class:"margin-30") do |f|%>
        <%= render 'shared/error_messages',object: f.object %>        
          <div id="article_form" class="field">
            <div class="input-field">              
              <%= f.text_field :title,class: "materialize-textarea" %>
              <%= f.label :title, "タイトル" %>
            </div>
            <div class="input-field">              
              <%= f.text_field :tag_list, value: @article.tag_list.join(','), class: "materialize-textarea" %>
              <%= f.label :tag_list,"タグ ※コンマ区切りで記入してください" %>
            </div>

        <!-- 追加したイベント -->    

            <div class="input-field">
              <input type="text" id="address" placeholder="地名、施設名などを入力するか、地図をクリックしてマーカーを立ててください">
              <label>場所</label>
              <a class="btn" onclick="codeAddress()">地図検索</a>
              <a id="del" class="btn marker-delete right" onclick="deleteMarker()">
              <i class="fas fa-map-marker-alt fas-2x" style="color:red"></i>削除
              </a>
            </div>

        <!-- ここまで -->

            <div id='map' class="margin-t-b-14"></div>          
            <%= f.fields_for :map, @article.build_map do |map| %>   
            <%= map.hidden_field :latitude %>
            <%= map.hidden_field :longitude %>
            <% end %>            
              <%= f.rich_text_area :content,placeholder:"入力してください",class: "materialize-textarea" %>
              <%= f.label :content, "本文" %>
            <%= f.label :thumbnail, "サムネイル" %>
            <%= f.file_field :thumbnail, accept: "image/jpeg,image/gif,image/png",class:"file_field" %>
            <div id="article_button"><%= f.submit "投稿!",class:"btn col s6 m6 article-form-btn push-s3 push-m3"%></div>
          </div>
        <% end %> 
      </div>
    </div>
  </div>
</div>

<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=<%= ENV['GOOGLE_MAP_API'] %>&callback=initMap" async defer></script>
<script>
var marker
var myLatLng
var map_lat
var map_lng
var geocoder
var map_result
let map

function initMap (){
  myLatLng = {lat: 35.68090045006303, lng: 139.76690798417752}
  map_lat = document.getElementById('article_map_latitude')
  map_lng = document.getElementById('article_map_longitude')
  marker = new google.maps.Marker();

  map = new google.maps.Map(document.getElementById('map'), {
  center: myLatLng,
  zoom: 12
  });

google.maps.event.addListener(map, 'click', mylistener);

     //クリックしたときの処理
function mylistener(event){
    marker.setPosition(new google.maps.LatLng(event.latLng.lat(), event.latLng.lng()));
    marker.setMap(map);    
    console.log(event.latLng.lat(), event.latLng.lng());
    map_lat.value = event.latLng.lat();
    map_lng.value = event.latLng.lng();
  }
}

  function deleteMarker(){
    marker.setMap(null);
    map_lat.value = "";
    map_lng.value = "";
}

//追加した関数

function codeAddress(){
  geocoder = new google.maps.Geocoder()
  let inputAddress = document.getElementById('address').value;
  geocoder.geocode( { 
    'address': inputAddress,
     'region': 'jp'
    }, function(results, status) {
    if (status == 'OK') {
      map.setCenter(results[0].geometry.location);
      map_result = results[0].geometry.location;
      map_lat.value = map_result.lat();
      map_lng.value = map_result.lng();
      marker.setPosition(new google.maps.LatLng(map_result.lat(), map_result.lng()));
      marker.setMap(map);
      console.log(map_lat.value,map_lng.value);
    } else {
      alert('該当する結果がありませんでした');
      initMap();
    }
  });   
}
</script>

随分長いコードになってしまいました。
しかし、実際は関数とイベントが1つ増えただけなのでその部分を見てもらえればわかるかと思います!
これによって、マップをクリックor位置を検索することで地図上にマーカーを立て、その位置情報をDBに保存することができるようになりました!
これらを利用する方法についてはまた後日記事を書こうかと思います。

map-demo.gif

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

Railsチュートリアルはじめます

はじめに

2020年12月末からプログラミング学習を始め、2021年1月21日現在で以下の学習を終えました。

  • HTML&CSSコース (#Progate)
  • はじめてのHTML (#ドットインストール)
  • はじめてのCSS (#ドットインストール)
  • JavaScriptコース (#Progate)
  • 詳解JavaScript 基礎文法編 (#ドットインストール)
  • Rubyコース (#Progate)
  • Ruby入門 (#ドットインストール)
  • SQLコース (#Progate)
  • MySQL入門 基礎編 (#ドットインストール)
  • MySQL入門 応用編 (#ドットインストール)
  • はじめてのGitとGitHub (#Udemy)

学習を通じて

今までは日々の学習をTwitterでアウトプットしていましたが、Qiitaでのアウトプットも挑戦しようと思います。
Railsチュートリアルの学習で以下のアウトプットをしていく予定です。

  • できるようになったこと
  • わからない→わかったまでの解決方法
  • 感想や意気込み

また、Qiitaでのアウトプットを通じて、以下のスキルを伸ばしていきたいと考えています。

  • マークダウン方式をできるようになる
  • 140文字以上のアウトプットに慣れる

さいごに

  • もっとこうしたほうがいいよ!
  • ここのエラーはこういう意味だよ!

などなど…お気づきの点がございましたら、コメントいただけると大変励みになります。
どうぞよろしくお願い申し上げます。

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

bundle install エラー

実行環境:
Winodows 10 Pro (64bit)
Vagrant 2.2.14
Ubuntu 16.04.6 LTS
Docker 18.09.7
ruby:ruby 2.7.2p137
Docker のコンテ内でRubyのsinatraライブラリをインストールするために
bundle install を実行したところ、以下のエラーが発生しました。
````

Fetching source index from https://rubygems.rog/

Retrying fetcher due to error (2/4): Bundler::HTTPError Could not fetch specs from https://rubygems.rog/

Retrying fetcher due to error (3/4): Bundler::HTTPError Could not fetch specs from https://rubygems.rog/

Retrying fetcher due to error (4/4): Bundler::HTTPError Could not fetch specs from https://rubygems.rog/

Could not fetch specs from https://rubygems.rog/

以下のコマンドを実行したら解決したという情報を入手
````

gem update --system

再度インストールを実行。変化がありません。

wget https://api.rubygems.org/specs.4.8.gz を実行してダウンロードできるか試しましたが、これは成功しました。
ドメインの IP アドレスを調べました。
````

root@dfed40fdb477:/var/www# host api.rubygems.org
api.rubygems.org is an alias for rubygems.org.
rubygems.org has address 151.101.2.132
rubygems.org has address 151.101.130.132
rubygems.org has address 151.101.194.132
rubygems.org has address 151.101.66.132
rubygems.org has IPv6 address 2a04:4e42::644
rubygems.org has IPv6 address 2a04:4e42:200::644
rubygems.org has IPv6 address 2a04:4e42:600::644
rubygems.org has IPv6 address 2a04:4e42:400::644
rubygems.org mail is handled by 10 mxb.mailgun.org.
rubygems.org mail is handled by 10 mxa.mailgun.org.

hostファイルに記述しました。

151.101.2.132  api.rubygems.org
151.101.130.132   api.rubygems.org
151.101.194.132  api.rubygems.org
151.101.66.132  api.rubygems.org

2a04:4e42::644 api.rubygems.org
2a04:4e42:200::644 api.rubygems.org
2a04:4e42:600::644 api.rubygems.org
2a04:4e42:400::644 api.rubygems.org

インストールが正常にできました。

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

【Rails チュートリアル】"NoMethodError (undefined method `activation_digest=' for #<User:0x00000003156938>

"NoMethodError (undefined method `activation_digest=' for #User:0x00000003156938

について

何故か本番環境だけで、このエラーがみられた

user.rb

    def activate
      self.update_attribute(:activated,    true)
      self.update_attribute(:activated_at, Time.zone.now)
    end

とself.をつけ忘れてた

何故、ローカルだと問題なかったのか、、、

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

railsでのカスタムヘルパーの使い方

引用先
Railsチュートリアル

カスタムヘルパーとは

Railsでは多くの組み込み関数が使えますが、使いたいメソッドがなかった時にオリジナルのメソッドを作成できます。
この新しく作ったメソッドのことをカスタムヘルパーといいます。

ファイルの場所

app/helpers/application_helper.rb
module ApplicationHelper

end

ファイルの位置は app/helpers/application_helper.rb
最初はこのようなファイル構造になっています。

使い方

例えばこのように記述する。

app/helpers/application_helper.rb
module ApplicationHelper

  def full_title(page_title = '')
    base_title = "Sample App"
    if page_title.empty?
      base_title
    else
      page_title + " | " + base_title
    end
  end
end

application.html.erbファイルの呼び出したい箇所へ記述。
今回はタイトルタグに記述します。

app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= full_title(yield(:title)) %></title>
  </head>
  <body>
    <%= yield %>
  </body>
</html>

このように記述すれば、タイトルタグにヘルパーファイルに記述したこのコードが呼び出されます。

def full_title(page_title = '')
    base_title = "Sample App"
    if page_title.empty?
      base_title
    else
      page_title + " | " + base_title
    end
  end

# コード解説 
if文によって、page_titleが.empty?(入れ物が空→true,入れ物がある→false)

→true:base_title(Sample App)を表示
→false:page_title|base_title(Sample App)を表示

今回はコードの量が多くないため恩恵を感じにくいですが、コードを別ファイルに分けることでコードが見やすくなりますね。

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

railsでカスタムヘルパーの使い方

引用先
Railsチュートリアル

カスタムヘルパーとは

Railsでは多くの組み込み関数が使えますが、使いたいメソッドがなかった時にオリジナルのメソッドを作成できます。
この新しく作ったメソッドのことをカスタムヘルパーといいます。

ファイルの場所

app/helpers/application_helper.rb
module ApplicationHelper

end

ファイルの位置は app/helpers/application_helper.rb
最初はこのようなファイル構造になっています。

使い方

例えばこのように記述する。

app/helpers/application_helper.rb
module ApplicationHelper

  def full_title(page_title = '')
    base_title = "Sample App"
    if page_title.empty?
      base_title
    else
      page_title + " | " + base_title
    end
  end
end

application.html.erbファイルの呼び出したい箇所へ記述。
今回はタイトルタグに記述します。

app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= full_title(yield(:title)) %></title>
  </head>
  <body>
    <%= yield %>
  </body>
</html>

このように記述すれば、タイトルタグにヘルパーファイルに記述したこのコードが呼び出されます。

def full_title(page_title = '')
    base_title = "Sample App"
    if page_title.empty?
      base_title
    else
      page_title + " | " + base_title
    end
  end

# コード解説 
if文によって、page_titleが.empty?(入れ物が空→true,入れ物がある→false)

→true:base_title(Sample App)を表示
→false:page_title|base_title(Sample App)を表示

今回はコードの量が多くないため恩恵を感じにくいですが、コードを別ファイルに分けることでコードが見やすくなりますね。

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

discordrbで音声が使用できない場合の対処

環境

  • OS: Windows10(64bit)
  • Ruby: ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [i386-mingw32]

起動時にこんなエラーが出る!

libsodium not available! You can continue to use discordrb as normal but voice support won't work.
Read https://github.com/shardlab/discordrb/wiki/Installing-libsodium for more details.

対処

URLに記載の方法だと、何度やっても解消しない(おそらくx64-mingw32版Ruby用の説明のため)。
C:\Windows\System32ではなく、C:\Windows\SysWOW6432bit用libsodium.dllsodium.dllにリネームして入れてやる。

libopusも入っていなければついでにlibopus.dllopus.dllにリネームして入れると良いと思います。

あとがき

こんな情報、私の検索能力ではどう調べても出てこなくて数日悩んで試行錯誤の上やっと気づきました(笑)
つよつよ開発者の方々はこんなこと当たり前に分かるのかなぁ、ともっと精進しなければと思わされました..

参考URL

libsodium: https://download.libsodium.org/libsodium/releases/
libopus: https://dsharpplus.github.io/natives/index.html

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

Dockerを用いてRuby on Railsの環境構築をする方法( Docker初学者向け )

Dockerを初めて勉強するとき、多くの方が「参考書」や「入門Dockerなどのサイト」で基礎的なところから学習すると思います。
しかし、Dockerとは何なのか、何のイメージも沸いていない状態で、様々な単語を羅列されても、さっぱりわからないという方も多いと思います。( 少なくとも私はそうでした。)
なので極論、基礎すっ飛ばしてまず実際にDockerで仮想環境を構築しながら学んでいこうという話です。
そして何となくでいいのでイメージを掴んでから、基礎を学べば良いと思います。
Dockerを初めて学習する方の中で、Ruby on Railsで開発したアプリケーションをポートフォリオとしている方も多いのではないでしょうか。
なので今回は、Dockerを用いてRuby on Railsの環境構築をする方法 をご紹介いたします。

ですが、超基本的なことだけ先に説明させてください。
( とにかく早く環境構築した人は 前提 まで飛んでください )

Dockerとは

1_y6CvfE6WUgoIdT8Mp0Ev_g.png
Dockerは 仮想環境を構築するためのツール です。

- Dockerエンジンとは -

Dockerエンジンとは Dockerイメージの作成 や コンテナの起動 などを行う、いわばDockerの要です。

- コンテナとは -

コンテナとはDockerエンジン上で構築される仮想環境そのものです。
コンテナは、CentOSUbuntsなどの OS、NginxMySQLなどの ミドルウェア、RailsWordPressなどアプリケーションまで、様々な環境を構築することができます。

- Dockerイメージとは -

Dockerイメージは、コンテナを作成するための レシピ のようなものです。
CentOS、MySQL、Ruby などなど、他にも数えきれないほどのDockerイメージがあります。

- Dockerfileとは -

Dockerfileは、オリジナルのDockerイメージを作成するための設計書のようなものです。
既存のDockerイメージをベースに、パッケージをインストールしたり、ファイルを書き換えたりした、新しいイメージを作成することができます。

- Docker composeとは -

docker-compose-button.jpg
Docker compose とは、複数のコンテナの構築、実行する手順を自動化するツールです。
その手順などを記したファイルがdocker-compose.ymlで、これのおかげで少ないコマンドの実行で複数のコンテナを起動することができます。

環境

  • MacOS
  • Docker 20.10.2
  • docker-compose 1.27.4
  • Ruby 2.5.7
  • Rails 5.2.4
  • MySQL 5.7

前提

  • DockerおよびDocker composeのインストールを済ませてください
  • これ以降の説明のmyappの部分はご自身のアプリケーション名に変えてください
  • とにかく早く環境構築したい方は、説明を飛ばし、コピペして コマンドを実行してください。

アプリケーションの作業ディレクトリを作成

mkdirで作業ディレクトリを作成します。

$ mkdir myapp

必要なファイルを用意

ファイル構成は以下のようになります。

myapp
  |-- Dockerfile
  |-- docker-compose.yml
  |-- Gemfile
  |-- Gemfile.lock

それでは、先ほど作成したディレクトリの中に以下の各ファイルを作成していきます。

① Gemfile

Gemfile
source 'https://rubygems.org'
gem 'rails', '~> 5.2.4', '>= 5.2.4.2'

③で作成するDockerfilebundle installする項目があるのですが、そのときにこのGemfileを使います。

② Gemfile.lock

Gemfile.lock

Gemfile.lockの中身は空でOKです。
Gemfile.lockGemfileとセットで必要なので、用意します。

③ Dockerfile

Dockerfile
FROM ruby:2.5.7
# ベースにするイメージを指定

RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs default-mysql-client vim
# RailsのインストールやMySQLへの接続に必要なパッケージをインストール

RUN mkdir /myapp
# コンテナ内にmyappディレクトリを作成

WORKDIR /myapp
# 作成したmyappディレクトリを作業用ディレクトリとして設定

COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
# ローカルの Gemfile と Gemfile.lock をコンテナ内のmyapp配下にコピー

RUN bundle install
# コンテナ内にコピーした Gemfile の bundle install

COPY . /myapp
# ローカルのmyapp配下のファイルをコンテナ内のmyapp配下にコピー

Dockerfileは自分オリジナルの イメージ を作成するための設計書のようなものです。
今回はruby:2.5.7という元々誰かが作ったイメージをベースに、Railsのインストールに必要なパッケージをインストールしたり、myappをコピーしたり、bundle installしたりと、様々なもの加えているというわけです。

④ docker-compose.yml

docker-compose.yml
version: '3'
# docker-composeの書式のバージョンを指定します。(原則、最新を指定する)

services:
  db:
    image: mysql:5.7
    environment:
      MYSQL_USER: root
      MYSQL_ROOT_PASSWORD: pass
    ports:
      - "3306:3306"
    volumes:
      - mysql_data:/var/lib/mysql

  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/myapp/
    ports:
      - 3000:3000
    depends_on:
      - db

volumes:
  mysql_data:

docker-compose.ymlは複数のコンテナを定義し、実行するためのファイルです。
ファイルに書かれている単語をひとつずつ解説してきます。

services:

servicesの中でコンテナを定義します。
今回はMySQLRailsをコンテナ化するので、名前をわかりやすくdbwebとします。
実際に作成されるコンテナ名は「アプリケーション名 + サービス名 + 連番」myapp_db_1myapp_web_1となります。

image:

利用するイメージを指定し、コンテナを構築します。

enviroment:

MySQLに関する環境変数を設定します。(今回はパスワードのみ)
パスワードはご自身で決めて構いません。

ports:

ポート番号に関するを設定します。
MySQL は 3306、 Rails は 3000 というポート番号がデフォルト値として設定されています。
Railsに関してはおなじみのlocalhost:3000の 3000 ですね。
Dockerを使わず、ローカルで環境構築した場合は直接このlocalhost:3000にアクセスすれば良かったわけですが、Dockerを使った場合、コンテナに外部からアクセスすることができないので、ちょっとした工夫が必要です。
そこで登場するのがこのports:です。
ports:- ローカル側のポート番号 : コンテナ側のポート番号で表します。
今回、ports: - 3000:3000としていますが、説明をわかりやすくするため、仮にもしports: - 9000:3000に設定するとします。
これはコンテナ側の3000ポート9000ポートでのアクセスを許可するという意味になります。
つまりこの場合、localhost:9000でアクセス可能になるということになります。

volumes:

ボリュームに関する設定をします。
ボリュームとはコンテナにおいて生成されるデータを永続的に保存するためのものです。
MySQLを例にとると、MySQLをコンテナ化し、そこに テーブル や カラム などのデータを保存したとします。
しかし、そのMySQLのコンテナを削除してしまうと、保存してあったテーブル、カラムなどのデータも一緒に削除されてしまいます。
それはまずいので、データだけローカルにも保存しておく、この仕組みをボリュームといいます。
このボリュームを利用すると、仮にコンテナを削除したとしても、新しくコンテナを立ち上げたときに、そのデータを再利用することができます。

ボリュームを保存する方法は大きく分けて2つあります。
1つ目は、保存したいディレクトリをマウントする方法、2つ目は、名前付きボリュームを利用する方法 です。

今回MySQLは2つ目の名前付きボリュームを利用する方法を使います。

この方法は、ローカルに名前付きのボリュームを作成し、そこにコンテナ側の指定したディレクトリを保存するというものです。
名前付きボリュームの場合、volumes:- ボリューム名 : コンテナ側の保存させたいディレクトリで表します。
今回、ボリューム名mysql_dataとし、コンテナ側の保存させたいディレクトリは MySQLのデータ保存場所である/var/lib/mysqlを指定します。
結果、volumes: - mysql_data:/var/lib/mysqlという書き方になります。
そして、version: や services: と同じ段落(トップレベル)の、一番下に書いているvolumes:にボリューム名を記載し、名前付きボリュームであることを明示します。

次にRailsは、1つ目の保存したいディレクトリをマウントする方法を使います。
この方法は、「ローカル側の指定したディレクトリ」と「コンテナ側の指定したディレクトリ」を同期させて、両者を常に同じ状態に保つというものです。
マウントする場合、volumes:- ローカル側の同期させたいディレクトリ : コンテナ側の同期させたいディレクトリで表します。
今回、volumes: - .:/myapp/としています。
つまり、.( docker-compose.ymlのあるディレクトリ、つまりローカル側のmyapp配下 ) と /myapp/( コンテナ側のmyapp配下 ) を同期するということです。
これにより、コンテナ起動後、ローカルのmyapp配下のファイルなどを編集すると、コンテナ側にも編集が反映されます。

build:

Dockerfileのあるディレクトリをして指定します。( docker-compose.ymlから見て同じディレクトリにDockerfileが存在するので.)
ここのステップで ③で説明したDockerfileを利用しオリジナルのイメージを作成し、コンテナを構築します。

command:

コンテナ起動時の実行されるコマンドを設定します。
今回設定するコマンドは、bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" です。
bash -c ""は コンテナの中に入り""内のコマンドを実行するという命令です。
rm -f tmp/pids/server.pidで rails server が起動していた場合、停止します。( 何らかの原因でrails server がすでに起動していた場合、新しいrails serverが起動できないため。)
bundle exec rails s -p 3000 -b '0.0.0.0'で rails server を起動します。
&&は複数のコマンドを実行するときに用います。

depends_on:

コンテナの作成順序の設定です。
depends_on: - dbは、MySQLのコンテナが起動してからRailsのコンテナを起動するという意味です。

rails new を実行

$ docker-compose run web rails new . --force --database=mysql --skip-bundle

アプリケーションの作業ディレクリに移動しdocker-compose runコマンドでrails newを実行します。

--force : 同じファイルがある場合、上書きする
--database=mysql : データベースにMySQLを指定する
--skip-bundle : bundle install をスキップする。( bundle installはあとで行います。)

このコマンドを実行すると、Dockerfileを元に、イメージとコンテナを作成し、そのコンテナの中でrails newを実行します。
よって、ローカルにも見慣れたファイルが作成されたと思います。

docker-compose build を実行

$ docker-compose build

rails newGemfileが更新されたので、bundle installするため、docker-compose buildを実行します。
先ほどのdocker-compose runで作成されたイメージはrails newされる前のGemfilebundle installしたイメージなので、更新されたGemfilebundle installしたイメージを再度作成する必要があります。

database.yml を編集

/config/database.yml
# MySQL. Versions 5.1.10 and up are supported.
#
# Install the MySQL driver
#   gem install mysql2
#
# Ensure the MySQL gem is defined in your Gemfile
#   gem 'mysql2'
#
# And be sure to use new-style password hashing:
#   https://dev.mysql.com/doc/refman/5.7/en/password-hashing.html
#
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
# 以下2行を編集
  password: pass
  host: db

development:
  <<: *default
  database: myapp_development

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  <<: *default
  database: myapp_test

# As with config/secrets.yml, you never want to store sensitive information,
# like your database password, in your source code. If your source code is
# ever seen by anyone, they now have access to your database.
#
# Instead, provide the password as a unix environment variable when you boot
# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
# for a full rundown on how to provide these environment variables in a
# production deployment.
#
# On Heroku and other platform providers, you may have a full connection URL
# available as an environment variable. For example:
#
#   DATABASE_URL="mysql2://myuser:mypass@localhost/somedatabase"
#
# You can use this database configuration with:
#
#   production:
#     url: <%= ENV['DATABASE_URL'] %>
#
production:
  <<: *default
  database: myapp_production
  username: myapp
  password: <%= ENV['MYAPP_DATABASE_PASSWORD'] %>

password:host:を編集します。
password: には docker-compose.ymlMYSQL_ROOT_PASSWORD:に書いたパスワードを記載します。
host: には docker-compose.ymlで命名したMySQLのコンテナ名dbを記載します。

docker-compose up -d でコンテナを起動

$ docker-compose up -d

このコマンドでコンテナを起動します。
-dをつけることでバックグラウンドで起動します。

これでコンテナが起動できました。
以下のコマンドでちゃんと起動しているか確認しましょう。

$ docker-compose ps

   Name                  Command               State                 Ports              
----------------------------------------------------------------------------------------
myapp_db_1    docker-entrypoint.sh mysqld      Up      0.0.0.0:3306->3306/tcp, 33060/tcp
myapp_web_1   bash -c rm -f tmp/pids/ser ...   Up      0.0.0.0:3000->3000/tcp 

このように、それぞれのコンテナが UP になっていれば起動成功です。

データベースを作成

以下のコマンドでデータベースを作成します。

$ docker-compose exec web rails db:create

localhost:3000にアクセス

localhost:3000にアクセスし、以下のページが表示されれば DockerによるRailsの環境構築 は完了です。
スクリーンショット 2021-01-20 19.39.11.png

環境構築後の開発の仕方

これまで通り、ファイルの編集などは基本ローカル環境で行います
しかしrails grails db:migrateなどの railsコマンド はコンテナ内で実行します。
なので、コンテナに入る必要があります。
以下のコマンドでRailsのコンテナに入ります。

$ docker-compose exec web bash

するとプロンプト( ターミナルの左側の部分 )が以下のように変わると思います。( @の後ろはコンテナのID )

root@97b8e3430f3f:/myapp#

これでコンテナの中に入れました。ここで railsコマンド を実行してください。
これまでのローカル環境での開発で、ターミナルで実行していたコマンドは全てこのコンテナ内で行います。( gitコマンドはローカル環境で行います )

コンテナを抜けるには、以下のコマンドを実行してください。( もしくはcontrol + d )

$ exit

- ログの表示 -

これまではrails sでサーバーを起動し、ログが表示されていたと思います。
コンテナ内において、ログを表示させる方法は以下の通りです。

コンテナ内に入り、以下のコマンドを実行します。

$ tail -f log/development.log

これでログが表示されます。

ターミナルでタブを3つ開き、「ローカルの操作をするタブ」「コンテナ内でrailsコマンドなどを実行するタブ」「ログを表示するタブ」に分けると効率的な開発ができるかもしれません。

- デバッグに関して -

開発中にエラーが発生し、controllerを編集し、デバッグしたとします。
この場合、一度サーバーを再起動させると思います。

Dockerの場合、Railsのコンテナ( myapp_web_1 )ごと以下のコマンドで再起動します。

$ docker-compose restart web

しかし、デバッグするたびにいちいちコンテナを再起動するのは非効率です。
なので、サーバー起動中もコードの更新をチェックしてくれるように設定します。

config/environments/development.rbの一番下に記載されている以下を

config.file_watcher = ActiveSupport::EventedFileUpdateChecker

次のように変更します。

config.file_watcher = ActiveSupport::FileUpdateChecker

これで再起動を行わずにデバッグが可能になります。

その他の基本的なDockerのコマンド

・ コンテナの停止、削除

$ docker-compose down

・ コンテナの停止、削除および紐づいている名前付きボリュームの削除

$ docker-compose down -v

※ 永続化しているデータが消えるの要注意 !

・ docker-composeのログを出力

$ docker-compose logs

コンテナがうまく起動しなかったときなどにログを見ると、原因がわかったりします。

・ イメージの一覧

$ docker images

・ イメージの削除

$ docker rmi イメージID

・ 名前付きボリュームの一覧

$ docker volume ls

・ 名前付きボリュームの削除

$ docker volume rm ボリュームの名前

まとめ

最初の内は理解できないことも多いと思います。
ひとつずつ着実に理解していくのではなく、全体的に漠然と理解していくと良いと思います。

この記事を読んでわからないことがあれば、コメントしていただければお答えいたします !

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

参考

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

ゲッターとセッターをの理解をごっちゃにしないために【なんでもかんでもアクセサを使わない!】

ゲッターとセッターの解説記事は何個かあるので、そこはお任せするとして、
あと、アクセサという便利なコードもありますが、使い分けについて書かれてる物がすくなかったので、初学者編の基礎として振り返ろうと思って書きました。

まずはよくある車を設計する所です。

アクセサを使える場合

車の設計
class Car
  attr_accessor :model, :color, :option
  def initialize(model, color, option)
    @model = model
    @color = color
    @option = option
  end
end

car = Car.new("Benz", "white", "ホイール")

#=> すべて変更可能

これだったら、attr_accessor でまとめてしまっても良いのですが、
例えば 「もう車作ってるのに、車種と色変えられたら困るよ!!!(オプションだけだったらいいけど・・・)」 という場合の書き方は、

ゲッターとセッターを使う場合

車の設計
class Car
  attr_reader :model, :color, :option
  attr_writer :option 
  def initialize(model, color, option)
    @model = model
    @color = color
    @option = option
  end
end

car = Car.new("Benz", "white", "ホイール")

#=>オプションだけ変更可能

っていう書き方が普通になりますけど、
アクセサを使って更にスマートに書くのであれば、

アクセサとゲッターを使うのがスマート

車の設計
class Car
  attr_accessor :option
  attr_reader :model, :color 
  def initialize(model, color, option)
    @model = model
    @color = color
    @option = option
  end
end

car = Car.new("Benz", "white", "ホイール")

#=>オプションだけ変更可能

になります。

ちなみに、

車の車種を変更しようとすると・・・

車の設計
class Car
  attr_accessor :option
  attr_reader :model, :color 
  def initialize(model, color, option)
    @model = model
    @color = color
    @option = option
  end
end

car = Car.new("Benz", "white", "ホイール")

car.model = "BMW"
#=>NoMethodError (undefined method `model=' for #<Car:0x00007fc009900440>)

という風に怒られます!!「車種変えるんじゃねぇよ!もう作ってるんだよ!」という事ですね。色を変えようとしても同じようなエラーになります。

まとめ

という事で、Ruby初学者が勉強するゲッター、セッター、アクセサの使い分けについて振り返っていきました。
初学者の方の参考になれば幸いです。ありがとうございました!!

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