20200702のRailsに関する記事は9件です。

なんとActiveRecordはDB跨ぎでの関連付けが可能っぽい

業務中に知って驚いたこと。休日に気が向いたら深堀するかも。

前提

  • User has_many Posts through UserPostsという関連付けをしたい。
  • Userテーブルは、PostsテーブルとUserPostsテーブルとは異なるDBのテーブル。

予想

User.rb
has_many :posts
has_many :read_posts, source: :posts, through: :user_posts

なんてasociationをモデル層に書いても流石にUser.read_postsは取ってこられないだろうと鼻を括っていた。

しかしだ。。。

  • ActiveRecordがpreloadを使っているのなら、関連付け可能で取ってこれる
  • ActiveRecordがeagerloadを使っているのなら、関連付け不可能(JOINを使っているから)

詳しいことは時間があったら調べてくれ

参考

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

Ruby on Rails チュートリアル(第4版) 第3章

3.1 演習

1.BitbucketがMarkdown記法のREADME (リスト 3.3) をHTMLとして正しく描画しているか、確認してみてください。
 省略
2.本番環境 (Heroku) のルートURLにアクセスして、デプロイが成功したかどうか確かめてみてください。
 省略

3.2.1 演習

1.Fooというコントローラを生成し、その中にbarとbazアクションを追加してみてください。

$ rails generate controller Foo bar baz

2.コラム 3.1で紹介したテクニックを駆使して、Fooコントローラとそれに関連するアクションを削除してみてください。

$ rails destroy controller Foo bar baz

3.4.2 演習

1.StaticPagesコントローラのテスト (リスト 3.24) には、いくつか繰り返しがあったことにお気づきでしょうか? 特に「Ruby on Rails Tutorial Sample App」という基本タイトルは、各テストで毎回同じ内容を書いてしまっています。そこで、setupという特別なメソッド (各テストが実行される直前で実行されるメソッド) を使って、この問題を解決したいと思います。まずは、リスト 3.30のテストが green になることを確認してみてください (リスト 3.30では、2.2.2で少し触れたインスタンス変数や文字列の式展開というテクニックを使っています。それぞれ4.4.5と4.2.2で詳しく解説するので、今はわからなくても問題ありません)。
 GREENになった

3.4.3 演習

1.サンプルアプリケーションにContact (問い合わせ先) ページを作成してください16 (ヒント: まずはリスト 3.15を参考にして、/static_pages/contactというURLのページに「Contact | Ruby on Rails Tutorial Sample App」というタイトルが存在するかどうかを確認するテストを最初に作成しましょう。次に、3.3.3でAboutページを作ったときのと同じように、Contactページにもリスト 3.40のコンテンツを表示してみましょう。)。

/test/controllers/static_pages_controller_test.rb
  test "should get contact" do
    get static_pages_contact_url
    assert_response :success
    assert_select "title", "Contact | #{@base_title}"
  end
/config/routes.rb
  get 'static_pages/contact'
/app/controllers/static_pages_controller.rb
  def contact
  end
/app/views/static_pages/contact.html.erb
  <% provide(:title, "Contact") %>
  <h1>Contact</h1>
  <p>
   Contact the Ruby on Rails Tutorial about the sample app at the
   <a href="https://railstutorial.jp/contact">contact page</a>.
  </p>

3.4.4 演習

1.リスト 3.41にrootルーティングを追加したことで、root_urlというRailsヘルパーが使えるようになりました (以前、static_pages_home_urlが使えるようになったときと同じです)。リスト 3.42のFILL_INと記された部分を置き換えて、rootルーティングのテストを書いてみてください。

/test/controllers/static_pages_controller_test.rb
  test "should get root" do
    get root_url
    assert_response :success
  end

2.実はリスト 3.41のコードを書いていたので、先ほどの課題のテストは既に green になっているはずです。このような場合、テストを変更する前から成功していたのか、変更した後に成功するようになったのか、判断が難しいです。リスト 3.41のコードがテスト結果に影響を与えていることを確認するため、リスト 3.43のようにrootルーティングをコメントアウトして見て、 red になるかどうか確かめてみましょう (なおRubyのコメント機能については4.2.1で説明します)。最後に、コメントアウトした箇所を元に戻し (すなわちリスト 3.41に戻し)、テストが green になることを確認してみましょう。
 省略

メモ

  • assert_selectメソッドでは、特定のHTMLタグが存在するかどうかをテストする (この種のアサーションメソッドはその名から「セレクタ」と呼ばれることがある)
  • provideメソッドを使ってタイトルをページごとに変更する。
  • <%= yield %>は、各ページの内容をレイアウトに挿入するためのもの。
  • コントローラを新規作成するためのrailsコマンドはrails generate controller ControllerName アクション名 (省略可)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby理解度チェック】何が起きているか説明できますか? &&とandの違い

面白い質問があったのでテスト形式でシェア

現象

def ampersand_return
  puts "foo" && return
  puts "bar"
end

def and_return
  puts "foo" and return
  puts "bar"
end

> ampersand_return
=> nil

> and_return
foo
bar
=> nil

何が起きているか説明できますか?

回答

原則として、&&andの違いはその優先順位にある

&& returnの方

&&が先に演算され、以下と同値になる

puts ("foo" && return)

"foo"はtruthyなので、右辺のreturnが処理され、メソッドは何も出力せずreturnする。

and returnの方

puts "foo"が先に演算され、以下と同値になる

(puts "foo") and return

putsnilを返すのでandの右辺は処理されず、"foo""bar"両方が出力されメソッドは正常終了する。

終わりに

andの例のようにmethod callが優先される演算子に関しては、ドキュメントには明記されていない?よう
このStack Overflow Answerによるとand, or, if, unless, until, while, rescueがこの挙動をするらしい

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

【Ruby理解度チェック】何が起きているか説明できますか? &&とand

面白い質問があったのでテスト形式でシェア

現象

def ampersand_return
  puts "foo" && return
  puts "bar"
end

def and_return
  puts "foo" and return
  puts "bar"
end

> ampersand_return
=> nil

> and_return
foo
bar
=> nil

何が起きているか説明できますか?

回答

原則として、&&andの違いはその優先順位にある

&& returnの方

&&が先に演算され、以下と同値になる

puts ("foo" && return)

"foo"はtruthyなので、右辺のreturnが処理され、メソッドは何も出力せずreturnする。

and returnの方

puts "foo"が先に演算され、以下と同値になる

(puts "foo") and return

putsnilを返すのでandの右辺は処理されず、"foo""bar"両方が出力されメソッドは正常終了する。

終わりに

andの例のようにmethod callが優先される演算子に関しては、ドキュメントには明記されていない?よう
このStack Overflow Answerによるとand, or, if, unless, until, while, rescueがこの挙動をするらしい

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

【Rails】hamlコード早見表

Github公式 Haml-rails
チュートリアル HamlTutorial

導入

Gemfile
# 以下を追加し`bundle install`
gem "haml-rails", "~> 2.0"

# Rails4の場合
gem "haml-rails", "~> 1.0.0"
# Rails3の場合
gem "haml-rails", "~> 0.4.0"

html.erbをhtml.hamlに変換
$ rails generate haml:application_layout convert
全てのerbファイルを変換する場合
$ rails haml:erb2haml

HTMLコード

erb
<div>blue</div>
<div class="sky">blue</div>
<div class="sky">blue</div>
<div class="sky" id="line">blue</div>
haml
%div blue
%div.sky blue
.sky blue
.sky#line blue

Rubyコード

erb
<% if user_signed_in? %>
  <%= hoge %>
<% else %>
  <%= skyblue %>
<% end %>
haml
- if user_signed_in?
  = hoge
- else
  = skyblue

ネスト

erb
<div class="header-nav">
  <nav>
    <ul class="nav-list">
      <% if current_user %>
        <li class="list-item"><%= content %></li>
        <li class="list-item" id="sky"><%= link_to "blue" %></li>
      <% else %>
        <li class="list-item"><%= content %></li>
        <li class="list-item" id="sky"><%= link_to "blue" %></li>
      <% end %>
    </ul>
  </nav>
</div>
haml
.header-nav
  %nav
    %ul.nav-list
      - if current_user
        %li.list-item= content
        %li.list-item#sky= link_to "blue"
      - else
        %li.list-item= content
        %li.list-item#sky= link_to "blue"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsでproduction環境運用する際にしたほうがいい設定

ブラウザ上のエラー表示をdevelopmentとproduction環境で同じにする

まずdevelopment環境でERRORが発生するとブラウザに詳細情報が表示されます。
8c1d32d1432226f625cab33c2ed1fcc7.png
しかし、production環境だと何かしらのERRORが発生したとしても「We’re sorry, but something went wrong.」と表示されてしまいます。
picture_pc_f8886f63bd354c264487153ad2c5c567.png
そこでproduction環境でもブラウザに詳細情報を表示させるには
config.consider_all_requests_local = falsetrueにしてあげれば良い。

config/environments/production.rb
config.consider_all_requests_local = true
参考文献

Railsドキュメント

本番環境でDBをresetする

Rails5以降のproduction環境では、db:dropやdb:resetなどのDBを破壊する系のコマンド実行を防止する機能が追加された。
そのためproduction環境でbundle exec rake db:migrate:reset RAILS_ENV=productionなどをしてもエラーを発生してしまう。

対処法

環境変数にDISABLE_DATABASE_ENVIRONMENT_CHECK=1を指定してあげれば良い。

$ bundle exec rake db:migrate:reset RAILS_ENV=production DISABLE_DATABASE_ENVIRONMENT_CHECK=1
参考文献

Rails5のproductionでrake db:dropはできない、普通には

知っておくべきRailsのconfig/enviroments配下の設定

下記サイト一通り目を通すことでproduction環境で詰まった際に解決の糸口が分かるかも?
Railsのconfig/enviroments配下を読んでみる

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

【Rails】APIモードで開発を始めたけど、やっぱりcookie使いたい

はじめに

自分用なので手順しか書かない

手順

controllerに追記

controllerでcookiesを呼び出す時の話
APIモードだと cookies がそもそも呼び出せない

ApplicationController か、cookiesを呼びだすcontrollerに以下を追加

include ActionController::Cookies

middleware追加

middlewareを追加しないとcookieの読み出しも書き込みも実際してくれない

application.rbに以下の2行を追加

config.middleware.use ActionDispatch::Cookies
config.middleware.use ActionDispatch::Session::CookieStore

おわり

ここまでやるとDeviseも自然にset-cookie返してくれるようになります
基本はDevise + jwtでやってたんですけど、API経由ではないルート追加することになり、header渡せず無事死亡しました

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

GETとPOST(GET以外)で同じコード、でも挙動が違う

ちゃんと調査しないと気付かなかったのでメモがてら、誰かが同じ問題に直面したときの助けになれば。

概要

ある日、GETとPOSTで挙動が違うという話が。
コードを見てみたら、コントローラーの同じアクションにGETとPOSTがルーティングされていました。
どう見ても同じ処理。そこに差異は無いので挙動ベースでデバッグしてみることに。
テストコードでは上手く動いていました。でも直接APIを叩いてみると、たしかに違う挙動。

最終的に気付いたのが、問題のあるリクエストではCSRFトークンが渡ってきていないことでした。
アクション内ではsessionが使われており、CSRFトークンが渡されていないことでリクエストは空のsessionに対して処理されていました。これは元々のsessionとは別のものとして扱われ、その結果挙動が変わってしまっていました。

詳細

例としてこのようなコントローラがあるとします。
(POSTの代わりにDELETEにしていますが、挙動は同様です)

app/controllers/session_controller.rb
class SessionsController < ApplicationController
  def destroy
    session.delete(:user_id)
  end
end

そしてこのようにルーティングされています。

config/routes.rb
match 'logout', to: 'sessions#destroy', via: [:get, :delete]

前提として session[:user_id] には既に値が格納されているとします。
このとき、以下の2つはどのように動作するでしょうか?

  • GET /sessions/destroy へのAPIリクエスト
  • DELETE /sessions/destroy へのAPIリクエスト

コードだけ見ると、明らかにどちらも session[:user_id] が削除されそうです。疑いようがありません。ただし、 これはCSRFトークンがリクエストに追加されているか否かで挙動が変わります。

2つともCSRFトークンが追加されていない場合、答えはこうです。

  • GET /sessions/destroy へのAPIリクエストは session[:user_id]削除される
  • DELETE /sessions/destroy へのAPIリクエスト session[:user_id]削除されない

なぜか?
ここで ApplicationController を覗いてみましょう。

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :null_session
end

おっと、なにやら protect_from_forgery というコードが。
※ApplicationControllerに書かれているかは各プロダクトごとに異なります

このprotect_from_forgery with: :null_session が関係しています。
ドキュメントには以下の記述があります。

CSRF protection is turned on with the protect_from_forgery method. By default protect_from_forgery protects your session with :null_session method, which provides an empty session during request.
CSRF保護は、protect_from_forgeryメソッドでオンになります。デフォルトでは、protect_from_forgeryは、リクエスト中に空のセッションを提供する:null_sessionメソッドでセッションを保護します。
(Google翻訳)

この仕様によって、CSRFトークンによる検証が正しくない場合に session の中身が空の状態で、かつ別物として処理されます。また、GETではそもそもCSRFトークンの検証がされないため、session の中身は本来の状態で動作します。
その結果、消したはずの session[:user_id] が消えていないという挙動になります。

振り返り

なぜちゃんと調査しなければ気付かなかったか?

上記の例ではもしかすると気づけたかもしれません。ですが、 protect_from_forgeryApplicationController 側に書かれていたり、それによって普段はあまり意識しなかったりします。これが自分で書いたコードでなければ尚更です。一度経験すればおそらく以後は気付けると思うので、良い経験になりました。

そもそもなぜGETとPOST(GET以外)を同じアクションにルーティングしているのか?

これについては経緯が不明でした。(実際のコードにはコメントがありお気持ちだけは受け止めました)
Railsガイドにもこのような記載があります。

1つのアクションにGETリクエストとPOSTリクエストを両方ルーティングすると、セキュリティに影響する可能性があります。本当に必要な理由がない限り、1つのアクションにすべてのHTTP動詞をルーティングすることは避けてください。

GETとPOSTはRFCで定義された仕様としても別物なので、同じアクションにルーティングするのは避けるべきかなと思います。

さいごに

おそらく今回のものはかなりレアケースかなぁと思います。とはいえどこかの誰かがハマってしまうと悲しいのと、この記事に目を触れた誰かが同様のルーティングをしない、またはルーティングを考える時に注意できる種になればと思います。

お気づきの点があれば気軽にコメントお願いします。些細なことでも歓迎です :tada:

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

Rails  テーブル作成、カラムの追加や削除

はじめに

よく忘れるので、メモ

モデル作成 テーブル作成

railsでモデルを作成する際に、テーブルを作成するためのマイグレーションファイルが同時に作られます。
↓モデル作成コマンド

$ rails generate model [モデル名] [属性名:データ型 属性名:データ型・・・] [オプション]

省略版
$ rails g model [モデル名] [属性名:データ型 属性名:データ型・・・] [オプション]

-コマンドを実行すると、自動で作られるファイル

 ①モデルのクラスファイル
 ②マイグレーションファイル
 ③モデルの自動テスト
 ④モデルの自動テストで使うfictureファイル

例 $ rails g model User name:string email:string
上のコマンドで作ると下のマイグレーションができる。

class CreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      t.string :name
      t.string :email

      t.timestamps
    end
  end
end

そしたら、マイグレーションをする。

$ rails db:migrate

終わり

テーブル削除 カラム追加・削除

※クラス名はAddXXXToYYY、またはRemoveXXXFromYYYとします。
「XXX」にはテーブル名、YYYにはカラム名(複数のカラムを変更したい場合などがあるのでカラム名ではなくても大丈夫)

カラム追加のマイグレーション作成

※memo(text型)のカラムを追加する場合

例 $ rails g migration AddMemoToUsers memo:text

作成されたら$ rails db:migrateを実行し反映させます。

カラム削除のマイグレーション作成

※name(string型)のカラムを削除したい場合

例 $ rails g migration RemoveNameFromUsers name:string

作成されたら$ rails db:migrateを実行し反映させます。

テーブルの削除

マイグレーションファイルの作成
本当はusersとしたかったところをuserssとしてしまい。テーブルを削除することに。なので、マイグレーションファイルを作成する。

例 $ rails g migration userss

マイグレーションファイル編集
消したいテーブルを指定して、drop_table :userssと記述する。

例 class DeleteUserss < ActiveRecord::Migration[6.0]
  def change
    drop_table :userss
  end
end

下記のコマンドを叩くと、データベースから削除される。

$ rails db:migrate

ファイルの削除

ファイルツリーから直接消さずに、コマンドを実行してファイルを削除しましょう。

$ rails destroy migration クラス名

省略
$ rails d migration クラス名

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