- 投稿日:2020-10-10T23:42:36+09:00
【RSpec】ヘルパーメソッドが使われたviewファイルをテストしたらActionView::Template::Error:が出た。。
個人開発のアプリに通知機能を実装し、その後GitにpushしてCircleCIのテスト結果を確認したら突然大量のテストが失敗していて少し困ったので、その解決方法をここで共有させて頂きます。
エラー内容
Failure/Error: -if unchecked_notifications.any? ActionView::Template::Error: undefined local variable or method `unchecked_notifications' for #<#<Class:0x0000555732134140>:0x00005557326f7f78>通知が来ている場合と来ていない場合で表示するviewを変えるために、
unchecked_notifications
という、ヘルパーメソッドを定義しました。しかし、RSpec実行時にはこのヘルパーメソッドが読み込まれておらずエラーが出てテストに失敗してしまったようです。notifications_helper.rbmodule NotificationsHelper def unchecked_notifications @notifications = current_user.passive_notifications.where(checked: false) end end解決策
解決策は簡単です。
テストを実行するファイルに、モジュールをインクルードするれば解決するはずです。↓例post_spec.rbrequire 'rails_helper' #ヘルパーメソッドをインクルード include NotificationsHelper RSpec.describe '投稿機能', type: :system do #テスト処理 end最後まで読んでいただきありがとうございます!
日々学習したことをアウトプットしてます!ご指摘などあればコメントいただけますと幸いです!
- 投稿日:2020-10-10T23:39:05+09:00
each文の中にeach文でいい気分!?
はじめに
each文が入れ子になると、読みにくいので、どう考えれば良いのか、まとめてみた。
配列の中の配列の中の配列
shopping_price = [["野菜", [200, 250, 220]], ["フルーツ", [1200, 1500]]]そもそも配列の中に配列がある場合、どのようにして中の値を取り出すのか。
それは、添字を重ねることで、取り出すことができる。
上の配列で1200
を取り出すなら、
puts shopping_price[1][1][0]
となる。
each文
基本的な記述は
配列.each do |ブロックパラメーター(変数)| #繰り返したい処理 endブロックパラメーターは
do~end
の中でのみ扱える変数。イメージとしては、配列の中が一つ一つ入っていく感じ。配列の名前が複数形の場合、その単数形を入れることが多い。
each文の中にeach文
shopping_price = [["野菜", [200, 250, 220]], ["フルーツ", [1200, 1500]]] shopping_price.each do |shopping| sum = 0 shopping[1].each do |price| sum += price end puts "#{shopping[0]}の合計金額は#{sum}" end #=>野菜の合計金額は670円 #=>フルーツの合計金額は2700円外のeach文には、
1回目に["野菜", [200, 250, 220]]
2回目に["フルーツ", [1200, 1500]]
が|shopping|
の中に入る。中のeach文の配列には、
shopping[1]
が当てられている。
つまり、
1回目に[200, 250, 220]
2回目に[1200, 1500]
がprice
の中に入る。1回目の計算は、
sum = 0 + 200
sum = 200 + 250
sum = 450 + 220となる。
shopping[0]には、
野菜
、フルーツ
がそれぞれ入る。最後に
[角括弧]がたくさん出てきて、読みにくいが、どの[角括弧]も必要。
each文の中にeach文や配列の中に配列は読みにくいので、リファクタリングが必要だろう。
- 投稿日:2020-10-10T23:35:01+09:00
検索機能の実装
はじめに
個人アプリの作成にて、投稿した記事の内容を検索で表示出来るように検索機能の実装を行う。
ルーティングの設定
今回は:idを指定してページに遷移しないので、collectionを使用してルーティングを設定します。
routes.rbresources :posts do get :search, on: :collection resource :likes, only: [:create, :destroy] -いいね機能実装 endモデルの設定
LIKE句
LIKE句は、文字列の検索をすることができる。
whereメソッドと一緒に使う。
実行例 詳細 where('title LIKE(?)', "a%") aから始まるタイトル where('title LIKE(?)', "%b") bで終わるタイトル where('title LIKE(?)', "%c%") cが含まれるタイトル where('title LIKE(?)', "d_") dで始まる2文字のタイトル where('title LIKE(?)', "_e") eで終わる2文字のタイトル app/models/post.rbdef self.search(search) return Post.all unless search Post.where('body LIKE(?)', "%#{search}%") endコントローラーにsearchアクションを定義
post_controller.rbdef search @posts = Post.search(params[:keyword]) endviewの実装
renderメソッドを使って、部分テンプレートを行っているものとします。
{ post: post } の右側のpostはeachメソッド内の変数としてのpostでpostのインスタンスを示しています。左側のpostは部分テンプレート内での変数の名前を表しています。search.html.erb<% @posts.each do |post| %> <%= render partial: "post", locals: { post: post } %> <% end %>おわりに
比較的簡単に検索機能の実装は完了しました。
他にも機能を実装し、出来ることを増やしていこうと思います。
最後まで見ていただきありがとうございます
- 投稿日:2020-10-10T21:58:23+09:00
Ruby Leet文字列に変換
はじめに
こちらは学習メモです。
Leetとは?
たとえば、「Warez」という語を leet で表記すると、「W@rez」や「W4r3z」などとなるように、一部のアルファベットを形の似た数字や記号などに変化させること。
今回は、Rubyで以下のように変換します。
アルファベット⇄記号変換表
アルファベット 記号 A 6 B 8 C 5 D 3 E 1 入力された文字列に対してLeetで文字を変換する。その後、文字列を出力する。
コード
word = gets.chomp.split('') word.each do |w| case w when 'A' print '6' when 'B' print '8' when 'C' print '5' when 'D' print '3' when 'E' print '1' else print w end end入力例
ABKTED[実行結果]
68KT13解説
word = gets.chomp.split('')・1行目では入力された文字を一文字ずつ区切って配列にし、word変数に代入
getsメソッド: 入力を一行ごとに「文字列」で受け取る。
chompメソッド: 文字列の改行を取り除く。
splitメソッド: 文字列を分解して配列にする。word.each do |w|・word変数の要素をw変数に代入する
2行目以降はcaseによって一致判定・文字の入れ替えを一文字ずつ行っています。
- 投稿日:2020-10-10T21:48:33+09:00
【Ruby】ハッシュテーブルの順番を逆にしたい
はじめに
たとえば、
reputation = {very_bad: '最悪', bad: '悪い', good: '良い', very_good: '最高'}
みたいなハッシュテーブルがあったとして、その順番を{very_good: '最高', good: '良い', bad: '悪い', very_bad: '最悪'}
のように後ろから並べたい。やり方
pry(main)> reputation.to_a.reverse.to_h #=> {very_good: '最高', good: '良い', bad: '悪い', very_bad: '最悪'}これだけです。
一応説明すると、
reverse
っていう配列の順番を逆にするメソッドを使いたいんだけどreputation
はhashなので使えない。なので一度to_a
でhashを配列に変換してreverse
した後、そのままだと配列のままなのでto_h
で元の戻す、ということです。あとがき
「gemの
enum_help
を使ってラジオボタンの表示は日本語表記に、内部ではint型で処理したい。評価が低い方から数字も低い方を割り当てたい、しかしラジオボタンの表示は評価が高い方から表示したい。」という場面で必要でした。
普通enum使うときってvalueが数字になるはずだから並べ替えやすいんですが、enum_help使うと上に書いたようなhashを渡すことになるので「どうやって並べ替えるんだ…」ってなりました。記事にすることもないような気がしますが、僕のような初心者だと
to_s
やto_i
は使っても、to_a
とかは使う機会が少なくて詰まることがあると思うので少しでも誰かの役に立てば。
- 投稿日:2020-10-10T21:27:39+09:00
ざっくりすぎるdocker-composeでのRails5+MySQL8.0+top-level volumesの環境構築
丁寧すぎるDocker-composeによるrails5 + MySQL on Dockerの環境構築(Docker for Mac)
とほぼ同じ内容です。
なので詳しく知りたい方はそちらへどうぞー
mysqlのバージョンが異なっていたり、top-level volumesを使っているなど少し異なりますー環境
- MacOS 10.15.7
- Docker Desktop for Mac
- Ruby 2.7.1
- Rails 5.2.4.4
- MySQL 8.0.21
$ mkdir rails_docker $ cd rails_docker$ vi DockerfileDockerfileFROM ruby:2.7.1 RUN apt-get update -qq && \ apt-get install -y build-essential \ libpq-dev \ nodejs \ && rm -rf /var/lib/apt/lists/* RUN mkdir /recruit_web ENV APP_ROOT /recruit_web WORKDIR $APP_ROOT ADD ./Gemfile $APP_ROOT/Gemfile ADD ./Gemfile.lock $APP_ROOT/Gemfile.lock RUN bundle install ADD . $APP_ROOT$ vi GemfileGemfilesource 'https://rubygems.org' gem 'rails', '~> 5.2.4', '>= 5.2.4.4'$ touch Gemfile.lock$ vi docker-compose.ymldocker-compose.ymlversion: '3' services: db: image: mysql:8.0.21 volumes: - db_data:/var/lib/mysql networks: - rails_docker_network environment: MYSQL_DATABASE: root MYSQL_ROOT_PASSWORD: password # mysql8.0の認証プラグイン(caching_sha2_password)をmysql_native_passwordに変更 command: --default-authentication-plugin=mysql_native_password container_name: rails_db_container web: build: . depends_on: - db command: rails s -p 3000 -b '0.0.0.0' volumes: - .:/recruit_web networks: - rails_docker_network ports: - "3000:3000" container_name: rails_web_container volumes: db_data: networks: rails_docker_network: name: rails_docker_network$ docker-compose run web rails new . --force --database=mysql --skip-bundle$ vi /confing/database.ymldatabase.ymldefault: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root # 追加 password: password # 追加 host: db # 追加$ docker-compose build $ docker-compose up -d以下のコマンドを実行するとデータベースが作られてlocalhost:3000でアクセスできると思いますー
$ docker-compose run web rails db:create詰まったところ
docker-compose.ymlversion: '3' services: db: image: mysql:8.0.21 volumes: - db_data:/var/lib/mysql environment: MYSQL_DATABASE: root MYSQL_ROOT_PASSWORD: password # 以下を指定していたらエラーが出た ports: - "3306:3306 web: build: . command: rails s -p 3000 -b '0.0.0.0' volumes: - .:/app_sample ports: - "3000:3000" # 以下を指定していたらエラーが出た(非推奨+なくても名前解決されるので必要なかった) links: - db volumes: db_data:なぜかlinks と db の公開ポートを指定していると
Mysql2::Error::ConnectionError: Access denied for user 'root'@'172.19.0.4' (using password: YES)と怒られかなり躓きました。。。
とりあえず、ポートは必要ないと気づき削除
ドキュメントよりlinksは非推奨でなくても名前解決できるそうなので削除
https://docs.docker.com/compose/networking/何か改善点あればよろしくおねがいしますー
- 投稿日:2020-10-10T21:22:59+09:00
Ruby 四則演算
はいじめに
学習用のメモになります
四則演算とは?
四則演算とは、足し算・引き算・掛け算・割り算のことをいいます。 ワークシート上やマクロの中でも四則演算は使われています。 四則演算は下記のような記号を使って計算式を作ります。
四則演算
基本的な四則演算を行うための演算子は次の通りです。
演算子 内容 例 * 乗算 5 * 3 / 除算 8 / 2 + 加算 3 + 6 - 減算 5 - 2 また四則演算以外にも次の演算子が用意されています。
演算子 内容 例 % 剰余 5 % 3 ** べき乗 5 ** 2 剰余は演算子の左辺を右辺で割った余りです。「5 % 3」の剰余は「2」となります。
べき乗は演算子の左辺を右辺の値で累乗した値です。「5 ** 2」は「25」となります。
- 投稿日:2020-10-10T21:01:34+09:00
Rubyのエラーが出ている原因の見つけ方について
前書き
よく出てくるエラー文を翻訳しなくてさっと確認する方法を解説してみました!
(ArgumentError) 引数エラー
このエラーが出た時は、文字通り引数についてのエラーになります。
例えば、引数の数があっていない時や値が正しくない時出たりします。下記のようなエラー文が出たとします。
一部分を記載arguments(given 2, expected 0)訳すと、
引数(与えられた 2, 期待される 0)
極端な例ですが、渡す引数が2で
受け取る側が引数がない状態なのでエラーが出てますよみたいな感じになります。
(ArgumentError) これが出た時は、(given,expected)を探してみてください!(NameError)名前エラー
名前に関するエラー。
変数名やメソッド名に間違いがある時にでる。
定義した時の名前が間違っているか、メソッドを呼び出した時の名前が間違っている可能性があります。一部分を記載undefined local variable or method '〇〇'訳すと、
未定義のローカル変数またはメソッド
〇〇の変数orメソッドに注目してみましょう!did you mean?(もしかして)
と提案してくている場合も多いのでここもチェック!(NoMethodError)ノーメソットエラー
メソットに関するエラー。
実行しようとしたメソットの呼び出し時の名前が間違っているか、メソッドが定義時の名前が間違っている可能性がある時にでる。一部分を記載test.rb:2in '〇〇'これはエラーが出ている部分を教えてくれいるので、この部分のメソッドを確認して見よう!
ノーメソッドエラーにも下記の記載があったらチェック!
did you mean?(もしかして)まとめ
とりあえずエラー文が出た時は、翻訳かけれましょう。
それで意味が分かれば見えてくる部分があると思います。出てきた意味をググってみたら解決方法が見つかる場合もありますので!
エラーが出た時こそ、成長のチャンスです。
って思えるように頑張ります!
- 投稿日:2020-10-10T20:38:31+09:00
Ruby on Rails 変数、定数まとめ
Ruby on Rails 変数、定数まとめ
Railsにおいてよく使う変数、定数についてまとめました。
前提条件
- 変数
- 変更可能
- 定数
- 定数不可
変数
ローカル変数
- メソッドやブロック内でのみ有効
- 変数名
- 小文字のスネークケース
user_name = 'jon'
Railsでの活用
- View
- View でローカル変数を扱いたい場合は、Viewファイル内で定義する。 [users/show.html.erb]
<% hoge = 'huga' %> <div><%= hoge %><div>
- Controller
- Viewに変数の値をわたす必要がなかったり、Contrller内で変数の値を共有する必要がない場合は、ローカル変数に定義する
def create article = Article.find( params[:id]) return render_404 if article.blank? endインスタンス変数
- 同じコントローラー内で使える変数
- 変数名
- 先頭に
@
を付けるRails での活用
- Controller Action内で定義したインスタンス変数を View へわたす。
def show @user = User.find(params[:id]) end[users/show.html.erb]
<div>ユーザーID:<%= @user.id %><div>※ローカル変数をわたすことはできない
def show user = User.find(params[:id]) end[users/show.html.erb]
<div>ユーザーID:<%= user.id %><div>-> 参照不可
undefined local variable or method 'user'
グローバル変数
- 全てのContrller使える
- プログラムのどこからでも、変更、参照が可能
- 変数名
- 先頭に
@
を付ける定数
クラス内で定義する
定数名
- 大文字、スネークケース
Rails での活用
- [config/initializers/constants.rb]に共通の定数を定義する
- 全ての Controller, View で参照することができる [config/initializers/constants.rb]
MAX_SIZE = 10
- 各 Model で使用する定数を定義するには、model ファイルのクラス内に定義する。 [user.rb]
class User OFFICIAL_ID = 100 endController や View で参照する場合は、
User::OFFICIAL_ID
と書く
- 投稿日:2020-10-10T20:04:05+09:00
[Rails5.2] Docker内のMysql 5.7の絵文字対応(文字コードをutf8mb4に変更する)
はじめに
Ruby on Railsでポートフォリオを作成しているRails初学者です。
今回はタイトルの通り、tableに絵文字情報を保存するためのmysqlをutf8mb4にした際の覚書です。Youtube Data APIから動画タイトルを保存しようとしたらエラーが出た。
ActiveRecord::StatementInvalid in VideosController#refresh Mysql2::Error: Incorrect string value: '\xF0\x9F\x94\xB5\xE8\x87...'絵文字が入っているタイトルがいくつかあり、それが影響でエラーが出たようです。
mysql上のdbの文字コードを見るとuft8になっており、これを変更しなければいけない様子・・・
ひとまず色々なコードを参考にして以下の通りに実施しました。環境
Docker for mac を使ってコンテナ内でRailsを開発しています。
ruby 2.4.5 mysql 5.7.31 Ruby on rails 5.0.7.2流れ
(準備:dumpする)
1. my.cnfを変更する
2. mysqlをリスタートする
3. ActiveRecordにオプションを追加
4. database.ymlの変更
5. docker-compose.ymlの変更し、コンテナの再起動
6. db:migrate:resetを実行
7. dumpデータのrestore準備:dumpする
そもそもこの作業を行うまでdumpという言葉を知らなかったのですが、dumpとはDBのテーブル内の情報をSQLの形で出力することだそうです。(ちなみに最後に行うrestoreは、dumpした情報をDBに投入すること)
今回データベース設定を変更し、各テーブルの文字コードをutf8mb4にするので、一旦データをファイルとして落としておいて、設定変更後に投入するという形をとりります。
dump/restoreを簡単に行うために以下のgemを使用します。
Gemfilegem 'yaml_db'今回使用するのはyaml_dbという、ダンプでyaml形式ファイルを出力してくれるgemです。
GitHub - yamldb/yaml_dbdumpするには以下のコマンドをすればOK
Terminalbundle exec rails db:data:dumpコマンド実行後、
db/data.yml
にファイルが出力されています。
こんな感じで、基本的にはテーブル名、カラム情報、レコードの順番で出力されます。data.ymlvideos: columns: - id - name - url - upload_at - created_at - updated_at records: - - 1 - "【衝撃】ボーナス支給額をクイズで決める会社" - https://www.youtube.com/watch?v=42ofwfioMFM - 2020-10-09 09:00:00.000000000 Z - 2020-10-10 08:07:38.000000000 Z - 2020-10-10 08:07:38.000000000 Z :以下で準備はOKです。
1. my.cnfを変更する
my.cnfファイルを以下のように設定します。
記載されている内容をきちんと理解したかったため、1行ずつ見ていきました。my.cnf[mysql] default-character-set=utf8mb4 #文字コードの設定 [mysqld] #mysqld Mysqlサーバの設定 character-set-server = utf8mb4 #文字コードの設定 skip-character-set-client-handshake #client側で指定した文字コードを無視するためのもの collation-server = utf8mb4_general_ci #ソート順の指定 init-connect = SET NAMES utf8mb4 #クライアントからserverへの送信に使用される文字セット指定各項目について、細かく見ていきます。
[mysql]と[mysqld]
mysqldとは、MySQLサーバとも呼ばれる、mysqlでの各種操作を受け持っているメインプログラムのこと。
mysql側の操作は必ずこのmysqldよりされるため、ここに各種設定が必要。
skip-character-set-client-handshake
character-set-client-handshakeとは、クライアント側の文字コードをMySQL側に反映する行為。これをスキップすることで、uft8mb4への設定ができる。
collation-server = utf8mb4_general_ci
collationは、照合順序:ソート順のこと。_で区切られた各単語でそれぞれ設定。
詳細は以下を参照
【MySQL】照合順序とは?
init-connect = SET NAMES utf8mb4
クライアントからサーバへの送信に使用される文字コードの指定
詳細は以下を参照
10.1.4 接続文字セットおよび照合順序2. mysqlをリスタートする
ひとまず、この設定を反映するためにmysqlをリスタートします。
Docker内のコンテナに入り以下のコマンドを実行します。もともとの設定を一応確認しておきます
terminalmysql>status; :(中略) Server characterset: utf8 Db characterset: utf8 Client characterset: utf8 Conn. characterset: utf8 :utf8になっています。
というわけで、一旦リスタートして、もう一度みてみます。terminalmysql>service mysql restartterminalmysql>status :(中略) Server characterset: utf8mb4 Db characterset: utf8mb4 Client characterset: utf8mb4 Conn. characterset: utf8mb4 :設定できました!
3. ActiveRecordにオプションを追加
次に、ActiveRecordのcreate_tableが実行される際に、utf8mb4を使って登録するよう設定していきます。
新しく
config/initializers/utf8mb4.rb
を作成し、以下のコードを記載。
config/initializersにファイルを投入すると、Railsが起動する前に初期設定として読み込まれるようになります。config/initializers/utf8mb4.rb#optionを設定するmodule module Utf8mb4 def create_table(table_name, options = {}) table_options = options.merge(options: 'ENGINE=InnoDB ROW_FORMAT=DYNAMIC') super(table_name, table_options) do |td| yield td if block_given? end end end ActiveSupport.on_load :active_record do module ActiveRecord::ConnectionAdapters class AbstractMysqlAdapter #最初にmodule utf8mb4を実行し、その後既存のメソッドを実行(super) prepend Utf8mb4 end end end4. database.ymlの変更
以下の内容を追加ないし変更します
config/database.ymlcharset: utf8mb4 encoding: utf8mb4 collation: utf8mb4_general_ci5. docker-compose.ymlの変更
Dockerの設定ファイルも以下のように変更します。
docker-compose.yml: db: image: mysql:5.7 command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci #utf8mb4をセット container_name: コンテナ名 volumes: - ./my.cnf:/etc/mysql/conf.d/my.cnf #my.cnfを読み込むよう設定 :この状態で、一度コンテナを再起動します。
6. db:migrate:resetを実行
ここまでのDBへの設定を反映させるため、DBをリセット→migrateします。
7. dumpデータのrestore
最後に、冒頭でdumpしたデータをもう一度投入すれば完了です。
以下のコマンドでrestoreできます。terminalrails db:data:load以上で設定は完了です!
終わりに
色々と設定してきましたが、dumpなどの機能を知ることができたので、いい勉強になりました。
全面的に参考にさせていただいたサイト様
大変ありがとうございます・・・!
・MySQLのencodingをutf8からutf8mb4に変更して寿司ビール問題に対応する
・RailsのDBバックアップ(gem:yaml_db)
・Rails5で絵文字を保存する utf8mb4 (docker)
- 投稿日:2020-10-10T19:47:06+09:00
DockerでのRailsアプリケーション構築
概要
Dockerの勉強会の際、作成したリポジトリがありそのまま埋めてるの勿体無いと思ったので記事にしました。
どなたかの参考になればいいなぁと思ってます。https://github.com/yodev21/docker_tutorial_rails
作業手順
ファイル作成
下記ファイルを作成
Dockerfile docker-compose.yml Gemfile Gemfile.lock# イメージ名にRuby(Ver2.6.5)の実行環境のイメージを指定 FROM ruby:2.6.5 # パッケージのリストを更新しrailsの環境構築に必要なパッケージをインストール RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs # プロジェクト用のディレクトリを作成 RUN mkdir /myapp # ワーキングディレクトリに設定 WORKDIR /myapp # プロジェクトのディレクトリにコピー COPY Gemfile /myapp/Gemfile COPY Gemfile.lock /myapp/Gemfile.lock # bundle install実行 RUN bundle install # ビルドコンテキストの内容を全てmyappにコピー COPY . /myappdocker-compose.ymlversion: '3' services: db: # postgresのイメージを取得 image: postgres environment: POSTGRES_USER: 'postgresql' POSTGRES_PASSWORD: 'postgresql-pass' restart: always volumes: - pgdatavol:/var/lib/postgresql/data web: # Dockerfileからイメージをビルドして使用 build: . # コンテナ起動時に実行 command: bundle exec rails s -p 3000 -b '0.0.0.0' # カレントディレクトリを/myappにバインドマウント volumes: - .:/myapp # 3000で公開して、コンテナの3000へ転送 ports: - "3000:3000" # Webサービスを起動する前にdbサービスを起動 depends_on: - db # データ永続化のためにpgdatabolのvolumeを作成し、postgresqlのデータ領域をマウント volumes: pgdatavol:source 'https://rubygems.org' gem 'rails', '5.2.4.2'Gemfile.lockrailsアプリケーション作成
docker-compose run web rails new . --force --database=postgresqlrailsプロジェクトに使用するデータベースの設定ファイルを修正
database.ymldefault: &default adapter: postgresql encoding: unicode # -------- 追加 -------- host: db username: postgresql password: postgresql-pass # -------- ここまで --------デタッチモード(バックグラウンド)で起動
docker-compose up -dbundle installが反映されない場合の対応
docker-compose build --no-cacheデータベース作成コマンド
docker-compose run web rails db:createScaffoldにて簡易的なアプリケーション作成
docker-compose run web bin/rails g scaffold User name:stringdocker-compose run web bin/rails db:migrateコンテナの停止
docker-compose stopコンテナ削除
docker-compose downコンテナ内に移動
docker-compose run web bashGo言語を使用してみる
ディレクトリ移動
cd doc/golang/FROM golang:1.9 RUN mkdir /echo COPY main.go /echo CMD ["go", "run", "/echo/main.go"]イメージのビルド
docker image build -t example/echo:latest .イメージの確認
docker image lsコンテナの起動
docker container run -d -p 9000:8080 example/echo:latestGETリクエストの確認
curl http://localhost:9000
- 投稿日:2020-10-10T19:05:22+09:00
投稿者のみアクセスできるようにする記述
開発環境
Ruby 2.6.5
Rails 6.0.3.3問題点
投稿者のみレビューの削除ができるように実装したいが、NoMethodErrorが出てしまう
原因
当初、条件式を以下のように記述をしておりました。
<% if current_user.id == @review.user_id %>こちらは現在ログインしているユーザーと投稿者が同じ場合、条件式がTrueとなり処理を実行するという記述です。
しかし、こちらの記述で読み込むとエラーが発生しておりました。確認したところ正しくは以下のような記述でした。
<% if user_signed_in? && current_user.id == @review.user_id %>こちらの記述でログインの有無を確認することにより正常に処理を行えるようになりました。
初歩的な箇所ですが、改めて気付いたので、備忘録として投稿いたしました。
- 投稿日:2020-10-10T18:57:58+09:00
Ruby × AWS Lambda × CloudWatch Eventsで定期実行プログラムを作成する
本記事で目指す構成
CloudWatch EventsをトリガーにLambdaを起動し、Slackへメッセージを飛ばす。
※ CloudWatch EventsにはあらかじめCron式で実行スケジュールを設定しておく。↑こんな感じで自動で定期実行してくれるようになる。
対象読者
- Rubyで定期実行プログラムを作成してみたい人
- Lambdaに触れてみたい人
仕様
言語: Ruby2.5系
インフラ: Lambda、ClowdWatch Events※ SlackBotは各自あらかじめ作成しておいてください。
完成形
periodic-slack-bot-on-aws-lambda
※ 一から作るのが面倒な方は↑からgit cloneしてください。
プログラムを作成
Slackへメッセージを飛ばすためのプログラムを作成していく。
ディレクトリを作成
$ mkdir periodic-slack-bot-on-aws-lambda $ cd periodic-slack-bot-on-aws-lambdaGitを設定
$ git init $ touch .gitignore./.gitignore.bundle /vendor/bundleGitで管理したくないものを記述。
Rubyのバージョンを指定
$ rbenv local 2.5.1 # 2.5系を推奨Gemをインストール
$ bundle init↑のコマンドでGemfileを生成し、以下のように編集する。
./Gemfile# frozen_string_literal: true source "https://rubygems.org" git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } ruby '2.5.1' gem 'async-websocket' gem 'slack-ruby-bot'その後、必要なGemをインストール。
$ bundle install --path vendor/bundle※ 後ほどGemも含めてzipファイルにパッケージングする必要があるため、グローバルではなく「vendor/bundle」以下(ローカル)にインストールしなければならない。
app.rbを作成
$ touch app.rb./app.rbrequire 'slack-ruby-client' Slack.configure do |conf| conf.token = ENV['SLACK_BOT_TOKEN'] end def post_to_slack(event:, context:) client = Slack::Web::Client.new client.chat_postMessage(channel: ENV['SLACK_CHANNEL_NAME'], text: 'テスト送信 from AWS Lambda', as_user: true) end※ 引数に「(event:, context:)」を渡さないと動かないので注意。
参照記事: Python の AWS Lambda 関数ハンドラーLambdaへデプロイ
プログラムを作成したら、Lambdaへデプロイするための準備を行う。
AWS CLIをインストール
今回は「AWS CLI」と呼ばれるツールを使いながらデプロイしていくので、まだインストールできてないない場合はインストールしておく。
$ brew install awscliIAMユーザーを作成
デプロイを行うためのIAMユーザーを作成していく。
まずは「IAM」→「ポリシー」→「ポリシーの作成」へと進み、JSONタブから以下の文を貼り付ける。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "apigateway:*", "cloudformation:*", "dynamodb:*", "events:*", "iam:*", "lambda:*", "logs:*", "route53:*", "s3:*" ], "Resource": [ "*" ] } ] }
適当にポリシー名や説明を記述し、「ポリシーの作成」をクリック。
次に「IAM」→「ユーザー」→「ユーザーの作成」へと進み、適当な名前を付けた後「プログラムによるアクセス」にチェックを入れて次へ進む。
「既存のポリシーを直接アタッチ」から先ほど作成した「MinimalDeployIAMPolicy」を選択し、次へ進む。(タグは任意でOK)最後に確認画面が表示されるので、問題無ければ「ユーザーの作成」をクリック。
すると「アクセスキーID」と「シークレットアクセスキー」の2つが発行されるので、csvファイルをダウンロードするなりメモするなり大事に保管しておく。AWS CLIの設定
$ aws configure --profile lambda(任意のプロフィール名でOK) AWS Access Key ID # 先ほど作成したアクセスキーID AWS Secret Access Key # 先ほど作成したシークレットアクセスキー Default region name # ap-northeast-1 Default output format # jsonターミナルで「aws configure」と打ち込むと対話形式で色々聞かれるので、それぞれ必要な情報を入力していく。
関数を作成
AWSのコンソール画面から「AWS Lambda」を開き、「関数の作成」をクリック。
- 関数名(任意の名前)
- ランタイム(Ruby2.5)
を入力して関数を作成する。(その他はデフォルトの値もしくは空欄でOK)
「環境変数の編集」から
- SLACK_BOT_TOKEN
- SLACK_CHANNEL_NAME
それぞれ環境変数をセット。
「基本設定を編集」からハンドラ名を「app.post_to_slack(ファイル名.メソッド名)」に変更。
デプロイ
作成したプログラムをzipファイルとしてパッケージング。
$ zip -r function.zip app.rb vendoraws cliを使いデプロイ。
$ aws lambda update-function-code --function-name PeriodicSlackBotOnAwsLambda --zip-file fileb://function.zip --profile lambda { "FunctionName": "PeriodicSlackBotOnAwsLambda", "FunctionArn": "arn:aws:lambda:ap-northeast-1:**************:function:PeriodicSlackBotOnAwsLambda", "Runtime": "ruby2.5", "Role": "arn:aws:iam::**************:role/service-role/PeriodicSlackBotOnAwsLambda-role-**************", "Handler": "app.post_to_slack", "CodeSize": 12941753, "Description": "", "Timeout": 3, "MemorySize": 128, "LastModified": "2020-10-09T21:08:11.888+0000", "CodeSha256": "**************/**************=", "Version": "$LATEST", "Environment": { "Variables": { "SLACK_CHANNEL_NAME": "#**************", "SLACK_BOT_TOKEN": "xoxb-**************-**************-**************" } }, "TracingConfig": { "Mode": "PassThrough" }, "RevisionId": "********-****-****-****-********", "State": "Active", "LastUpdateStatus": "Successful" }※ --function-nameには先ほど作成した関数名をセット。
テスト実行
「テストイベントの設定」から適当にテストを作成。(ハッシュは空欄でOK)
「テスト」をクリックし、指定したSlackチャンネルへメッセージが飛んでいれば成功。
CloudWatch Eventsでスケジュール管理
トリガーの設定
「トリガーを追加」をクリック。
- トリガー名: CloudWatch Events
- ルール: 新規ルールの作成
- ルール名: test_30minutes(任意)
- ルールの説明: 30分に1回実行(任意)
- ルールタイプ: スケジュール式
- スケジュール式: cron(*/30 * * * ? *)
※ 実行間隔はそれぞれお好みで設定。
cron式の書き方の説明については今回省略。
参照: クーロン(cron)をさわってみるおもし上手く行かなかった場合はCloudWatchのロググループ内にログが出力されているはずなので適宜デバッグ。
あとがき
お疲れ様でした!もし記事通りに進めて上手く動作しない箇所があったらコメント蘭などで指摘していただけると幸いです。
- 投稿日:2020-10-10T18:52:14+09:00
モンティホール問題をRubyで検証したら、よく理解できてよく解らなくなった話
モンティホール問題って何?
モンティホール問題とは、直感的に納得できない確率の問題です。
ネットで調べてもなんだかスッキリしなかったので自分で検証してみることにしました。こちらのサイトでも概要は分かりますが、少しこのページでも解説します
モンティホール問題の概要
箱を選んだ段階で、はずれの箱を1つ開けてくれます。
その後選んだ箱を変えることができるとき、選択肢を変える・変えないのどうするのがお得かという問題です。
箱1 箱2 箱3 当たり はずれ はずれ 例えば、上のように3つある箱の中から箱2を選んだ状態で(まだ開けてはない)、ゲームの主催者がはずれの箱を開けたとします。
状況的には下のような感じ
箱1 箱2 箱3 当たり はずれ はずれ 選択中 OPEN 箱3がはずれと分かった時点で選択を変更できる場合に、変更すべきかどうか?という問題です。
直感的には
当たり or はずれ
の2択なのでどちらを選んでも変わらなそうですが、数学的には次のようになります
そのまま 選択肢を変更 当たりの確率 33% 67% これを最初見たとき「そんなバカな…」って思いました。
解説を見てもいまいち納得できなかったので、プログラミングを使って検証したらとても腑に落ちたので解説していきます。
(でも結局新たな謎が増えました)さっそくコーディングして検証
Rubyを使って検証していきます。
(もう少しリファクタはできますが、やってることが分かりやすいようにあえて冗長に書いてる部分もあります。それでも少し分かりにくいかも…笑)コード
ruby# 試行回数(100万回くらいやれば十分でしょ) number = 1_000_000 # 当たりとはずれを用意 win = 1 lose = 0 # 箱を用意 boxes = [win, lose, lose] # ケース1(選択肢を変更しない場合) count = 0 number.times do # ランダムでどの箱を選ぶか決定 random_number = [0, 1, 2].sample selected_box = boxes[random_number] # はずれの箱を開ける(次に選択肢を変更しないのでこの処理は特に意味がない) random_number != 2 ? boxes.delete_at(2) : boxes.delete_at(1) # 選んだ箱のままで当たりかどうか判定 count += 1 if selected_box == win end # 確率の計算 prob_1 = (count.to_f / number.to_f).round(5) * 100 puts "選択肢を変更しない場合の確率: #{prob_1}%" # ケース2(選択肢を変更する場合) count = 0 number.times do # 箱を選択 random_number = [0, 1, 2].sample selected_box = boxes[random_number] # はずれの箱を開けて選んだ箱を変更 if random_number == 0 boxes.delete_at(2) selected_box = boxes[1] elsif random_number == 1 boxes.delete_at(2) selected_box = boxes[0] else boxes.delete_at(1) selected_box = boxes[0] end # 判定 count += 1 if selected_box == win end # 確率の計算 prob_2 = (count.to_f / number.to_f).round(5) * 100 puts "選択肢を変更した場合の確率: #{prob_2}%"結果
ファイルを実行した結果です。
確かに直感が間違っていることがわかります
少し解説
この問題をRubyで解いてみて、そゆことかーと思ったことを解説していきます。
ポイントは最初に選んだ選択肢による場合分けです箱を選んだ時点ではずれの箱を一つ教えてくれるわけですから、この問題は次の2パターンに場合分けできます。
- 最初に当たりの箱を選んでいた場合
- 最初にはずれの箱を選んでいた場合
それぞれの確率は当然次のようになります
最初に当たりの箱を選ぶ確率 最初にはずれの箱を選ぶ確率 33% 67% では次にここから選択肢を変える・変えないの話をみていきます
選択肢を変えない場合
選択肢を変えない場合、最初に当たりの箱を選ばないといけません。
もともと当たりを引く確率が33%なので、この場合の当たりを引く確率は33%になります選択肢を変える場合
選択肢を変える場合、最初に当たりの箱を選んでいたらはずれ、はずれを選んでいたら当たりになります。
つまり、最終的に当たりを引くためには最初にはずれの箱を選択する必要があります。
最初にはずれを選ぶ確率は67%ですので、この場合の当たりを引く確率は67%になります少し疑問
ここまで解説してきましたが、書いてる途中で疑問も出てきました。
はずれの箱が開かれた段階で、別の参加者がやってきたとしましょう。
このとき、別の参加者から見たらどちらの箱が当たりかどうかは五分五分では?
この疑問を思いついたはいいが上手い説明ができないので、数学に詳しい方がいれば是非コメントしてください。
最後に
すこしモヤっとして終わってしまいましたが、面白い問題でした!
モンティホール問題は単純なのに奥が深いですね〜
- 投稿日:2020-10-10T16:18:35+09:00
アソシエーション(多対多)!!
アソシエーションって何??
簡単に説明するとモデルを利用したテーブル同士の関連付けのことですね。
テーブル同士で関連付けておき、一方のモデルからもう一方のモデルに
アクセスできるようにするためということですね。*今回は、「多対多」のアソシエーションの説明になります。
関連付けを行う理由
Railsでは、「関連付け(アソシエーション: association)」とは2つの
Active Recordモデル同士の繋がりのことですね。2つモデルの間には関連付けを行なう必要がありますが、
その理由を知っていますか?それはですね、関連付けを行う事でコードの共通操作をより
シンプルで簡単にできるからなんです。例:アソシエーション(多対多)
今回は、usersテーブルとroomsテーブルで説明したいと思います。
まずは、テーブルごとに、他テーブルとの関係性を考えましょう。
・ユーザーがどのチャットルームに属しているかを管理する
・チャットルームにどのユーザーが存在するかを管理する今回の下記の画像になります。
ここで注目すべきポイントは、usersテーブルとroomsテーブルの関係性が、
今回のDB設計では「多対多」という関係性が存在していますね。
「多対多」は、関連するテーブルのidをお互いが複数持っている関係性のことです。今回の場合は、「一人のユーザーは複数のチャットルームに所属する」
「一つのチャットルームには複数ユーザーが所属する」という場合の関係性ですね。しかしですね、このテーブルの関係性を示すために、外部キーへ複数の値を
保存できれば簡単なのですが、1つのカラムに対して複数の値を保存することはできません。よく考えられる方法としては、関連するidが増えるごとにカラムを増やすという
パターンがあります。
例としてわかりやすいように下記に画像を載せておきますね。画像を見てもらえるとわかるのですが、カラムを不安と
無駄なカラムが生じていますね。このようなDB設計は、関連するレコードが増えるほどカラムが増えるため、
良くない設計とされているものですね。これを解決するために使用するのが、中間テーブルです。
中間テーブル
では、中間テーブルって何って思われると思うので簡単に説明します。
中間テーブルとは、その名の通り2つのテーブルの中間にあるテーブルのことです。今回で言うと、usersテーブルとroomsテーブルの間に中間テーブルを作成します。
中間テーブルは、「多対多」の関係にある2つのテーブルの間に挟まって、2つの組み合わせパターンだけをレコードとして保存することですね。
また、2つのモデルのみでは「多対多」のアソシエーションを
組むことはできません。
そのため、中間テーブルを利用して「多対多」の関係を定義します。まだこれだけの説明ではイメージがあまりできてないかもしれないので、
次はSNSに例えて説明します。
次の説明からはテーブル名を変えていきますので、先ほどのテーブル名とは異なります。わかりにくかったら申し訳ないです。。タグ付き写真投稿アプリを例に中間テーブルの役割を確認しましょう
『Instagram』などの、一つの写真に複数のタグ付けができる写真投稿アプリをイメージしてみて下さい。
『Instagram』のようにタグ付け機能を実現するには3つのテーブルが
必要です。タグを保存するテーブル、写真を保存するテーブル、
そして「どの写真にどのタグが登録されているか」を保存するテーブルです。このうち写真やタグを保存するテーブルの名前はtagsテーブル、photosテーブルで良いでしょう。「どの写真にどのタグが ~」のテーブルは、photos_tagsテーブルとします。関係する2つのテーブルをアンダーバーで繋いだ名前です。
それぞれのテーブルに保存されているレコードの関係性は以下のようになります。
上記の画像の見てもらうとわかるのですが、中間テーブルには、「どの写真とタグが関連づいているか」という情報が記録されています。一つのレコードには「photo_id × tag_id」の組み合わせが記録され、すべての写真とタグの組み合わせの数だけ、レコードが蓄積されていきます。例えば、10個の写真にそれぞれ、異なるタグが3つずつ付いている場合、これらの関係性を表すための中間テーブルには30個のレコードが生成されることになります。
ここまでが、中間テーブルを用いて情報をDBに保存することの説明です。ここからは、中間テーブルを用いてアソシエーションを設定する方法を確認します。
中間テーブルを経由して「多対多」のテーブルへアソシエーションを組むには、これまで使用してきたhas_manyメソッドに、throughオプションを記述する必要があります。
throughオプションの説明を次にさせて頂きます。throughオプション
has_manyメソッドのthroughオプションは、モデルに多対多の関連を定義するときに利用します。
throughという名前のとおり、「〜を経由する」という意味です。
もっとわかりやすく参考画像を載せておきます。throughオプションを使用し、多対多のアソシエーションを定義する場合は、それぞれのモデルに以下のような記述をします。
models/photo.rbclass Photo < ApplicationRecord has_many :photos_tags has_many :tags, through: :photos_tags endmodels/tag.rbclass Tag < ApplicationRecord has_many :photos_tags has_many :photos, through: :photos_tags endmodels/photos_tag.rbclass PhotosTag < ApplicationRecord belongs_to :photo belongs_to :tag end多対多の関係にある2つのテーブルのモデルでは、has_manyメソッドによる「1対多」のアソシエーションを互いに定義するのと合わせて、
throughオプションによって経由する中間テーブルを指定します。一方、中間テーブルのモデルでは、belongs_toメソッドで多対多の関係にある2つのテーブルを指定します。
以上が、「多対多」のアソシエーションを定義する方法です。
まとめ
・「多対多」のアソシエーションには中間テーブルを用いること
・「多対多」の関係にある2つのテーブルのモデルでは、has_manyメソッド
による「1対多」のアソシエーションを互いに定義するのと合わせて、
throughオプションを使用して経由する中間テーブルを指定すること今回の説明は、長くなってしまって説明がわかりにくくなっていたら、
すいません。。
私なりに頑張ってまとめたので参考になれば嬉しいです。以上。
- 投稿日:2020-10-10T15:21:48+09:00
部分一致検索機能の実装をRansuckを使用せず行う
部分一致検索機能の実装をRansuckを使用せず行ったのでまとめていく。
今回は名前、メールアドレスを入力するとそれに部分一致で該当するユーザーの情報を取得できるという簡単な機能を作る。例 名前フォームに「の」 メールアドレスフォームに「 」で検索 ↓ 野比 のび太 nobinobi@example.com が出力するみたいな感じ。にしたい。結論からということでまずはコード載せますね。
routes.rbRails.application.routes.draw do root 'users#index' resources :users endindex.html<h1>ドラえもんキャラを部分一致検索してみよう</h1> <%= form_with(url: 'users', local: true, method: :get) do |f| %> <span class="small">検索対象: 名前 メールアドレス</span> <div> <%= f.label :name, '名前' %> <%= f.text_field :name , value: @search_params[:name] %> </div> <div> <%= f.label :email, 'メールアドレス' %> <%= f.text_field :email, value: @search_params[:email] %> </div> <%= f.submit '検索', class: 'btn btn-default' %> <%end%> <h1>検索結果</h1> <% @users.each do |user| %> <%= user.name %> <%= user.email %> <% end %>users_controller.rbclass UsersController < ApplicationController def index @search_params = search_params @users = User.search(search_params) end private def search_params params.permit( :name, :email ) end enduser.rbclass User < ApplicationRecord scope :search, -> (search_params) do return if search_params.blank? name_like(search_params[:name] ) .email_like(search_params[:email]) end scope :name_like, -> (name){where("name LIKE ?" ,"%#{name}%")} scope :email_like, -> (email){where("email LIKE ?", "%#{email}%")} endschema.rbActiveRecord::Schema.define(version: 2020_10_10_011424) do create_table "users", force: :cascade do |t| t.string "name" t.string "email" t.datetime "created_at", null: false t.datetime "updated_at", null: false end endseeds.rbUser.create!(name: "野比 のび太",email: "nobinobi@example.com") User.create!(name: "剛田 武",email: "soulmate@example.com") User.create!(name: "骨川 スネ夫",email: "kanemoti@example.com") User.create!(name: "ドラえもん",email: "dora@example.com") User.create!(name: "ドラミちゃん",email: "dorami@example.com") User.create!(name: "源 しずか",email: "piano@example.com")一個一個説明していきます。
まずviewの説明からindex.html<h1>ドラえもんキャラを部分一致検索してみよう</h1> <%= form_with(url: 'users', local: true, method: :get) do |f| %> <span class="small">検索対象: 名前 メールアドレス</span> <div> <%= f.label :name, '名前' %> <%= f.text_field :name , value: @search_params[:name] %> </div> <div> <%= f.label :email, 'メールアドレス' %> <%= f.text_field :email, value: @search_params[:email] %> </div> <%= f.submit '検索', class: 'btn btn-default' %> <%end%> <h1>検索結果</h1> <% @users.each do |user| %> <%= user.name %> <%= user.email %> <% end %>まずここなのですが
<%= form_with(scope: :search ,url: 'users', local: true, method: :get) do |f| %>検索フォームはform_withを使用しています。form_tag と form_forとの違いは下記URLにわかりやすく説明されています。
https://qiita.com/hmmrjn/items/24f3b8eade206ace17e
(以前は関連するモデルがなかったときは form_tag 関連するモデルがある時は form_forを使用していましたがrails5.1で非推奨となっています。具体例として挙げるとUser情報を使いたいだけ(検索時)などはform_tag
User情報を登録したい→登録されるデータベースが存在している時はform_forを使用していた。)
rails5.1以降より推奨されているform_withはそこら辺の処理をうまい感じに切り分けて実行してくれます。またデフォルトで非同期通信のAjaxが実装されてます。今回はJSは使用しない実装なので、リモートフォームを無効にするため local: true と指定し、HTML形式でデータをmethod: get にて通信しています。続いてコントローラーを見ていきましょう。
users_controller.rbclass UsersController < ApplicationController def index @search_params = search_params @users = User.search(search_params) end private def search_params params.permit( :name, :email ) end endここでのみそは User.search(search_params) です。こちらのsearchメソッドなのですがUsermodelの方に部分一致検索機能をscopeしています。送られた値に対しsearchでnameとemailに部分一致処理をし返します。続いてmodelを参照してください。
user.rbclass User < ApplicationRecord scope :search, -> (search_params) do return if search_params.blank? name_like(search_params[:name] ) .email_like(search_params[:email]) end scope :name_like, -> (name){where("name LIKE ?" ,"%#{name}%")} scope :email_like, -> (email){where("email LIKE ?", "%#{email}%")} endsearchスコープはこちらに飛んで一つ一つ部分処理をした後コントローラに返されます。
return if search_params.blank?
を入れておく事で仮に何も値がはいっていなくても処理をそこで止めてコントローラに返します。
検索対象が今回のように:name :emailと複数である場合はparamsを繰り返し処理とし一つ一つ検索を返すようにします。今回は以上となります。
もしここは違うなどありましたどんどん指摘しください。
- 投稿日:2020-10-10T15:21:48+09:00
部分一致検索機能の実装をransuckを使用せず行う
部分一致検索機能の実装をransuckを使用せず行ったのでまとめていく。
今回は名前、メールアドレスを入力するとそれに部分一致で該当するユーザーの情報を取得できるという簡単な機能を作る。例 名前フォームに「の」 メールアドレスフォームに「 」で検索 ↓ 野比 のび太 nobinobi@example.com が出力するみたいな感じ。にしたい。結論からということでまずはコード載せますね。
routes.rbRails.application.routes.draw do root 'users#index' resources :users endindex.html<h1>ドラえもんキャラを部分一致検索してみよう</h1> <%= form_with(url: 'users', local: true, method: :get) do |f| %> <span class="small">検索対象: 名前 メールアドレス</span> <div> <%= f.label :name, '名前' %> <%= f.text_field :name , value: @search_params[:name] %> </div> <div> <%= f.label :email, 'メールアドレス' %> <%= f.text_field :email, value: @search_params[:email] %> </div> <%= f.submit '検索', class: 'btn btn-default' %> <%end%> <h1>検索結果</h1> <% @users.each do |user| %> <%= user.name %> <%= user.email %> <% end %>users_controller.rbclass UsersController < ApplicationController def index @search_params = search_params @users = User.search(search_params) end private def search_params params.permit( :name, :email ) end enduser.rbclass User < ApplicationRecord scope :search, -> (search_params) do return if search_params.blank? name_like(search_params[:name] ) .email_like(search_params[:email]) end scope :name_like, -> (name){where("name LIKE ?" ,"%#{name}%")} scope :email_like, -> (email){where("email LIKE ?", "%#{email}%")} endschema.rbActiveRecord::Schema.define(version: 2020_10_10_011424) do create_table "users", force: :cascade do |t| t.string "name" t.string "email" t.datetime "created_at", null: false t.datetime "updated_at", null: false end endseeds.rbUser.create!(name: "野比 のび太",email: "nobinobi@example.com") User.create!(name: "剛田 武",email: "soulmate@example.com") User.create!(name: "骨川 スネ夫",email: "kanemoti@example.com") User.create!(name: "ドラえもん",email: "dora@example.com") User.create!(name: "ドラミちゃん",email: "dorami@example.com") User.create!(name: "源 しずか",email: "piano@example.com")一個一個説明していきます。
まずviewの説明からindex.html<h1>ドラえもんキャラを部分一致検索してみよう</h1> <%= form_with(url: 'users', local: true, method: :get) do |f| %> <span class="small">検索対象: 名前 メールアドレス</span> <div> <%= f.label :name, '名前' %> <%= f.text_field :name , value: @search_params[:name] %> </div> <div> <%= f.label :email, 'メールアドレス' %> <%= f.text_field :email, value: @search_params[:email] %> </div> <%= f.submit '検索', class: 'btn btn-default' %> <%end%> <h1>検索結果</h1> <% @users.each do |user| %> <%= user.name %> <%= user.email %> <% end %>まずここなのですが
<%= form_with(scope: :search ,url: 'users', local: true, method: :get) do |f| %>検索フォームはform_withを使用しています。form_tag と form_forとの違いは下記URLにわかりやすく説明されています。
https://qiita.com/hmmrjn/items/24f3b8eade206ace17e
(以前は関連するモデルがなかったときは form_tag 関連するモデルがある時は form_forを使用していましたがrails5.1で非推奨となっています。具体例として挙げるとUser情報を使いたいだけ(検索時)などはform_tag
User情報を登録したい→登録されるデータベースが存在している時はform_forを使用していた。)
rails5.1以降より推奨されているform_withはそこら辺の処理をうまい感じに切り分けて実行してくれます。またデフォルトで非同期通信のAjaxが実装されてます。今回はJSは使用しない実装なので、リモートフォームを無効にするため local: true と指定し、HTML形式でデータをmethod: get にて通信しています。続いてコントローラーを見ていきましょう。
users_controller.rbclass UsersController < ApplicationController def index @search_params = search_params @users = User.search(search_params) end private def search_params params.permit( :name, :email ) end endここでのみそは User.search(search_params) です。こちらのsearchメソッドなのですがUsermodelの方に部分一致検索機能をscopeしています。送られた値に対しsearchでnameとemailに部分一致処理をし返します。続いてmodelを参照してください。
user.rbclass User < ApplicationRecord scope :search, -> (search_params) do return if search_params.blank? name_like(search_params[:name] ) .email_like(search_params[:email]) end scope :name_like, -> (name){where("name LIKE ?" ,"%#{name}%")} scope :email_like, -> (email){where("email LIKE ?", "%#{email}%")} endsearchスコープはこちらに飛んで一つ一つ部分処理をした後コントローラに返されます。
return if search_params.blank?
を入れておく事で仮に何も値がはいっていなくても処理をそこで止めてコントローラに返します。
検索対象が今回のように:name :emailと複数である場合はparamsを繰り返し処理とし一つ一つ検索を返すようにします。今回は以上となります。
もしここは違うなどありましたどんどん指摘しください。
- 投稿日:2020-10-10T14:54:59+09:00
Trailblazerでcreateを試してみる
はじめに
抽象レイヤーを提供してくれるGem,Trailblazerの存在を知り勉強がてらにCreateを試してみた。
Trailblazerの概要については他の記事でわかりやすく説明していただいてるので割愛とする。こちらの記事がとてもわかりやすかったです。
TrailBlazer概要まとめてみた
環境
ruby '2.7.2'
'rails', '~> 6.0.3'
template engine: hamlCreate
Trailblazerのディレクトリ構成
. └── todo ├── cell │ ├── index.rb │ └── new.rb ├── contract │ └── form.rb ├── operation │ ├── create.rb │ └── index.rb └── view ├── form.haml ├── index.haml └── new.hamlOperation
module Todo::Operation class Create < Trailblazer::Operation class Present < Trailblazer::Operation step Model(Todo, :new) step Contract::Build( constant: Todo::Contract::Form ) end step Subprocess(Present) # present classのstep呼び出し step Contract::Validate(key: :todo) # contractを使ってバリデーション step Contract::Persist() # モデルに保存する end endContract
module Todo::Contract class Form < Reform::Form include Reform::Form::ActiveModel property :title property :description property :completed validates :title, presence: true validates :description, length: { minimum: 2 } # overrideもできる # def title # super.capitalize # end end endCell
module Todo::Cell class New < Trailblazer::Cell include ActionView::RecordIdentifier include ActionView::Helpers::FormOptionsHelper include SimpleForm::ActionViewExtensions::FormHelper end endView
= simple_form_for(model, html: {novalidate: true}) do |f| = f.input :title, placeholder: "Title" %br = f.input :description, as: :text, placeholder: "Description" %br = f.submit 'Submit' = link_to 'Back', todos_path確認
今回試したプロジェクト
参考記事
- 投稿日:2020-10-10T14:14:08+09:00
【Rails】deviseのviewやcontrollerの編集とカスタマイズ方法
はじめに
deviseのviewとcontrollerを編集、カスタマイズする方法
deviseを導入した後、見た目が味気なく英語ばかりのviewを整えたい。
deviseのcontrollerに編集を加えてカスタマイズする必要がある。
そんな時の方法を簡単に紹介します。目次
1.前提
2.devise.rbの設定ファイルを変更
3.contorollerの生成・カスタマイズ
4.viewファイルの生成・編集
5.まとめ開発環境
ruby 2.6.5
rails 6.0.0
devise 4.7.31.前提
devise導入済み
model生成済み
ルーティングは随所で設定実装
それでは実装します。
2.devise.rbの設定ファイルを変更
config/initializers/devise.rbを開きます。config.scoped_viewsを有効にします。
config/initializers/devise.rb# ==> Scopes configuration # Turn scoped views on. Before rendering "sessions/new", it will first check for # "users/sessions/new". It's turned off by default because it's slower if you # are using only default views. config.scoped_views = true #←デフォルトはfalse#Railsサーバを再起動します。
「gemを入れた時」などと同様で再起動が必要です。これを怠るとソースが反映されず「エラーは起きずないが、思い通りにならないという」原因を突き止めるのが難しい状況に陥ります。3.controllerファイルの生成・編集
deviseのcontrollerを生成します。
$ rails g devise:controllers usersこの時の公文は
rails g devise:controllers モデル名
です。
今回はdeviseモデルをuserにしているので(大体userにしますが)その名前を使います。
app > controllers > users >
の下にファイルができます。
例えば、ユーザー登録関連のcontrollerをカスタマイズしたければ、regisrations_controller.rbファイルを編集します。ここで注意が必要なのが、ルーティングの設定です。
ルーティングで記述している[コントローラー名]#[アクション名]
と実際の
[コントローラー]#[アクション]が違うと、当たり前ですがうまく行きません。
なので、rails routes 等でしっかり確認する事をおすすめします。4.viewファイルの生成・編集
deviseのviewファイルを生成・編集します。
$ rails g devise:viewsapp > views > devise >
devise直下にフォルダができます。例えば登録画面を編集したければ、regisrationsのディレクトリにあるファイルを編集します。
ここで注意するべきなのは、viewのファイル名です。
deviseではrenderやredirect_toで遷移先を指定しなければ、コントローラーの処理後メソッド名に遷移する様になってます。
なので、それぞれの名前を合わせる必要があります。5.まとめ
contorollerの生成・カスタマイズとviewファイルの生成・編集を紹介しました。
deviseのcontrollerを使うと、デフォルトで持ってる機能を自動で使えると言うメリットがあります。
しかし、その反面に間違った記述をしてても、裏でdeviseが動いてくれるので、思い通りの挙動にならずエラーも出ないという状況にも陥ります。
その為にも、ルーティング、コントローラー、ビューがどうの様に辿ってきてるか注意する事をおすすめします。最後に
私はプログラミング初学者ですが、自分と同じ様にエンジニアを目指す方々の助けになればと思い、記事を投稿しております。
それではまた次回お会いしましょう〜
- 投稿日:2020-10-10T14:11:08+09:00
「もっとプログラム脳を鍛える数学パズル」_Q41 (code:Ruby) -> Rust
「もっとプログラマ脳を鍛える数学パズル」をRustで書き直すのは、ボケ防止にちょうど良いかもしれない、と思った。
Q41:スタートメニューのタイル
こういう問題、どういう発想で考えてるんだろう。図形を対象とする問題は楽しい。
Ruby
q41_2.rbW, H = 10, 10 @memo = {} def search(tile) return @memo[tile] if @memo[tile] return 1 if tile.min == H pos = tile.index(tile.min) cnt = 0 [[1,1],[2,2],[4,2], [4,4]].each do |px,py| check = true px.times do |x| if (pos + x >= W) || (tile[pos + x] + py > H) check = false elsif tile[pos + x] != tile[pos] check = false end end next if !check px.times do |x| tile[pos + x] += py end cnt += search(tile) px.times do |x| tile[pos + x] -= py end end @memo[tile.clone] = cnt end puts search([0] * W)いやさすがに圧縮しすぎな気がする。アルゴリズムの図解が書籍中には無いので、変数名の適当さとデータ構造がむき出しであることが相まって理解が難しい。どういう判断によって、次の状態がどうなっていくのか、を図解しないと、再帰的探索のイメージがつかない。私だけかもしれないが。
Rust
main.rsuse std::collections::HashMap; fn main() { let space_width = 10; let space_height = 10; let mut q41 = Q41 { memo: HashMap::new(), }; let space = Space::new(space_width,space_height); println!( "{}", q41.patterns(&space, &vec![(1, 1), (2, 2), (4, 2), (4, 4)]) ); } struct Q41 { memo: HashMap<Space, u64>, } impl Q41 { pub fn patterns(&mut self, space: &Space, tiles: &Vec<(u64, u64)>) -> u64 { match self.memo.get(space) { Some(v) => *v, _ => { if space.is_filled() { return 1; } let mut count = 0; for t in tiles { if space.can_be_placed(t) { let new_space = space.place(t); count += self.patterns(&new_space.unwrap(), tiles); } } self.memo.insert(space.clone(), count); return count; } } } } #[derive(Eq, Hash)] struct Space { rows: Vec<u64>, width: usize, } impl PartialEq for Space { fn eq(&self, other: &Self) -> bool { self.rows == other.rows } } impl Space { pub fn new(width:u64, height:u64) -> Space { Space { rows: vec![0u64; height as usize], width: width as usize, } } pub fn height(&self) -> usize { self.rows.len() } pub fn min_value(&self) -> u64 { let mut min = std::u64::MAX; for i in 0..self.height() { if self.rows[i] < min { min = self.rows[i]; } } min } pub fn index_of_min_value(&self) -> usize { let min_value: u64 = self.min_value(); self.rows.iter().position(|&v| v == min_value).unwrap() } pub fn is_filled(&self) -> bool { for i in 0..self.height() { if self.rows[i] != self.width as u64 { return false; } } true } pub fn place(&self, tile: &(u64, u64)) -> Option<Space> { if !self.can_be_placed(tile) { return None; } let mut new_space: Space = self.clone(); let py = self.index_of_min_value(); for y in py..py + tile.1 as usize { new_space.rows[y] += tile.0; } return Some(new_space); } pub fn can_be_placed(&self, tile: &(u64, u64)) -> bool { let index_of_min = self.index_of_min_value(); let (tw, th) = tile; if index_of_min + *th as usize > self.height() { // height over! return false; } for dh in 0..*th { let ih = index_of_min + dh as usize; if self.rows[ih] + tw > self.width as u64 || self.rows[ih] != self.rows[index_of_min] { return false; } } return true; } pub fn clone(&self) -> Space { Space { rows: self.rows.clone(), width: self.width, } } #[warn(dead_code)] pub fn to_string(&self) -> String { let mut str = String::new(); for i in 0..self.height() { str.push_str(&self.rows[i].to_string()); str.push_str(", "); } format!("rows=[{}],width={}",str, self.width) } }タイルを埋め込む「空間に直結したメソッド」があるので、そのメソッドを
Space
構造体側に整理した。これだけでも、本体の処理patterns()
は分かりやすくなったのではないか。このくらいの規模になってくると、設計の勉強としてちょうど良い。
- 投稿日:2020-10-10T13:50:08+09:00
【買付代行サービス個人開発 - No.010】会社の切替画面、会社の詳細画面、会社切替後は注文画面に行くようにする
概要
会社の切替画面、会社の詳細画面、会社切替後は注文画面に行くようにする
Figmaに近づけるToDoリスト
- 会社の切替画面修正
- 注文画面修正
- 会社の詳細画面修正
ToDoリスト詳細
ロゴ追加
app/assets/images/svg/sample.svg
追加会社の切替画面修正
app/views/orgs/index.html.slim= render(::Layout::NavbarComponent.new(org: @org, tab: false)) .py-10.px-8 header .max-w-7xl.mx-auto.px-4.sm:px-6.lg:px-8 h1.text-3xl.font-bold.leading-tight.text-gray-900 | 所属会社一覧 main .max-w-7xl.mx-auto.sm:px-6.lg:px-8 .px-4.py-8.sm:px-0 .border-4.border-dashed.border-gray-200.rounded-lg.h-96 .bg-white.shadow.sm:rounded-md.my-16.m-auto(class="w-3/4") ul - @orgs.each do |org| / TODO:Sassで場合分けできるようにする - border_t = org == @orgs.first ? '' : 'border-t border-gray-200' li(class=border_t) = link_to orders_ordering_org_sides_path, class: 'block hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out' do .flex.items-baseline.px-4.py-4.sm:px-6 .min-w-0.flex-1.flex.items-baseline .min-w-0.flex-1.px-4.md:grid.md:grid-cols-3.md:gap-4 div.align-middle .text-xl.font-medium.text-indigo-600.truncate.leading-10 = org.name div .text-xs.text-gray-500 | 会社タイプ .text-lg.leading-5.font-medium.text-gray-900.truncate / TODO: ENUM化する | 買付会社 div .text-xs.text-gray-500 | オーナー .text-lg.leading-5.font-medium.text-gray-900.truncate / TODO: Userモデルの紐付けをしたら、呼び出すようにすする | 山田 太郎 div i.fas.fa-sign-in-alt.fa-lg.text-gray-600
- 注文画面修正
app/views/orders/ordering_org_sides/index.html.slim.bg-gray-100 = render(::Layout::NavbarComponent.new(org: @org, tab: true, active: :order_ordering_org_sides)) / ...略
- 会社の詳細画面修正
app/views/orgs/show.html.slim= render(::Layout::NavbarComponent.new(org: @org, tab: true)) .bg-white.shadow.overflow-hidden.m-auto.mt-16.sm:rounded-lg(class="w-1/2") .px-4.py-5.border-b.border-gray-200.sm:px-6 h3.text-lg.leading-6.font-medium.text-gray-900 | 会社詳細 p.mt-1.max-w-2xl.text-sm.leading-5.text-gray-500 | 会社の詳細を説明します .px-4.py-5.sm:p-0 dl .sm:grid.sm:grid-cols-3.sm:gap-4.sm:px-6.sm:py-5 dt.text-sm.leading-5.font-medium.text-gray-500 | 会社名 dd.mt-1.text-sm.leading-5.text-gray-900.sm:mt-0.sm:col-span-2 = @org.name .mt-8.sm:mt-0.sm:grid.sm:grid-cols-3.sm:gap-4.sm:border-t.sm:border-gray-200.sm:px-6.sm:py-5 dt.text-sm.leading-5.font-medium.text-gray-500 | 会社タイプ dd.mt-1.text-sm.leading-5.text-gray-900.sm:mt-0.sm:col-span-2 / TODO:enum化する | 買付会社 .mt-8.sm:mt-0.sm:grid.sm:grid-cols-3.sm:gap-4.sm:border-t.sm:border-gray-200.sm:px-6.sm:py-5 dt.text-sm.leading-5.font-medium.text-gray-500 | オーナー dd.mt-1.text-sm.leading-5.text-gray-900.sm:mt-0.sm:col-span-2 | 山田太郎動作確認
準備
bin/rails db:migrate:reset bin/rails db:reset受入基準
- デザインがFigmaぽくなっている
- 画面遷移が下図のようになっている。会社の切替後は注文一覧画面に行くようになっている # ドロップダウンのJSは後日実装予定
- 投稿日:2020-10-10T12:10:16+09:00
【Rails】ActiveStorageを用いた画像複数枚投稿のエラー
エラー【undefined method `to_model'】
フリマアプリの開発中、以下のようなエラーが出ました。
Can't resolve image into URL: undefined method
to_model' for #<ActiveStorage::Attached::Many:0x00007fb7ffa59fb0>
Did you mean? to_yaml現状
- ActiveStorageというGemをを用いて画像保存を可能にしている。
- 1つの商品につき複数の画像の投稿を可能にした。←今ここ
エラーの内容
to_model
というメソッドは定義されていないよーって言われてます。以下のように、保存した画像を表示させたい時にエラーがでました。
問題があったコード
<%= image_tag @item.images, class: 'buy-item-img' %>解決したコード
<%= image_tag @item.images[0], class: 'buy-item-img' %>一つの商品に複数枚画像があるため、どの画像を表示させるかを記述する必要があります。
そうじゃなかったらどの画像を表示するのか判断できないですもんね?他の方の記事などを見てみると、
@item.images.url
と記述すれば解決することもあったそうです。
参考になれば、と思います!
- 投稿日:2020-10-10T10:47:31+09:00
Ruby と Python で解く AtCoder ABC178 D 動的計画法
はじめに
AtCoder Problems の Recommendation を利用して、過去の問題を解いています。
AtCoder さん、AtCoder Problems さん、ありがとうございます。今回のお題
AtCoder Beginner Contest D - Redistribution
Difficulty: 830今回のテーマ、動的計画法
いわゆる典型的な動的計画法の問題です。
前の記事 - Qiita で取り上げました、AtCoder Beginner Contest E - Crested Ibis vs Monster では、目標値を超えてた場合もケアしないといけませんが、今回はジャストでよいので、その分少し簡単です。Ruby
ruby.rbs = gets.to_i dp = Array.new(s.next, 0) dp[0] = 1 s.times do |i| next if dp[i].zero? 3.upto(s) do |x| if i + x <= s dp[i + x] += dp[i] dp[i + x] %= 1000000007 else break end end end puts dp[s]ruby.rbif i + x <= s dp[i + x] += dp[i] dp[i + x] %= 1000000007 else break end今回はジャストでよいので、
i + x <= s
についてのみdpを加算します。Python
python.pyfrom sys import stdin def main(): input = stdin.readline s = int(input()) dp = [0] * (s + 1) dp[0] = 1 for i in range(s): if dp[i] == 0: continue for x in range(3, s + 1): if i + x <= s: dp[i + x] += dp[i] dp[i + x] %= 1000000007 else: break print(dp[s]) main()PyPy は凄く速いですね。
Ruby Python PyPy コード長 (Byte) 244 405 405 実行時間 (ms) 284 509 70 メモリ (KB) 14400 9060 64596 まとめ
- ABC 178 D を解いた
- Ruby に詳しくなった
- Python に詳しくなった
- 投稿日:2020-10-10T09:30:10+09:00
Railsで本番環境(EC2, AmazonLinux)でPDF出力ができない
本番環境(EC2, AmazonLinux)でPDF出力ができない
wicked_pdf
とwkhtmltopdf-binary
というGemを使ってPDF出力機能を付けていますが、開発環境ではうまく行くのに本番環境では下記のエラーが発生。
Railsのバージョンは4.2です。RuntimeError (Failed to execute: ["/var/www/~~~/shared/bundle/ruby/2.4.0/gems/wkhtmltopdf-binary-0.12.6.3/bin/wkhtmltopdf", "--encoding", "UTF-8", "--page-size", "A4", "file:////tmp/wicked_pdf20201007-11835-gppxfs.html", "/tmp/wicked_pdf_generated_file20201007-11835-j83wu8.pdf"] Error: PDF could not be generated! Command Error: /var/www/~~~/shared/bundle/ruby/2.4.0/gems/wkhtmltopdf-binary-0.12.6.3/bin/wkhtmltopdf_centos_7_amd64: error while loading shared libraries: libpng15.so.15: cannot open shared object file: No such file or directory ):
libpng15.so.15
というライブラリがない様ですが、同様のエラーをネットで調べて色々と試しましたが解決しませんでした。Gemを変更し解決
2016年の記事ですが、下記が参考になりました。
https://qiita.com/s-mori/items/00aef46e6a10499f8254
https://qiita.com/yaboojp/items/526c9397070ca5d05256
wkhtmltopdf-binary
はAmazonLinuxに対応していない様で、
AmazonLinuxに対応しているwkhtmltopdf-binary-aml
を使うことでうまくいきました。修正前のGemfile
gem 'wicked_pdf' gem 'wkhtmltopdf-binary-aml'修正後のGemfile
gem 'wicked_pdf' gem 'wkhtmltopdf-binary-aml', git: 'https://github.com/insphire/wkhtmltopdf-binary-aml'修正前の
wicked_pdf
config/initializers/wicked_pdf.rbWickedPdf.config = { :exe_path => "#{Gem.loaded_specs['wkhtmltopdf-binary'].full_gem_path}/bin/wkhtmltopdf" }修正後の
wicked_pdf
config/initializers/wicked_pdf.rbWickedPdf.config = { :exe_path => "#{Gem.loaded_specs['wkhtmltopdf-binary-aml'].full_gem_path}/bin/wkhtmltopdf" }Bundlerのバージョンが変更されない様にして、bundle installしました。
$ bundle _1.16.1_ install
日本語表示に対応させる
本番環境にデプロイすると、日本語表示がされていませんでした。
なのでIPAフォントを本番サーバでインストールします。cd /usr/share/fonts $ yum install -y ipa-gothic-fonts ipa-mincho-fontsフォントを変更したことでレイアウトが崩れてしまったので、CSSなどを整えて、領収書機能を完成させることができました。
開発環境でのエラー
AmazonLinux対応のGemに変更したことで、今度は開発環境でエラーが起こる様になってしまいました。
RuntimeError - PDF could not be generated! Command Error: /Users/~~~/vendor/bundle/ruby/2.4.0/bundler/gems/wkhtmltopdf-binary-aml-e5340ed88aa8/bin/wkhtmltopdf:15:in `exec': Bad CPU type in executable - /Users/~~~/vendor/bundle/ruby/2.4.0/bundler/gems/wkhtmltopdf-binary-aml-e5340ed88aa8/libexec/wkhtmltopdf-darwin-x86 (Errno::E086)開発環境でも本番環境でもうまくやるには、
wicked_pdf.rb
をif Rails.env.production?
などを使ってかき分けたり、
Gemfileを以下の様にして環境別に切り替えればできます。group :development do gem 'wkhtmltopdf-binary' endgem 'wkhtmltopdf-binary', group: :development
- 投稿日:2020-10-10T01:27:37+09:00
絵文字を日本語に変換する gem
Google は絵文字で検索できる……!
絵文字を日本語に訳してくれるライブラリ
日本語 -> 絵文字 は事例がありました。
日本語で絵文字入力するための IME 追加辞書を公開しましたですがその逆が見当たらなかったので作ってみました。
https://github.com/d-mato/emojaEmoja.translate("?食べたい") # => "赤リンゴ食べたい"翻訳結果を MeCab に投げれば形態素解析をしてテキストマイニングもできるし、 ElasticSearch に投げれば全文検索もできそうです。
日本語 -> 絵文字 にも一応対応
用途が思いつかないけど、日本語から絵文字のサジェストも可能です。
Emoja.search("猫") # => ["?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?"]絵文字辞書
こちらのリポジトリにある
emoji_ja.json
を活用させていただいております
https://github.com/yagays/emoji-ja
みなさまの
お遊びプロジェクトで使ってもらえたら幸いです!
- 投稿日:2020-10-10T00:33:58+09:00
Dockerと仮装サーバーとコンテナについて
Docker!仮想サーバー!コンテナ!について
Dockerとは
コンテナ化を用いてアプリケーションを開発・配置・実行するためのオープンソースソフトウェアのこと!
仮想サーバーとは
1台のサーバーで仮想的に複数のサーバーを稼働させる仕組みのこと!
コンテナとは
他のユーザーから隔離された実行環境のこと。仮想サーバーに比べて起動時間が短く、同じ性能のハードウェアであれば、より多くのコンテナを同時に動かすことができる!
デプロイの種類について
カナリアリリースとは
一部のユーザーにのみ新機能の公開を行い、新しいバージョンにバグなどがないかを検証する手法のこと。サービスに不具合があっても全体に影響しませんよ!というもの。
ブルーグリーンデプロイメントとは
仮想サーバーを用い、2つの本番環境を用意し、それぞれバージョンを設定することができるデプロイの運用方法のこと。2つ用意するからどちらかに不具合が生じた場合どちらかにリクエストの方向を変える事ができる為、その間に不具合が生じた方をロールバックする事ができる!
イミュータブルデプロイメントとは
常に変更を行わない環境を構築することで、デプロイ時に新しい環境に切り替える手法のこと。古い環境を消去しますよというもの!
Docker公式 http://docs.docker.jp/
まとめ
Dockerについて概要を知るkとができましたね。おまけでクラウドの種類もよく使われるので載せておきます。
おまけ
SaaS
「Software as a Service」の略で、「サース」または「サーズ」と読む。
クライアント側に導入せずに、サービスを提供しているサーバーに直接アクセスをしてサービスを利用する状況を指す!
(例)
Microsoft Office 365などのオフィスソフト
GmailなどのWebメール
Dropboxなどのオンラインストレージ
サイボウズなどのグループウェアPaaS
「Platform as a Service」の略で、「パース」と読む。
作成したアプリケーションなどを、ネットワーク上に公開するためのプラットフォームを提供するサービスのこと!
(例)
HerokuIaaS
「Infrastructure as a Service 」の略で、「イアース」や「アイアース」と読む。
サービスを利用するユーザーが、仮想化をしたCPUやメモリ、ストレージなどをインターネット経由で提供するサービスのこと!
(例)
Microsoft Azure
Google Compute Engine現場からは以上です!
- 投稿日:2020-10-10T00:23:55+09:00
[Ruby on rails] いいね機能の実装
はじめに
投稿アプリで他のユーザーがいいねを出来るようにいいね機能を実装。
usersテーブルとpostsテーブルとlikesテーブルがあるものとします。アソシエーション
まずは、各テーブルの関係性を考え、アソシエーションを定義します。
ユーザー(1):いいね(多)
投稿(1):いいね(多)
1人1投稿に1回のいいねまでにしたいのでバリデーションもかけます。like.rbclass Like < ApplicationRecord belongs_to :user belongs_to :post validates_uniqueness_of :post_id, scope: :user_id end投稿が削除されたされた場合いいねも削除。
post.rbhas_many :likes, dependent: :destroyuser.rbhas_many :likes, dependent: :destroy def already_liked?(post) self.likes.exists?(post_id: post.id) endコントローラー実装
likes_controller.rbclass LikesController < ApplicationController def create @like = current_user.likes.create(post_id: params[:post_id]) redirect_back(fallback_location: root_path) end def destroy @post = Post.find(params[:post_id]) @like = current_user.likes.find_by(post_id: @post.id) @like.destroy redirect_back(fallback_location: root_path) end endルーティングの設定
routes.rbresources :posts do resource :likes, only: [:create, :destroy] end
post_likes
DELETE /posts/:post_id/likes(.:format) likes#destroy
POST /posts/:post_id/likes(.:format) likes#create
viewの実装
~.html.erb<% if current_user.already_liked?(post) %> <%= link_to post_likes_path(post), method: :delete do %> <i class="fas fa-heart"></i> <% end %> <% else %> <%= link_to post_likes_path(post), method: :post do %> <i class="far fa-heart"></i> <% end %> <% end %> <%= post.likes.count %> //いいねの数を表示すでにcurrent_userがいいねしているか?
trueであればいいねを解除
して、falseであればいいね
をする。最後に
いいねの実装方法は他にも非同期での実装などもあります。
まだまだ勉強中ですが、色々な技術を勉強し出来ることを増やしていきたいと思います。
最後まで読んでいただきありがとうございます
- 投稿日:2020-10-10T00:10:38+09:00
【Rails】Nginx, Puma な環境によるデプロイ&サーバーの勉強【AWS EC2】
はじめに
個人開発アプリを
WEBサーバー:Nginx
アプリケーションサーバー:Puma
を利用し、AWS EC2にデプロイしました。Ruby 2.5.3
Rails 5.2.4.2
MacOS:Catalina 10.15.6AWSの設定は既出の優れた記事通りで理解を進めながらOKだったのですが、駆け出したばかりである為、特にNginxの設定(nginx.conf等)が難しく感じ、幾つものエラーを乗り越え、試行錯誤してクリアしました。
最近はPumaをアプリケーションサーバーに選ぶ方が多いかと思います。
デプロイで必要になるnginx.conf,yourapp.confの設定2つを載せている記事がなかった為、誰かのためになるかと思い作成しました。
エラーに躓き、調べることでサーバーへの理解が深まったのでよしとしますが、恐らくチャレンジする皆さまも同じところで躓きそうなので、自分の場合はこのような設定でデプロイ出来ましたよ、という体で記事にしたいと思います。
1 NginxとPumaについて整理
自分自身、復習の意味を込めて両者について整理します(相違点があれば是非ご教示願います!)。
【Nginx】...WEBサーバー。ブラウザからのコンテンツリクエストを受け取り、ブラウザにレスポンスする。静的コンテンツ(画像など)のリクエストの場合、WEBサーバーが処理する。動的コンテンツはアプリケーションサーバーが担当する。WEBサーバーの代表はApache,Nginxなど
【Puma】...アプリケーションサーバー。NginxがWEBサーバーで静的コンテンツ処理に対し、こちらはNginxでは処理できない動的コンテンツを捌く。WEBサーバーからリクエスト→アプリケーションサーバー受け→Railsアプリケーションの処理結果を、WEBサーバーに返す。代表例としてUnicorn,Pumaなど。現在RailsはPumaをデフォルトのアプリケーションサーバーとして採用している。
PumaとUnicornの選択ですが前者はマルチスレッド、後者はマルチプロセスの違いがあるようです。Pumaの方がより多くのリクエストを効率的に捌ける利点があるようですが、通常使用ではさほど変わりはないという意見もあります。私はRailsで推奨されているPumaを選択しました。
2 puma.rbの設定
次にpuma.rbの設定です。以下が私の設定です。
puma.rb(local)# アプリケーションディレクトリ app_dir = File.expand_path("../../", __FILE__) # ソケット通信を図る為bindでURI指定 bind "unix://#{app_dir}/tmp/sockets/puma.sock" # PIDファイル所在(プロセスID) pidfile "#{app_dir}/tmp/pids/puma.pid" # stateファイルはpumactlコマンドでサーバーを操作する。その所在。 state_path "#{app_dir}/tmp/pids/puma.state" # 標準出力/標準エラーを出力するファイルの所在。 stdout_redirect "#{app_dir}/log/puma.stdout.log", "#{app_dir}/log/puma.stderr.log", true threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } threads threads_count, threads_count #port ENV.fetch("PORT") { 3000 } environment ENV.fetch("RAILS_ENV") { "development" } plugin :tmp_restartportは使用しない為、コメントアウトしました。
PumaとNginxはソケット通信させる為、2行目のbindで指定します。
bindでは下記のようにURIでパスを指定します。bind "unix://#{app_dir}/tmp/sockets/puma.sock"3 nginx.confの設定
Nginxは主に2つのファイルを変更します。
1 nginx.conf...Nginx自体の設定ファイル
2 yourapp.conf...アプリケーション毎のNginx設定ファイル下記が私のnginx.conf設定です
(EC2)etc/nginx/nginx.confuser nginx; worker_processes auto; error_log /var/log/nginx/error.log; pid /var/run/nginx.pid; include /usr/share/nginx/modules/*.conf; events { worker_connections 1024; } http { log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; include /etc/nginx/mime.types; default_type application/octet-stream; include /etc/nginx/conf.d/*.conf; index index.html index.htm; upstream puma { server unix:///var/www/rails/yourapp/shared/tmp/sockets/puma.sock; } server { listen 80 default_server; listen [::]:80 default_server; server_name Elastic IP(URL); root /var/www/rails/yourapp/current/public; location / { try_files $uri $uri/index.html $uri.html @webapp; } location @webapp { proxy_read_timeout 300; proxy_connect_timeout 300; proxy_redirect off; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_pass http://puma; } error_page 404 /404.html; location = /40x.html { } error_page 500 502 503 504 /50x.html; location = /50x.html { } } server { listen 443 ssl; server_name Elastic IP(URL); location @webapp { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_pass http://puma; } } }4 yourapp.confの設定
yourapp.confとしていますが、yourappにはあなたが作っているアプリケーション名が入ります。
(EC2)/etc/nginx/conf.d/yourapp.conf# log directory error_log /var/www/rails/yourapp/shared/log/nginx.error.log; access_log /var/www/rails/yourapp/shared/nginx.access.log; upstream app_server { # for UNIX domain socket setups server unix:/var/www/rails/yourapp/shared/tmp/sockets/yourapp-puma.sock fail_timeout=0; } server { listen 80; server_name ; # nginx so increasing this is generally safe... # path for static files root /var/www/rails/yourapp/current/public; # page cache loading try_files $uri/index.html $uri @app_server; location / { # HTTP headers proxy_pass http://app_server; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_redirect off; } # Rails error pages error_page 500 502 503 504 /500.html; location = /500.html { root /var/www/rails/yourapp/current/public; } client_max_body_size 4G; keepalive_timeout 5; }私がデプロイする際に参考にした記事を載せます。
Rails5+Puma+Nginxな環境をCapistrano3でEC2にデプロイする(後編)
Rails5アプリケーションのAWSによるネットワーク構築 Nginx+Puma+Capistranoな環境とAWS構築(VPC EC2 RDS CloudFlont Route53 etc)
インフラ初心者がNginx+PumaのRails5アプリケーションをCapistrano3でデプロイした話
所感
サーバーの扱いに慣れていなかったため、EC2へのデプロイに当たってAWS等の設定よりも、上記NginxとPumaの設定に苦慮した次第です。最終的にCapistrano、CircleCIを利用してAWS EC2にデプロイすることができました。
CDについては先人たちの記事通りに進めていけば難なくできるかと思います。
エラーが多発している時は参りますが、新機能の実装やエラー解決できた時の嬉しさが勝るのでやめられません!