【Rails】多対多のアソシエーションに別名をつけたいあなたに
最近涼しくなってきましたねー…と思ったら次の日は暑かったりで体を壊しそうになります。(2週間前にすでに壊した)
今日は最近コードをレビューする機会があって、多対多のアソシエーションをうまいこと設定できていなくて指摘したかったものの、どうやって設定するか微妙に調べることになったのでもう忘れないようにブログにしておこうと思った次第。
想定
会員がブログを投稿できる機能があって、他の人が書いたブログをお気に入りできるみたいな機能を作る想定で
以下のような感じのテーブル構成をイメージ
とりあえずわかりやすいところから
各モデルのアソシエーションのとりあえずわかりやすいところから記載
# user.rb class User < ApplicationRecord has_many :blogs has_many :favorites end # blog.rb class Blog < ApplicationRecord belongs_to :user has_many :favorites has_many :users, through: :favorites end # favorite.rb class Favorite < ApplicationRecord belongs_to :user belongs_to :blog end
多対多の関連がある時はthrough
オプションをつけてあげると中間テーブルを経由して関連先のモデルを取得できるようになります。
through
をつけないと中間テーブルの先のレコードを取得するのが一手間必要になり微妙なので設定するのが吉です。
# blogからuserを取得する例 # through設定した場合 blog.users # through設定しない場合 blog.favorites.map { |favorite| favorite.user }
設定しない場合の取得方法はあくまで例ですが何かしら下処理が必要になってしまいます…
で、それならuserにもthroughを設定してblogを取得できるようにしたらいーじゃん!ってなりますよね。
ただ今回はblogとuserにはすでにアソシエーションが存在します。
こんな時は別名をつけてあげれば良いのです!
多対多で中間テーブルの先のモデルとアソシエーションする
こんな感じで実現できます。
# user.rb class User < ApplicationRecord has_many :blogs has_many :favorites has_many :favorite_blogs, through: :favorites, source: :blog end
source
オプションを設定することでuser.favorite_blogs
でuserがお気に入りしたブログを一気に取得することができます。
Railsは外部キーの名前などでアソシエーションの判断をしていますが、アソシエーションの名前を外部キーの名前から外れた名前にする場合はそれをオプションをつけて教えてあげる必要があります。
source
オプションがthroughする際にアクセスすべきモデルを教えるオプションとなります。
source: :blog
とすることでfavorite.rb
で設定しているbelongs_to :blog
を参照するようになり、favorite_blogs
というblog_id
に紐づくという判断ができない名前であってもblog.rbに辿り着けるようになるといった感じです!(説明わかりづらい説)
なのでこのようなアソシエーションを作成したいとなった場合はsource
オプションを活用しましょう!
ちなみに1対多で違った名前を付ける際の対処
たとえば何らかの理由でhas_many :blogs
をhas_many :write_blogs
にしなければいけなくなった際は
class User has_many :write_blogs, class_name: 'Blog', foreign_key: :user_id # 以下省略 end
みたいな感じで、このアソシエーションはどこのモデルとのアソシエーションなのか、どの外部キーを用いるのかをオプションで明示する必要があります。
最後に
NEW GAMEを見てるとみんな才能があって素敵に思う。
ねねっちを弊社にスカウトしたいのだけどどこで出会えるのかしら
Rubyのヒアドキュメントで改行文字が認識されなくて戸惑った話
できるだけ気づきがあったらブログを更新しようと努力したいきむらです。
今回の記事はめっちゃ初歩的かつ、なんならヒアドキュメント関係ない記事です…笑
概要
SlackBotに通知を投げさせるような処理を作っていて文面をヒアドキュメントで作成しようとしていました。
配列に値を格納してヒアドキュメント上でjoinを使って改行文字(\n
)で連結表示させようとしていた時のことです。
joinの引数にもたせた改行文字がうんともすんともいわないホラー的な現象がおきていました。
書いていたプログラムは下のような感じです。
str_ary = ['hoge', 'fuga', 'piyo'] <<~EOS 文字列一覧です #{str_ary.join('\n')} EOS
これで
文字列一覧です hoge fuga piyo
という結果を期待していたのですが実際に帰ってきた結果は以下
文字列一覧です hoge\nfuga\npiyo
これは無情ですね。悲しみしかありません。
ヒアドキュメント上だと改行文字が認識されないのかな?とか思ってしまって無駄に調べ物して時間を潰してしまいました。
原因
調べたと言うより途中で気づいてしまいました。真犯人に。
結果的に言うとヒアドキュメントは1mmも悪いことはしていませんでした…
何が原因だったのかと言うと
str_ary.join('\n')
こいつでした。セパレート文字の指定の仕方が悪かったのです。
解決法
犯人であるセパレート文字の指定の方法を
str_ary.join("\n")
シングルクォーテーション
からダブルクォーテーション
に変更しました。
すると、なんということでしょう、期待した結果が得られるではありませんか。
シングルクォーテーションだと式展開ができないって言うのは知っていたのですが特殊文字もエスケープされてしまって効かなくなるとは知りませんでした。
つまりヒアドキュメントは何も悪くない。文字列の指定の仕方が悪かったんだ。
めっちゃ細いし初歩的なことなんですが、僕みたいに迷える子羊となってしまう人が現れるかもしれないのでメモメモ。
さいごに
最近仕事をこなしてもこなしても増えるんだよなあ
自分、分裂できないかなあ
AWS CodeDeployを使ってGitHubにあげているリポジトリをデプロイする
AWS CodeDeployを使ってみたら思いの外、簡単に設定できてデプロイができたので覚書きです!
そういえばAWSには結構お世話になってるけど記事を書くのは初めてという事実…
CodeDeployとは
AWS CodeDeployは読んで字のごとくEC2インスタンスなどにコードをデプロイしてくれるサービスです。
デプロイ対象のEC2にエージェントをインストールする
CodeDeployを利用するためにはCodeDeployのエージェントをEC2インスタンスにインストールしておく必要があります。
エージェントのインストールにはaws-cliでS3からエージェントをダウンロードする必要があるのでEC2にS3のread権限のロールを設定してあげる必要があります。
EC2にロールを設定する
まずはIAMにアクセスします。
https://console.aws.amazon.com/iam
ロール>新しいロールの作成でロールの作成を行う。
作成するロールは以下
ロールタイプ | ポリシー |
---|---|
Amazon EC2 | AmazonS3ReadOnlyAccess |
作成したロールをデプロイ対象のEC2に設定します。
CodeDeployのエージェントをインストールする
EC2にロールを設定後、EC2にSSHログインをします。
以下コマンドを実行していきCodeDeployのエージェントをインストールします。
$ sudo yum install aws-cli $ aws s3 cp s3://aws-codedeploy-ap-northeast-1/latest/install . --region ap-northeast-1 $ chmod +x ./install $ sudo ./install auto
エージェントのインストールはこれで完了です!!
デプロイするアプリケーションにappspec.ymlを作成
CodeDeployでどのようにデプロイを実行するかを設定するファイルappspec.yml
をデプロイするアプリケーションのプロジェクトルートに配置します。
今回はAmazonLinuxを想定しています。
version: 0.0 os: linux files: - source: / destination: /var/www/application
上記の設定はアプリケーションのファイル全体をEC2の/var/wwwapplication
配下にデプロイするという設定を行っています。
設定内容は今回は省略しますが、以下の記事がわかりやすかったです。
appspec.ymlを作成したらコミットしてGitHubにプッシュしておきます。
(CodeDeployはGitHubのデフォルトブランチをデプロイするのでプッシュしたブランチがデフォルトブランチではない場合はマージしておきます。)
CodeDeployを使ってデプロイを実行する
いよいよCodeDeployを利用してデプロイしていきます。
CodeDeploy用のロールを作成する
実行する前にCodeDeployのアプリケーションを作成するためにはCodeDeployのロールが必要なので再びIAMのダッシュボードにアクセスします。
作成するロールは以下になります。
ロールタイプ | ポリシー |
---|---|
AWS CodeDeploy | AWSCodeDeployRole |
アプリケーションを作成する
AWSのダッシュボードからCodeDeployにアクセスします。
初めてCodeDeployを利用する際は以下のような画面になるので今すぐ始める
をクリックする。
チュートリアルをやるか問われますが実施しなくても良いのでカスタムデプロイ
を選択してウォークスルーのスキップ
をクリックする。
ウォークスルーをスキップするとアプリケーションの作成画面に遷移するので、以下の設定を行う。
項目 | 設定内容 |
---|---|
アプリケーション名 | 任意 |
デプロイグループ名 | 任意 |
デプロイタイプ | インプレースデプロイ |
環境設定 | Amazon EC2インスタンス > デプロイしたいインスタンス |
デプロイ設定 | CodeDeployDefault.OneAtATime |
サービスロール | CodeDeploy用に作成したロール |
デプロイを実行する
CodeDeployダッシュボードから先ほど作成したアプリケーションのリンクにアクセスします。
デプロイグループを選択して、新しいリビジョンのデプロイ
をクリックします。
画面遷移したら以下の設定を行います。
項目 | 設定内容 |
---|---|
リポジトリタイプ | アプリケーションはGitHubに格納されています |
デプロイメントの説明 | 任意 |
GitHubアカウント | GitHubの認証を行います |
リポジトリ名 | GitHubのリポジトリ名(アカウント名/リポジトリ名の形式で) |
コミットID | デプロイしたいコミットID(省略形のコミットIDではなくフルの方で) |
その他の設定は任意で行いデプロイ
ボタンをクリックします。
するとデプロイが開始され指定したEC2へデプロイが実行されます!
最後に
設定する内容はちょいちょいありますが設定さえ行ってしまえば簡単にデプロイを実行することができるようになります!
さらにGitHubの設定をもう少し行うことでデフォルトブランチに変更が加わったのを検知して自動でデプロイを起動するといったことも可能です!(次の記事はそれかこうかな…笑)
いやー、AWSは本当に便利。設定するだけでなんでもできちゃうんじゃないかな。(ただし財力が必要)
ロードバランサーを経由してPOST投げたりするとdue to access control checkと言われて困り果てた時の対処
コンスタントに記事を更新と言ってからはや2ヶ月くらいでしょうか。。。笑
備忘録がてら出会ったことがないエラーにあったので久々に更新
概要
最近、ConoHaでアプリケーションサーバー2台の冗長環境を作ってくれないかと言われ割とお安めな価格で対処していた時のことです。
クライアントが新環境でアプリケーションの動作確認を行ってもらっていたところ
「Safariでログインとかサインアップできないんだけど調査おなしゃす!!」
というあまりやりたくない系のお願いをされたのがことの発端。
それで実際にSafariで動作確認してみると確かにログイン系の処理ができない。
超勘弁。ブラウザの要素検証したらdue to access control check
という文字が。
調べて見たらCORSの対策が打ててない時にでるっぽかった。
構成
インフラ
ConoHaロードバランサー
ConoHaVPS(アプリケーションサーバー) × 2
ConoHaVPS(DBサーバー) × 1
アプリケーション
Ruby on Rails 4.2.3
Ruby 2.3.0
MariaDB
解決策
Nginxの設定でCORSに関するヘッダー情報をリクエストに追加する設定を追記
location / { # ここから add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods "POST, GET, OPTIONS"; add_header Access-Control-Allow-Headers "Origin, Authorization, Accept"; add_header Access-Control-Allow-Credentials true; # この辺まで追記 proxy_set_header X-Real-IP $remote_addr; proxy_set_header Client-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; proxy_set_header Host $http_host; proxy_redirect off; proxy_pass http://oceanworks-puma; }
これでログインできるようになった。
その他
最近、疲れが取れない。。。
Rails 5.1のform_withを使ってうまくredirectできないあなたへ
かなり気まぐれですがネタができたらコンスタントにブログを投稿しようという気持ちになり始めました。
今年一年で50投稿を目指して頑張ります。
概要
Rails5.1 + sorceryを使って認証周りをサクッと作ろうとしたのですが、5.1から出てきたform_with
に微妙にはめられました。
結構初歩的っぽいですが僕と同じような気持ちになる人がいるかもしれないので備忘録。
というかRailsの4.x系とか5.0系やってきた人が試しに5.1使ってみよってなったら割とハマるんじゃないかと勝手に思ったり…
※ ブログの内容的にはsorceryはもはや関与しません
事象
以下みたいな感じでログインフォームを作ってました。
<%= form_with url: sign_in_path, method: :post do %> <div class="field"> <%= label_tag :email %><br /> <%= text_field_tag :email %> </div> <div class="field"> <%= label_tag :password %><br /> <%= password_field_tag :password %> </div> <div class="actions"> <%= submit_tag 'Login' %> </div> <% end %>
これでログインできるか確認するためにLoginボタンを押すと
ログイン成功してるはずなのに画面遷移しない・・・・!!!
いやいやと思ってLoginボタンを連打してみるも状況は変わらず。
しかし画面再読み込みしたらログインできてる。
ログ見たら
Started POST "/sign_in" for 127.0.0.1 at 2017-07-08 23:03:12 +0900 Processing by UserSessionsController#create as JS
なんかJSをレンダリングしようとしてますやん!
そりゃ画面遷移しないわけだ。
解決策
ちゃんとform_with
の仕様を把握してから使えばよかったのですが、解決策としてはなんてことない感じでした。
解決したコードがこちら
<%= form_with url: sign_in_path, method: :post, local: true do %> <div class="field"> <%= label_tag :email %><br /> <%= text_field_tag :email %> </div> <div class="field"> <%= label_tag :password %><br /> <%= password_field_tag :password %> </div> <div class="actions"> <%= submit_tag 'Login' %> </div> <% end %>
ほぼ変わってない感じですが、form_with
にlocal: true
オプションをつけてあげてます!
これでHTMLをレンダリングしてくれるので意図した動きになります!
どうもform_with
はデフォルトで生成されるformタグにdata-remote
がついちゃうっぽいです。
local: true
を設定することでこれをつかないようにできるとのこと。
よくよく考えたらscaffoldで生成された_form.html.erb
みるとform_with
にlocal: true
がついてんだよなあ
まあしかしこれで大人の階段をまた一つ登ることができました
余談
scaffoldで生成されるerbファイルのform_with
のブロック変数がform
になってるんですね!
いままではf
がブロック変数でしたが、やはり可読性を考えると一文字よりも意味のある単語の方がよいんですかねえ
CircleCIでelasticsearchにkuromojiをインストールして起動させる
CircleCIで自動テストをしたいもののelasticsearchの処理を含んだ処理周りがうまくテストされなくて結構ハマったので備忘録。
やりたいこと
アプリケーションの機能として簡単な全文検索をelasticsearchを使って実現していたので、CircleCIにもelasticsearchを導入して全文検索用のindexを作成をアプリケーションのテストコードで行なっていた。
CircleCIを使ってGitHubでプルリクエストを上げた瞬間に自動でテストが走らせるCI環境を構築して、全テストをパスさせたい!
問題点
しかし、elasticsearchを導入してpluginとしてkuromojiを導入する必要があり、それを実現するのに四苦八苦。
elasticsearchを動かすだけならできるのだが、kuromojiを導入する方法がわからない…
ひとまずここみたら全てが解決しました。
解決法
circle.yml
のdependencies
に以下を追記してあげるだけでOK
dependencies: post: - wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.3.2.tar.gz - tar -xvf elasticsearch-5.3.2.tar.gz - elasticsearch-5.3.2/bin/elasticsearch-plugin install analysis-kuromoji - elasticsearch-5.3.2/bin/elasticsearch: {background: true} - sleep 10 && wget --waitretry=5 --retry-connrefused -v http://127.0.0.1:9200/
これ書いておくとelasticsearchをwgetでダウンロードして、解凍して、kuromojiインストールして、elasticsearchを起動してくれる。
毎回wget走るの重くなりそうでいややなーとなる場合は以下みたいな感じに書き換える。
dependencies: cache_directories: - elasticsearch-5.3.2 post: - if [[ ! -e elasticsearch-5.3.2 ]]; then wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.3.2.tar.gz && tar -xvf elasticsearch-5.3.2.tar.gz && elasticsearch-5.3.2/bin/elasticsearch-plugin install analysis-kuromoji; fi - elasticsearch-5.3.2/bin/elasticsearch: {background: true} - sleep 10 && wget --waitretry=5 --retry-connrefused -v http://127.0.0.1:9200/
cache_directories
を使うとディレクトリをキャッシュできるようなので、wgetして解凍したディレクトリをキャッシュしてくれるようになる。
! -e elasticsearch-5.3.2
のところでディレクトリの存在確認をしてくれるのでキャッシュされてなかったらwget、解凍、インストールしてくれるし、キャッシュされてたらこの処理を飛ばしてくれるようになるので無駄なオーバーヘッドなくなるぽい。
これでelasticsearchをCircleCI上でも使えるようになってめでたしめでたし。
ちなみに
machine: services: - elasticsearch
最初、これでelasticsearch立ち上がるやん!と喜んでいたのですが、この記述をしているとwgetで落としてきたelasticsearchとは別物の子が動いちゃうので、いくらkuromojiをインストールしてもpluginがインストールされていないことになってテストが全く通らなくて禿げ上がりそうでした。
この記述に気づかず1時間ぐらい消費したのは愚かでした…
CentOS7系でhttpアクセスを許可する
毎度ConoHaでサーバー構築する際に忘れてしまうので備忘録
操作
httpを許可する
$ firewall-cmd --add-service=http --zone=public --permanent
httpsを許可する
$ firewall-cmd --add-service=https --zone=public --permanent
ファイアウォールをリロード
$ firewall-cmd --reload
※ファイアウォールが正しく設定されているか以下のコマンドで確認
$ firewall-cmd --list-all public (active) target: default icmp-block-inversion: no interfaces: eth0 sources: services: dhcpv6-client http https ssh ports: protocols: masquerade: no forward-ports: sourceports: icmp-blocks: rich rules:
serviceのあたりにhttpとhttpsが追加されていればOK
CentOS7で毎度環境構築するたびに忘れてて、検索するのがだるいのでブログ化
あとserviceコマンドに慣れすぎててsystemctlもよくど忘れする
あとmysqlいれたつもりになってるとmariadbが入ってて焦るときがある(ほぼmysqlと一緒だけど…)