20200625のRailsに関する記事は25件です。

簡単なrender

renderメソッドを使ってなかったので使ってみる。

一番簡単なやつ

index.html.erb
<%= render 'sample'%>

これで、indexのビューには _sample.html.erb の中身が埋め込まれる。

補足.
埋め込まれるファイルは、
<%= render '○○'%>
の○○の頭に
_をつけたもの。
例えば
<%= render 'form'%>
であれば_form.html.erbファイルが埋め込まれる。

埋め込むビューファイルのことをパーシャルという。
renderを使うことで同じようなビューを使いまわせて楽。

_sample.html.erb
<h1>これが表示されるよ</h1>

変数を渡したいとき

index.html.erb
<%= render 'sample2', aaa: @name %>

このように書くと@nameをaaaとしてパーシャル内で利用できる。

例えば@nameを渡して

_sample2.html.erb
<%= aaa.length %>

とすれば@nameの文字数を出力してくれる。

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

アクション

newアクション

表示してほしいアドレスが送られてきたら、まずルーティングにより処理される。
例えば /pages/new が送られてきたら、
ルートさんはルーティング対応表を見ながら対応するコントローラーとそのアクションを見つける。
「えーと、パスは pages/new で、getメソッドだから・・・pages コントローラーのnewアクションを
呼ぼう」
こうしてpages コントローラーのnewアクションに処理が移る。

newアクションの中に

def new
 @name=User.new
end

のようなものがあれば、Userクラスのインスタンスをつくり、@nameインスタンス変数へ代入し、ビューへ渡す。
インスタンスはクラスをnewしてできたもの。

ここで作られたインスタンスは設定により色々格納できるようになっているが中身は空っぽ。

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

【初学者手順書2】Ruby on Rails :Railsの雛形作成

初学者手順書1の続きです。
今回は、Railsの雛形を作成していきます。

Rails雛形作成

Railsアプリケーションを作成
ターミナル上、指定のディレクトリで実行

% rails _Ver.指定_ new アプリ名 -d RDBMS名(mysqlなど)

ファイルの文字コードの設定(必要な場合)

config/database.yml
# m4を削除
encoding: utf8

データベース作成

% rails db:create

不要なファイルが作成されないように追記(必要な場合)
rails gでコントローラを作成する場合テストファイルなどを自動で作るため

config/application.rb
# 省略
module アプリ名
  class Application < Rails::Application
    config.load_defaults 6.0
    config.generators do |g|
      g.stylesheets false
      g.javascripts false
      g.helper false
      g.test_framework false
    end
  end
end

GitHubコミットされたくないファイルを記載

.gitignore
public/uploads/*

READMEに、データベース設計やアプリの情報を記載していく。

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

マイグレーション

rails g model モデル名

等のコマンドを打つとdbフォルダ内にマイグレーションファイルが作成され、そこでテーブルの管理ができる。

t.string  

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

model

modelについて

rails g model モデル名 

でモデルを作成。

バリデーションやモデル同士の関連付けなどを記入する。

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

Rails初心者から中級者までに伝えたい、Controllerのアンチパターン3つ

tl; dr

controllerでは特に、インスタンス変数の濫用はやめよう!

すべてのはじまり

ほとんど触ったことないプロジェクトに移籍させられ早1ヶ月、そのコードを知る熟練の先輩も去った荒廃の地で、私は今日も一人、Railsと激闘を繰り広げていた!
というわけで(?)、今回はcontrollerでやってはいけないアンチパターンを紹介していきたい。

クソコード1. インスタンス変数に入れればええやろ

これは初心者がやらかしがちな印象
こういうやつ

sample_controller.rb
class SampleController < ApplicationController
  def show
    hoge = Hoge.new
    @hoge_kuso1 = hoge.kuso
    @hoge_kuso2 = hoge.sugoku_kuso
    @hoge_kuso3 = Hoge.fetch_kuso
  end
end

......

こ☆ろ☆す☆ぞ

まず第一に、Railsのコントローラーは一つのディレクトリに入るものをだいたい一つのクラスで固めるので、言い換えれば複数のアクションのロジックが一つのクラスにまとまっている。なので例えば次のようなコードは日常茶飯事である。

sample_controller.rb
class SampleController < ApplicationController
  def action1
  end

  def action2
  end
end

この状態で2つのアクションがインスタンス変数をたくさん使ったとしよう。それが以下

sample_controller.rb
class SampleController < ApplicationController
  def action1
    hoge = Hoge.new
    @hoge_kuso1 = hoge.kuso
    @hoge_kuso2 = Hoge.fetch_kuso
  end

  def action2
    hoge = Hoge.new
    @hoge_kuso1 = hoge.sugoku_kuso
    @hoge_kuso2 = Hoge.fetch_kuso
  end

  ...
end

ついでに Hoge クラスはこう

hoge.rb
require 'rest-client'

class Hoge
  def kuso
    :kuso
  end

  def sugoku_kuso
    "sugoku kuso"
  end

  def self.fetch_kuso
    RestClient.get('https://kuso.com/kuso3').body rescue 'honma kuso'
  end
end

この状態の何が悪いのかというと、インスタンス変数をたくさん使ったことにより、コード全体でどの変数がどこに所属しているのかが分かりにくいことだ。例えば @hoge_kuso1 はクラス内スコープの変数であるので、もしかしたら他のところの定義とコンフリクトしてバグるかもしれない。それにクラス内検索しても複数の全然関係ない奴が引っかかる可能性もあるので、補足もしづらい。あんまりメリットはないのである。

解決策

単純な話、極力インスタンス変数を使わないことだ。具体的にはこう。

sample_controller.rb
class SampleController < ApplicationController
  def action1
    @hoge = Hoge.new
  end

  def action2
    @hoge = Hoge.new
  end

  ...
end
hoge.rb
require 'rest-client'

class Hoge
  def kuso
    :kuso
  end

  def sugoku_kuso
    "sugoku kuso"
  end

  # 要素をモデルからアクセスできるようにして、controller上でインスタンス変数を使う可能性をを極力排除する
  def fetched_kuso
    @fetched_kuso ||= self.class.fetch_kuso
  end

  def self.fetch_kuso
    RestClient.get('https://kuso.com/kuso3').body rescue 'honma kuso'
  end
end

どういうことかというと、インスタンス変数に入れるのはモデル程度にしておいて、詳細なデータはモデル内に内包してしまおう、ということだ。これのメリットは2つ

  1. コントローラーがスッキリする
  2. ロジックが複数の場所に依らない

正直メリットしかないので、やらない手はないと思う。ちなみにたとえば複数のモデルに対して一括で値を取得したいんだけど、その場合はどうするの?ということについては

sample_controller.rb
class SampleController < ApplicationController
  def action1
    @hoges = 10.times.map { Hoge.new }
    Hoge.set_kusos(@hoges)
  end
end
hoge.rb
require 'rest-client'

class Hoge
  def kuso
    :kuso
  end

  def sugoku_kuso
    "sugoku kuso"
  end

  attr_accessor :fetched_kuso
  # 専用のセッター
  def self.set_kusos(hoges)
    hoges.each do |hoge|
      hoge.fetched_kuso = self.fetch_kuso
    end
  end

  def self.fetch_kuso
    RestClient.get('https://kuso.com/kuso3').body rescue 'honma kuso'
  end
end

とするなどして、モデル内にデータを保持するといい。とにかくコントローラーで無闇にロジックを書かないようにすることは大事である。

クソコード2. set_〇〇って書いとけば安泰やろ。其の壱

ダメです
例えばこう書く人はほとんどだと思う

sample_controller.rb
class SampleController < ApplicationController
  before_action :set_hoge, only: :action1

  def action1
  end

  private
  def set_hoge
    @hoge = Hoge.new
  end
end

絶対10人に9人はこう書いてる。なぜならscaffoldでもこう書くのを推奨してるから。
じゃあこれの何がダメなのかというと、以下の2つ

  1. コードが追いにくい
  2. 引数を渡しにくい

まず1について

sample_controller.rb
class SampleController < ApplicationController
  before_action :set_hoge,  only: :action1
  before_action :set_hoges, only: :action2

  def action1
  end
  def action2
  end

  private
  def set_hoge
    @hoge = Hoge.new
  end

  def set_hoges
    @hoges = [Hoge.new, Hoge.new]
  end
end

こう書かれた場合に、action1を確認した瞬間、あ、これは @hoge を使うんだな、ってなる人はいないだろう。
少なくとも、
action1がなにも定義されていない -> あ、before_actionが定義されているということはつまり -> やっぱり @hoge を使っていたか
となるのではないだろうか?
これはアハ体験としては適切な体験かもしれないが、コーディングに脳科学を持ち込まないでほしい。はっきり言ってストレスである。

また、 クソコード1でも述べたように、コントローラー内で使用されるインスタンス変数は所在がわかりにくくなりがちである。よってインスタンス変数を無闇に使うのはよろしくない。

次に2について

sample_controller.rb
class SampleController < ApplicationController
  before_action(only: :action1) do
    set_hoge('fuga')
  end

  def action1
  end

  private
  def set_hoge(fuga)
    @hoge = Hoge.new(fuga)
  end
end

こうしないと変数が渡せない。逆にこれだったら渡せるじゃん?という諸君。確かに渡せるが、これだとせっかく便利なskip_before_actionが利用できない。そこまでしてset_〇〇を使う必要ある?という気持ちでいっぱいである。

解決策

答えは単純である。

sample_controller.rb
class SampleController < ApplicationController
  def action1
    @hoge = fetch_hoge
  end

  private
  def fetch_hoge
    Hoge.new
  end
end

これであれば、インスタンス変数がロジック内に組み込まれているので、見た瞬間に @hoge を使うことがわかる。また引数を渡すのも容易である。とってもスマート、故にハッピー。

また、これだとredirect_toを書くのに手間がかかるじゃん!そっちはどうすんのさ!?っていうみなさんのために、以下のコードを授けよう

sample_controller.rb
class SampleController < ApplicationController
  class DataNotFound < RuntimeError; end
  rescue_from DataNotFound do
    redirect_to :root_path, flash: { error: 'データが見つかりませんでした' }
  end

  def action1
    @hoge = fetch_hoge
  end

  private
  def fetch_hoge
    hoge = Hoge.new
    raise DataNotFound unless hoge
    hoge
  end
end

これであれば元々の機能を維持できるだろう。

クソコード3. set_〇〇って書いとけば安泰やろ。其の弐

ダメだって言ってんだろ!!!
場合にもよるけど、例えばヘッダーで使う値をこうやって作る人いるよね

sample_controller.rb
class SampleController < ApplicationController
  before_action :set_header_de_tukau_yatu

  def set_header_de_tukau_yatu
    @header_info = 'void'
  end
end

これの問題点は「@header_info を利用する」ためには、「:set_header_de_tukau_yatuを呼び出す」必要があることを知っていなきゃいけないことだ。この場合、viewがほしいのは @header_info だけ、なのになぜその設定方法までこっちが把握してなきゃいかんの?っていう話

解決策

こういうときにどうすればいいのかというと helper_methodを使う。例えば以下

sample_controller.rb
class SampleController < ApplicationController
  helper_method :header_info

  def header_info
    @header_info ||= 'void'
  end
end

こうすることで、viewは header_info を呼び出すと、情報が得られる。ということを知っているだけでいい。わざわざ内部のロジックまで知る必要もないし、インスタンス変数もロジックに近い場所にある。とっても素晴らしい。

おわりに

この記事ではアンチパターンを3つしか紹介していないが、多分世の中にはもっとたくさんのアンチパターンがあると思う。しかし多くの場合、それは「可読性が下がる」「使い勝手が悪い」のいずれかないし両方を満たしているのではなかろうか?そしてその二つを生み出しがちな諸悪の根源、それはインスタンス変数の濫用である(暴論。今回本当に伝えたかったことはただ一つ。みなさんご唱和ください。

インスタンス変数の濫用はやめよう!!!!!

読了ありがとうございました。

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

Rails初心者から中級者までに伝えたい、アンチパターン3つ

tl; dr

controllerでは特に、インスタンス変数の濫用はやめよう!

すべてのはじまり

ほとんど触ったことないプロジェクトに移籍させられ早1ヶ月、そのコードを知る熟練の先輩も去った荒廃の地で、私は今日も一人、Railsと激闘を繰り広げていた!
というわけで(?)、今回はcontrollerでやってはいけないアンチパターンを紹介していきたい。

クソコード1. インスタンス変数に入れればええやろ

これは初心者がやらかしがちな印象
こういうやつ

sample_controller.rb
class SampleController < ApplicationController
  def show
    hoge = Hoge.new
    @hoge_kuso1 = hoge.kuso
    @hoge_kuso2 = hoge.sugoku_kuso
    @hoge_kuso3 = Hoge.fetch_kuso
  end
end

......

こ☆ろ☆す☆ぞ

まず第一に、Railsのコントローラーは一つのディレクトリに入るものをだいたい一つのクラスで固めるので、言い換えれば複数のアクションのロジックが一つのクラスにまとまっている。なので例えば次のようなコードは日常茶飯事である。

sample_controller.rb
class SampleController < ApplicationController
  def action1
  end

  def action2
  end
end

この状態で2つのアクションがインスタンス変数をたくさん使ったとしよう。それが以下

sample_controller.rb
class SampleController < ApplicationController
  def action1
    hoge = Hoge.new
    @hoge_kuso1 = hoge.kuso
    @hoge_kuso2 = Hoge.fetch_kuso
  end

  def action2
    hoge = Hoge.new
    @hoge_kuso1 = hoge.sugoku_kuso
    @hoge_kuso2 = Hoge.fetch_kuso
  end

  ...
end

ついでに Hoge クラスはこう

hoge.rb
require 'rest-client'

class Hoge
  def kuso
    :kuso
  end

  def sugoku_kuso
    "sugoku kuso"
  end

  def self.fetch_kuso
    RestClient.get('https://kuso.com/kuso3').body rescue 'honma kuso'
  end
end

この状態の何が悪いのかというと、インスタンス変数をたくさん使ったことにより、コード全体でどの変数がどこに所属しているのかが分かりにくいことだ。例えば @hoge_kuso1 はクラス内スコープの変数であるので、もしかしたら他のところの定義とコンフリクトしてバグるかもしれない。それにクラス内検索しても複数の全然関係ない奴が引っかかる可能性もあるので、補足もしづらい。あんまりメリットはないのである。

解決策

単純な話、極力インスタンス変数を使わないことだ。具体的にはこう。

sample_controller.rb
class SampleController < ApplicationController
  def action1
    @hoge = Hoge.new
  end

  def action2
    @hoge = Hoge.new
  end

  ...
end
hoge.rb
require 'rest-client'

class Hoge
  def kuso
    :kuso
  end

  def sugoku_kuso
    "sugoku kuso"
  end

  # 要素をモデルからアクセスできるようにして、controller上でインスタンス変数を使う可能性をを極力排除する
  def fetched_kuso
    @fetched_kuso ||= self.class.fetch_kuso
  end

  def self.fetch_kuso
    RestClient.get('https://kuso.com/kuso3').body rescue 'honma kuso'
  end
end

どういうことかというと、インスタンス変数に入れるのはモデル程度にしておいて、詳細なデータはモデル内に内包してしまおう、ということだ。これのメリットは2つ

  1. コントローラーがスッキリする
  2. ロジックが複数の場所に依らない

正直メリットしかないので、やらない手はないと思う。ちなみにたとえば複数のモデルに対して一括で値を取得したいんだけど、その場合はどうするの?ということについては

sample_controller.rb
class SampleController < ApplicationController
  def action1
    @hoges = 10.times.map { Hoge.new }
    Hoge.set_kusos(@hoges)
  end
end
hoge.rb
require 'rest-client'

class Hoge
  def kuso
    :kuso
  end

  def sugoku_kuso
    "sugoku kuso"
  end

  attr_accessor :fetched_kuso
  # 専用のセッター
  def self.set_kusos(hoges)
    hoges.each do |hoge|
      hoge.fetched_kuso = self.fetch_kuso
    end
  end

  def self.fetch_kuso
    RestClient.get('https://kuso.com/kuso3').body rescue 'honma kuso'
  end
end

とするなどして、モデル内にデータを保持するといい。とにかくコントローラーで無闇にロジックを書かないようにすることは大事である。

クソコード2. set_〇〇って書いとけば安泰やろ。其の壱

ダメです
例えばこう書く人はほとんどだと思う

sample_controller.rb
class SampleController < ApplicationController
  before_action :set_hoge, only: :action1

  def action1
  end

  private
  def set_hoge
    @hoge = Hoge.new
  end
end

絶対10人に9人はこう書いてる。なぜならscaffoldでもこう書くのを推奨してるから。
じゃあこれの何がダメなのかというと、以下の2つ

  1. コードが追いにくい
  2. 引数を渡しにくい

まず1について

sample_controller.rb
class SampleController < ApplicationController
  before_action :set_hoge,  only: :action1
  before_action :set_hoges, only: :action2

  def action1
  end
  def action2
  end

  private
  def set_hoge
    @hoge = Hoge.new
  end

  def set_hoges
    @hoges = [Hoge.new, Hoge.new]
  end
end

こう書かれた場合に、action1を確認した瞬間、あ、これは @hoge を使うんだな、ってなる人はいないだろう。
少なくとも、
action1がなにも定義されていない -> あ、before_actionが定義されているということはつまり -> やっぱり @hoge を使っていたか
となるのではないだろうか?
これはアハ体験としては適切な体験かもしれないが、コーディングに脳科学を持ち込まないでほしい。はっきり言ってストレスである。

また、 クソコード1でも述べたように、コントローラー内で使用されるインスタンス変数は所在がわかりにくくなりがちである。よってインスタンス変数を無闇に使うのはよろしくない。

次に2について

sample_controller.rb
class SampleController < ApplicationController
  before_action(only: :action1) do
    set_hoge('fuga')
  end

  def action1
  end

  private
  def set_hoge(fuga)
    @hoge = Hoge.new(fuga)
  end
end

こうしないと変数が渡せない。逆にこれだったら渡せるじゃん?という諸君。確かに渡せるが、これだとせっかく便利なskip_before_actionが利用できない。そこまでしてset_〇〇を使う必要ある?という気持ちでいっぱいである。

解決策

答えは単純である。

sample_controller.rb
class SampleController < ApplicationController
  def action1
    @hoge = fetch_hoge
  end

  private
  def fetch_hoge
    Hoge.new
  end
end

これであれば、インスタンス変数がロジック内に組み込まれているので、見た瞬間に @hoge を使うことがわかる。また引数を渡すのも容易である。とってもスマート、故にハッピー。

また、これだとredirect_toを書くのに手間がかかるじゃん!そっちはどうすんのさ!?っていうみなさんのために、以下のコードを授けよう

sample_controller.rb
class SampleController < ApplicationController
  class DataNotFound < RuntimeError; end
  rescue_from DataNotFound do
    redirect_to :root_path, flash: { error: 'データが見つかりませんでした' }
  end

  def action1
    @hoge = fetch_hoge
  end

  private
  def fetch_hoge
    hoge = Hoge.new
    raise DataNotFound unless hoge
    hoge
  end
end

これであれば元々の機能を維持できるだろう。

クソコード3. set_〇〇って書いとけば安泰やろ。其の弐

ダメだって言ってんだろ!!!
場合にもよるけど、例えばヘッダーで使う値をこうやって作る人いるよね

sample_controller.rb
class SampleController < ApplicationController
  before_action :set_header_de_tukau_yatu

  def set_header_de_tukau_yatu
    @header_info = 'void'
  end
end

これの問題点は「@header_info を利用する」ためには、「:set_header_de_tukau_yatuを呼び出す」必要があることを知っていなきゃいけないことだ。この場合、viewがほしいのは @header_info だけ、なのになぜその設定方法までこっちが把握してなきゃいかんの?っていう話

解決策

こういうときにどうすればいいのかというと helper_methodを使う。例えば以下

sample_controller.rb
class SampleController < ApplicationController
  helper_method :header_info

  def header_info
    @header_info ||= 'void'
  end
end

こうすることで、viewは header_info を呼び出すと、情報が得られる。ということを知っているだけでいい。わざわざ内部のロジックまで知る必要もないし、インスタンス変数もロジックに近い場所にある。とっても素晴らしい。

おわりに

この記事ではアンチパターンを3つしか紹介していないが、多分世の中にはもっとたくさんのアンチパターンがあると思う。しかし多くの場合、それは「可読性が下がる」「使い勝手が悪い」のいずれかないし両方を満たしているのではなかろうか?そしてその二つを生み出しがちな諸悪の根源、それはインスタンス変数の濫用である(暴論。今回本当に伝えたかったことはただ一つ。みなさんご唱和ください。

インスタンス変数の濫用はやめよう!!!!!

読了ありがとうございました。

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

rails routes(ルーティング)の順番の解説

自分用のまとめ

https://qiita.com/gawach/items/4e7460d06f70e3013eea
にてまとめられているが、私は違う理解の仕方をしている。
それを説明します。

⑴
  1 get "posts/:id" => "posts#show"
  2 get "posts/new" => "posts#new"
⑵
  3 get "posts/new" => "posts#show"
  4 get "posts/:id" => "posts#new"
⑶(プロゲートに書いてあった正しいコード)
    get "posts/index" => "posts#index"
    get "posts/:id" => "posts#show" 

⑴は間違いであり、⑵は実は正しいと決める、それを下記に示す。

上から順にコードを優先していくため、
:id以外の特定のURLが:idより先にくるようにする。
そしてそれら以外の余ったものが:idの対象となる。
上記のようなルールがあるため、
:idはposts/newのようなURLより先に記述されているとエラーになる。

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

【Rails】lazy_high_chartsを用いてグラフを作成する方法

目標

本の月間登録推移を1日ごとに見れるグラフを作成します。
ezgif.com-video-to-gif.gif

開発環境

・Ruby: 2.5.7
・Rails: 5.2.4
・Vagrant: 2.2.7
・VirtualBox: 6.1
・OS: macOS Catalina

前提

下記実装済み。

Slim導入
Bootstrap3導入
Font Awesome導入
ログイン機能実装
投稿機能実装

実装

1.Gemを導入

Gemfile
# 追記
gem 'lazy_high_charts'
ターミナル
$ bundle

2.application.jsを編集

application.js
//= require rails-ujs
//= require activestorage
//= require turbolinks
//= require highcharts/highcharts // 追記
//= require highcharts/highcharts-more // 追記
//= require_tree .

3.コントローラーを編集

books_controller.rb
def index
  @book = Book.new
  @books = Book.all
  # 追記
  days = (Date.today.beginning_of_month..Date.today).to_a
  books = days.map { |item| Book.where(created_at: item.beginning_of_day..item.end_of_day).count }
  @graph = LazyHighCharts::HighChart.new('graph') do |f|
    f.title(text: '本 月間登録推移')
    f.xAxis(categories: days)
    f.series(name: '登録数', data: books)
  end
end

① 今月1日から今日までの日付を取得し、変数へ代入する。

days = (Date.today.beginning_of_month..Date.today).to_a

で取得した日付内に作成された本の数を取得し、変数へ代入する。

books = days.map { |item| Book.where(created_at: item.beginning_of_day..item.end_of_day).count }

③ グラフを作成する

@graph = LazyHighCharts::HighChart.new('graph') do |f|
  f.title(text: '本 月間登録推移') # タイトル
  f.xAxis(categories: days) # 横軸
  f.series(name: '登録数', data: books) # 縦軸
end

4.ビューを編集

books/index.html.slim
/ 追記
= high_chart('sample', @graph)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

bundle config --global build.mysql2 でエラー

新アプリケーションを作成する前段階で起きた話(一部ユーザー名に編集加えてます)。

bundlerの構成オプションをいじろうとして以下のコマンドを入力

bundle config --global build.mysql2 --with-opt-dir="$(brew --prefix openssl)"

するとこんなエラーが...

There was an error while trying to write to /Users/hoge/.bundle/config. 
It is likely that you need to grant write permissions for that path.

ターミナルから「configをいじる権限ないよ。おととい来やがれ」と言われてしまいました・・・・・・(過激)。

.configがどうなっているか確認したいなと思い以下のコマンドを入力

% cd .bundle
% cd .config

すると不可思議なことが発生する

cd: no such file or directory: .config

!!??

.config がないって・・・どういうこと・・・?

なんでだろう・・・と思って権限の確認を行うために以下のコマンドを入力

% cd
% ls -ls

すると権限一覧がこちら

※一部抜粋
drwxr-xr-x+  51 hoge  staff    1632  6 25 19:41 .
drwxr-xr-x    6 root       admin     192  3  3 20:00 ..
-r--------    1 hoge  staff       7  3 27 23:03 .CFUserTextEncoding
-rw-r--r--@   1 hoge  staff   18436  6 24 10:28 .DS_Store
drwx------@ 286 hoge  staff    9152  6 23 18:06 .Trash
-rw-r--r--    1 hoge  staff       1  5 23 15:13 .bash_profile
-rw-------    1 hoge  staff   12288  4 30 11:58 .bashrc.swp
drwxr-xr-x    4 root  staff     128  4 16 17:02 .bundle
drwxr-xr-x    3 hoge  staff      96  4  3 13:42 .gem

.bundleがrootになってる!!!!

ということで以下のコマンド入力して権限者を変更しましょう。


sudo chown -R [権限を与えたいユーザー名] .bundle

今回ダミー名の「hoge」を入力。


sudo chown -R hoge .bundle

解決!!!!

同様のエラーが生じた際の参考になれば幸いです!

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

Cloud9がメモリー足りないってよ: Railsチュートリアル備忘録

Cloud9がメモリー足りないってよ: Railsチュートリアル備忘録

Cloud9に以下のメッセージが出てコンソールが固まる事態に

This environment is running low on memory (132 MB of virtual memory left).
To free up memory, kill processes by choosing "Tools > Process List" in the menu bar.
To migrate this environment to one that has more memory, ...

解決法

EC2インスタンスを再起動する

経緯

Railsチュートリアル13章進行中のこと
トリガーもよくわからいのですがTestを走らせると

... Missing helper file helpers/microposts_helper.rb ...

といったエラーのが出るように

Ruby on RailsでAbstractController::Helpers::MissingHelperError:Missing helper file が発生したときの対処法 - Qiita

bundle updateするといいよとの記事があったため
試してみたところ

Cloud9上で上記通知がでるように...

ググってみてもあまり情報がなく
いっそ試してみたかったDockerを利用して環境再構築...なんて考えましたが

結局EC2インスタンスを再起動することで2つとも解決しました

インスタンスの再起動 - Amazon Elastic Compute Cloud

いまさらですがオンラインIDEであるCloud9は
AWSの一部であるEC2サービスで提供される
仮想サーバー(これがEC2インスタンス)上で動いているわけですね

インスタンスという言葉がすっと頭に馴染むようになってきたこととに喜び、
AWSの全体像について視野が広がったという学びでした

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

Gem simple calendarでのカレンダー実装と条件分岐

概要

現在DMMWEBCAMPというプログラミングスクールで学習しておりまして、
ポートフォリオ作成にあたりイベントテーブルを作成してカレンダーに表示させました。
これから作成する方のお役に立てればと思います。

完成図

スクリーンショット 2020-06-25 19.05.35.png

参考にさせて頂いた記事

【Rails】Simple Calendarを使ったカレンダー表示
https://qiita.com/mikaku/items/0971442a7308dc669122

github
https://github.com/excid3/simple_calendar

事前準備

イベントテーブル

スクリーンショット 2020-06-25 18.01.19.png

・data_and_timeがスタートする日時
・meetingfinishtimeが終了する日時

イベント参加テーブル

※投稿いいね機能の延長です。
スクリーンショット 2020-06-25 18.11.29.png

アソシエーション

models/event.rb
class Event < ApplicationRecord
 has_many :event_participates, dependent: :destroy
end
models/event_participate.rb
class EventParticipate < ApplicationRecord
 belongs_to :event
end

gem install

gem 'simple_calendar', '~>2.0'
$ bundle install

Simple calendarのViewインストール

$ rails g simple_calendar:views

・views/simple_calendar内に3種類のカレンダーの雛形が出来ます。

application.scss(CSS)への記載

stylesheets/applecation.scss
*= require simple_calendar
*= require_tree .
*= require_self

start_time end_timeの定義

models/event.rb
class Event < ApplicationRecord
  has_many :event_participates, dependent: :destroy

  def start_time
    self.date_and_time
  end

  def end_time
    self.meetingfinishtime
  end
end

このgemではstart_timeとend_timeの定義が必要です。(特にstart_time)
作成しているカラムが最初からその名前であれば良いのですが、違うカラム名なのであればモデルの方で定義しましょう。ただし日付と時刻を扱うのでデータ型は恐らくdatetime型でなければならないと思います。
最初定義せずに実装したところエラーが出ました。

githubではscaffoldを使用して最初から上記名称でカラムを作成しておりました。

カレンダーの表示

私のポートフォリオ

views/events/index.html.erb
</div>
        <div class= 'calender'>
            <%= month_calendar events: @events do |date, event| %>
                <%= date %>

                <% event.each do |event| %>
                <br>
                <% if user_signed_in? %>
                    <% if event.user_id == current_user.id %>
                        <i class="fa fa-circle notification-circle" style="color: red;"></i>
                    <% end %>
                <% end %>
                    <% if event.event_participates.where(user_id: current_user).exists? %>
                        <span class="participate">
                          <%= link_to event.title,event_path(event) %>
                        </span>
                    <% else %>
                         <span class="other">
                          <%= link_to event.title,event_path(event) %>
                        </span>
                    <% end %>
                <% end %>
            <% end %>
        </div>

カレンダーの表示とイベントのタイトルだけであれば以下のみで良いです。

<div class= 'calender'>
            <%= month_calendar events: @events do |date, event| %>
                <%= date %>

                <% event.each do |event| %>
                  <%= event.title %>
                <% end %>
            <% end %>
</div>
controllers/events_controller.rb
def index
    @events = Event.all
end

コントローラーで定義しているEvent.allを@eventsに格納しカレンダーで表示しています。
<%= month_calendar events: @events
この構文の中のevents:は恐らく変えるとエラーが出ますが、インスタンス変数は情報が格納されていればどんなものを使用しても大丈夫だと思います。

現在は先ほどインストールしたmonth_calendarを使用していますが、他に2つテンプレートが用意されているので3種類から使用することが出来るようです。

私は参加(participate)アクションを取っていればbackground-colorをオレンジに、
主催しているイベントであれば赤丸のアイコンを付けたかったので以下のような条件分岐を
カレンダー内で行いました。

  <% if event.user_id == current_user.id %>
     <i class="fa fa-circle notification-circle" style="color: red;"></i>
  <% end %>

イベントテーブルの中のuser_id(つまりイベント作成者)が自分であればイベントタイトルの前にfont awesomeのアイコンを表示というものです。インライン要素のものであればここは何でも良いと思います。

次に

 <% if event.event_participates.where(user_id: current_user).exists? %>
      <span class="participate">
        <%= link_to event.title,event_path(event) %>
      </span>
<% else %>
      <span class="other">
        <%= link_to event.title,event_path(event) %>
      </span>
<% end %>

1行目の <% if event.event_participates.where(user_id: current_user).exists? %>で
eventに参加(participete)するユーザーの中に自分(current_user)が存在しているかどうかを探しています。
見つかればclass participate、そうでなければclass otherとすることで、
表示する情報は同じでもclass名が違うという状態にすることが出来ました。

後はCSSで色を変えていくだけなので、皆さんのお好みのようにしていただければと思います。

ちなみにmonth_calenderのviewは以下です。

views/simple_calendar/_month_calendar.html.erb
<div class="simple-calendar">
  <p class="carendar-title"><%= "#{Date.today.month}月のイベントスケジュール" %></p>

  <div class="calendar-heading" data-turbolinks="false">
    <!--%= link_to t('simple_calendar.previous', default: 'Previous'), calendar.url_for_previous_view %-->
    <!-- <span class="calendar-title"><%= t('date.month_names')[start_date.month] %> <%= start_date.year %></span> -->
    <!--%= link_to t('simple_calendar.next', default: 'Next'), calendar.url_for_next_view %-->
    <i class="fa fa-circle notification-circle" style="color: red;"><span>主催イベント</span></i>
    <span class="calendar-info">参加予定</span>
  </div>

  <table class="table table-striped calendar-table">
    <thead>
      <tr>
        <% date_range.slice(0, 7).each do |day| %>
          <th><%= t('date.abbr_day_names')[day.wday] %></th>
        <% end %>
      </tr>
    </thead>

    <tbody>
      <% date_range.each_slice(7) do |week| %>
        <tr>
          <% week.each do |day| %>
            <%= content_tag :td, class: calendar.td_classes_for(day) do %>
              <% if defined?(Haml) && respond_to?(:block_is_haml?) && block_is_haml?(passed_block) %>
                <% capture_haml(day, sorted_events.fetch(day, []), &passed_block) %>
              <% else %>
                <% passed_block.call day, sorted_events.fetch(day, []) %>
              <% end %>
            <% end %>
          <% end %>
        </tr>
      <% end %>
    </tbody>
  </table>
</div>

私のポートフォリオの仕様で上の部分をコメントアウトしています。
コメントアウトの場所に本来であれば次月や前月へのリンクが出てきます。
細かいところであれば柔軟に変えれそうです。

まとめ

最初はfullcalendarでの実装を目論んだのですが、理解が及ばず諦めて今回こちらのgemを使用しました。イベントタイトルがstart_timeとend_timeの間で毎回出てしまうことを改善できればと思ったのですが私の力不足で難しかったです。
要素のclass名に特に意味はありません。スペルを所々間違えてますが、、
このぐらい簡単なカレンダーでも見せ方次第で私のものよりもっといいものになると思うので是非やってみてください。

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

AWSでデプロイしちゃおうってわけ(AWS登録編)

こんばんはですね。
お久しぶりになってしまい非常に私反省しております。
昨日は地震が酷かったですね。
寝ていて若干起きたけど、眠気に負けてすぐ寝ましたが。

気になったのはニュースで
「余震には気をつけましょう!」
っていってるけど

気をつけようがないだろ!!!予測できないんだし!!!

と思ってしまう私は悪い子でしょうか。

さて今回はデプロイに関してAWSを使ってデプロイしていくわよっ!!

1 まずはAWSのHPへアクセスっ!!

こちらから飛んで頂戴
AWS

開くとへいっB4F43F82-6C2A-4F66-969D-8DC7AC4671EC.png

そんで右上への無料サインアップから登録して頂戴!!

※住所、電話番号を登録するページで一番下でアーグリメント〜って聞かれるからそれはチェックしておく様に!!

2 クレジットカート登録+電話番号認証

若いアンタ達なら画像無しでも出来るハズダワ。。。。

その次にプランを選ぶところがあるから

有無を言わさずベーシック無料プラン!!!!!!

そんで登録するとへいっスクリーンショット 2020-06-25 18.32.56.png

これで準備完了よ。

次はインスタンスとかなんちゃらとかやっていくわよ!!!!

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

�Rails + Heroku でメール認証

概要

Railsアプリのメール認証をHerokuの本番環境で実装しました。
当初はRailsチュートリアルの通りSendGridを使用していたのですが、すぐにアカウントが凍結されてしまい一歩も先に進めなかったのでGmailに切り替えました。

logを確認したらNet::SMTPAuthenticationErrorの一文があったので、SendGridではなくそもそもGmailでエラーを起こしていた可能性も...?

SendGridで送信に失敗する場合確認するべきこと

存在するメールアドレスへ送信しているか?

開発環境でgemの「letter_opener_web」などを使用している場合、「test@mail.com」のような架空のアドレスでも送信をすることができましたが、本番環境では送信できません。(そもそもアドレスがなきゃ届かない...)
私ほどの間抜けでなければしないミスでしょうが、タイポの確認は最初にしてみても損はないでしょう。

Net::SMTPAuthenticationErrorが出ていないか?

送信元アドレスにGmailを設定している場合、「2 段階認証プロセス」をオンにしていないとエラーが発生するようです。
(他のメールサービスでも同様のセキュリティがあるのかな?)
次のコマンドから確認ができます。

heroku logs --tail

正常に送信されていれば次のようにメールの内容が表示されるので、どちらにしてもログは確認しておいたほうがいいでしょう。

 From:         please-change-me-at-config-initializers-devise@example.com
 Reply-To:     please-change-me-at-config-initializers-devise@example.com
 To:           test@mail.com
 Message-ID:   <5ef461204d29d_475308ddc-2ec6f04e388a.mail>
 Subject:      =?UTF-8?Q?
 Mime-Version: 1.0
 Content-Type: text/html;
 charset=UTF-8
 Content-Transfer-Encoding: base64
 ~メール本文~

SendGridのアカウントを凍結されていないか?

Railsチュートリアルに倣ってコンソールからSendGridに登録するとほぼ確実に凍結されました。(私は5/5で凍結されました...)
登録後すぐはHerokuのダッシュボード画面にある「Installed add-ons」のSendGridをクリックすると、SendGridへアクセスできるのですが、一度メールの送信に失敗するとクリックしてもアクセスが出来なくなりました。
この状態になるとアカウントが凍結されており、新しくappを作成し直すかSendGrid社へコンタクトを取って凍結を解除してもらうしかないようです。
ちなみに、SendGridで検索してトップに出てくる日本語ページ(構造計画研究所)ではサポート対象外になっているので本社に英語でコンタクトを取らなければならないようです。
Azure marketplace等のアドオンとしてSendGridを利用していますが、日本語サポートは受けられますか?

GmailのSMTPを利用する

Googleアカウントに2段階認証をオンにする

Googleアカウント管理画面の「セキュリティ」 > 「二段階認証プロセス」から設定をして二段階認証をオンにしてください。

コードの追加

config/environments/production.rb
  # herokuでメールを送信するための設定
  config.action_mailer.delivery_method = :smtp
  host = "#{ENV['HEROKU_APPNAME']}.herokuapp.com"
  config.action_mailer.default_url_options = { host: host, protocol: 'https' }
  ActionMailer::Base.smtp_settings = {
    :address        => 'smtp.gmail.com',
    :port           => '587',
    :authentication => :plain,
    :user_name      => ENV['GMAIL_USERNAME'],
    :password       => ENV['GMAIL_PASSWORD'],
    :domain         => 'gmail.com',
    :enable_starttls_auto => true
  }

{ENV['HEROKU_APPNAME']}は、変数にせず直接アプリの名前を入力しても問題ありません。

Herokuの環境変数を設定

バッシュボードの「Settings」 > 「Config Vars」で「Reveal Config Vars」をクリックすると環境変数の設定が出来ます。
コンソールからも出来ますがこちらの方が分かりやすいのでいつもこっちを使っています。

Key => Value
GMAIL_USERNAME => 送信元に設定するメールアドレス
GMAIL_PASSWORD => 二段階認証の設定で取得したパスワード
HEROKU_APPNAME => Herokuで設定しているアプリ名

参考

Heroku Add-onsのSendGridトラブルシューティング
Railsでgmailでメールを送ろうとしたけどsmtp認証のエラーが出て詰まった時の解決方法

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

CronをDocker上で動かす

概要

  • バージョン: Ruby 2.6.5 Rails 5.2.4
  • 開発環境: Docker/docker-compose

実現したいこと

  • cronを使ってRailsのバッチ処理を実現したい
  • Docker環境で動かしたい

BusyBoxの利用がお勧め

元々、Wheneverを使って設定したcronをDocker上で動かそうとしていました。

Docker環境だとcronが動かないようなので、色々な記事を参考にし各種ファイルの設定を行いました。

しかし、cronが動かない。Dockerコンテナに入りエラーログを調べ、対応しても上手くいきませんでした。

ちなみに、エラーログとその対応は以下になります。

log/crontab.log
bundler: failed to load command: bin/rails (bin/rails)
log/crontab_error.log
ActiveRecord::AdapterNotSpecified: 'production' database is not configured. Available: ["default", "development", "test"]

以下の記事を参考にし、
https://www.cotegg.com/blog/?p=1606
schedule.rbに環境変数を渡す記述をしたり、database.ymlにproductionデータベースを構成する記述を追記したりしました。

cronの再起動を行い、再びlogファイルを確認しましたが、再度同じエラーが吐かれてしまいました。

恐らく、環境変数を上手く渡せていなかったり、Docker 上でのcron の実行環境が、うまく開発環境と認識されていないことが原因だったのではないかと思います。

私の場合は、結局Wheneverを使って設定したcronをDocker上で動かすことができませんでした。

そこで、この問題が比較的簡単に解決してくれたのが、
BusyBoxという方法です。

BubyBoxのメリット

以下のDocker上でcronを使用する際の課題を解決し、シンプルに定期実行をしてくれます。

  • cronで実行するプログラムにコンテナに設定した環境変数を渡したい。
    • cronは環境変数が独立している
    • いちいちファイルに書き出し、読み込みが必要
  • ログが標準出力・標準エラー出力されない

BusyBoxのインストール(Debian系)

※AlpineLinuxの場合、crondで使えるようです

apt-get install -y busybox-static

ディレクトリ構成

.
|- Dockerfile
|- crontab
|- docker-compose.yml
|- main.sh

Dockerfile

Dockerfile
FROM ruby:2.6.5

# インストール
RUN apt-get update && apt-get install -y \busybox-static \

# タイムゾーン設定
ENV TZ=Asia/Tokyo

# mai.shファイルをコピー
COPY ./main.sh /myapp

CMD ["busybox", "crond", "-l", "8", "-L", "/dev/stderr", "-f"]

crontab

テスト用に1分毎に実行します

* * * * * /app/main.sh

docker-compose.yml

crontabファイルをマウントすることで、外部から実行時間を指定できるようになります。

docker-compose.yml
version: '3'

services:
  busybox:
    build: .
    volumes:
      - ./crontab:/var/spool/cron/crontabs/root

main.sh

権限エラーを防ぐために、ターミナル上で以下を実行します。

chmod +x main.sh

テストとして、Dateを出力します。

main.sh
date

実行

docker-composeで実行する場合

docker-compose build   # Dockerイメージをビルド
docker-compose up -d   # docker-compose.ymlの変更を反映させる
docker-compose logs -f # ログ出力

1分毎に、dateを出力できました!

787193B8-CC36-469B-909A-0AD8ED1DF701_4_5005_c.jpeg

参考にした記事

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

Rails×Heroku環境でSSL化したときhttp->httpsにリダイレクトさせる方法

config/environments/production.rb
  # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
  config.force_ssl = true # ←ここがコメントアウトされてるので解除するだけ

公式ドキュメント
https://help.heroku.com/J2R1S4T8/can-heroku-force-an-application-to-use-ssl-tls

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

slimの始め方

slimのインストール

アプリのフォルダ内でslimをインストールする。

gem install slim

これで拡張子が.html.slimのslimファイルを、みなさんが普段使っている.html.erbとして変換されるようになるそうです。

gem install html2slim

これでhtml.erb → html.slim に変換させることができるようになるそうです。

bundle exec erb2slim app/views app/views

これでエラーが出た場合は、gemfileに下記の2文を追加してbundle installします。

gem 'slim-rails'
gem 'html2slim'
bundle install

これでviewフォルダに元からあったerbファイルを削除するそうです。
※拡張子が.erbのファイルがない場合は、飛ばしてよし。

bundle exec erb2slim app/views app/views -d

今後自動的にslimファイルを作成されるようにするためには 、
config/application.rbにあるconfigを以下のようにslimを指定すればOK。

config/application.rb
module App
  class Application < Rails::Application
    config.generators.template_engine = :slim #slimに変更
  end                                                                                                                                                                     
end

slimファイルを表示させる前の準備

rails g controller tweets

viewフォルダのなかに、tweetsフォルダができています。
そのなかにindex.html.slimというファイルを新しく作ります。
そのファイルの中に分かりやすいように何か書いておきましょう。

views/tweets/index.html.slim
Hello, world!

コントローラーの中にindexアクションを追加しておきます。

controllers/tweets_controller.rb
class ArchivesController < ApplicationController
    def index
    end
end

次にルーティングです。

config/routes.rb
Rails.application.routes.draw do
  root "tweets#index"
end

サーバーを再起動して、ページを表示

サーバーの再起動を忘れないようにしましょう。
再起動しないままだと、「ArchivesController#index is missing a template for request formats: text/html」というようなエラーが出ます。

http://localhost:3000/

参考記事

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

rails ルーティング

ルーティング

root トップページのルーティングを設定するときに使う。

get ページを取得するときに使う

post データを送るときに使う

put データを置換するときに使う

patch データを部分的に更新するときに使う

delete データを削除するときに使う

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

【バリデーション】rails 今日の日付以降を指定する方法

【ゴール】

日付入力の際に、今日以降の日付しか入力できないように
バリデーションをかける

【メリット】

■ UXの向上
■ validate理解度向上

【開発環境】

■ Mac OS catalina
■ Ruby on Rails (5.2.4.2)
■ Virtual Box:6.1
■ Vagrant: 2.2.7

【実装】

※任意のメソッド名でvalidateを作成
※メソッドの処理を追記
 ①「return if start_day.blank?」 で空かどうか判断
 ②「if start_day < Date.today」で今日の日付を取得 & 条件分岐 & 比較
 ③「errors.add」でエラー文もついでに定義
 

model/item.rb


validate :date_before_start
validate :date_before_finish



  def date_before_start
    return if start_day.blank?
    errors.add(:start_day, "は今日以降のものを選択してください") if start_day < Date.today
  end

  def date_before_finish
    return if finish_day.blank? || start_day.blank?
    errors.add(:finish_day, "は開始日以降のものを選択してください") if finish_day < start_day
  end

以上!!

【合わせて読みたい】

■RubyとRailsにおけるTime, Date, DateTime, TimeWithZoneの違い
https://qiita.com/jnchito/items/cae89ee43c30f5d6fa2c

■ 【ERROR メッセージ表示】rails 部分テンプレートでいつでも使える 簡易版
https://qiita.com/tanaka-yu3/items/63b189d3f15653cae263

■ 【Date.today】 今日だけの情報を取り出す方法 rails
https://qiita.com/tanaka-yu3/items/741711bd743b80eda51a

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

railsのlink

文字でリンクする

1.URLでリンクする場合
<%= link_to “次へ”, “http://www.XXXXXXXXXXXX” %>

2.パスでリンクする場合
<%= link_to "次へ", "root_path" %>

ちなみにパスの確認方法

rake routes

と打って一番左の列に _path をつけたものがパス。

画像でリンクする

<%= link_to image_tag("画像名", class:"クラス名"), パス %>

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

gem

gem

gemfileに使いたいgemを記載する。
その際 

group :development, :test do

group :development do

group :test do

のようなグループ分けがされているが、これはgemがどの環境において使われるかによって分類する。
gemはデータ量が小さくないので、本番環境では使用しないgemなどをこれらに入れる。

:development
は開発環境のみで使用するgem

:test
はテストにのみ使用するgem

:development, :test
は開発環境及びテストで使用するgem

本番環境でも使用するgemはこれらのグループの外側に入れる。

gemをgemfileに記載したらコンソールで

bundle install

するのがお決まり。

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

最初メモ

アプリを作る最初の最初

rails new アプリの名前 -d 使用するデータベース

でアプリのひな型を作る。
次に

rails db:create

コマンドでデータベースを作る。

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

Railsで投稿編集時に予め所属カテゴリにチェックをつける方法

多対多の関係のPostとCategoryをもつテーブルに置いて、Post編集時に既存のカテゴリに予めチェックボックスをつける方法。form_with model: @postの形式を使わないと{checked:}オプションを使えないので少しハマった。

前提:多対多のテーブル構成を実現する

以下を参考にテーブル構成してる前提です。ただし投稿のtitleは本記事ではnameになってます。

Railsで投稿とカテゴリの紐付け機能を実装する

collection_check_boxesの引数に{checked:}オプションを追加

前段の多対多のテーブルを作成している前提だと、@post.category_idsで所属カテゴリを配列で取得できるらしい。それに.map(&:to_param)してやることでチェックが付く。この&:to_paramというのはいつも固定で、@postcategory_idsのようにテーブルやモデルの名称によって変わるものではない。

app/views/posts/edit.html.erb
<%= f.label :category, 'カテゴリ' %>
      <%= f.collection_check_boxes(:category_ids, Category.all, :id, :name, { checked: @post.category_ids.map(&:to_param) }) do |category| %> 
        <%= category.label do %>
          <%= category.check_box %>
          <%= category.text %>
        <% end %>
      <% end %>

https://stackoverflow.com/questions/30766766/how-to-pre-populate-collection-check-boxes-in-an-edit-form

もしform_with model: @postで「undefined method 'post_path'」とエラーが出る時

form_with model: @post, url: posts_create_path doみたいな書き方をしていると{checked:}オプションは使えない。以下の回答のコメント欄にあるようにresoucesを使わないといけない。
https://teratail.com/questions/200474

本題:最終的なコード

routes.rb
Rails.application.routes.draw do
  resources :posts, only: [:new, :create, :edit, :update]
end

posts_controller.rb
class PostsController < ApplicationController

  def  new
    @post = Post.new
  end

  def create
    @post = Post.create(post_params)
    redirect_to('/posts/index')
  end

  def show
    @post = Post.find(params[:id])
  end

  def edit
    @post = Post.find(params[:id])
  end

  def update
    @post = Post.find(params[:id]).update(post_params)
    redirect_to("/posts/index")
  end

  private

  def post_params
    params.require(:post).permit(:name, category_ids: [])
  end

end
app/views/posts/edit.html.erb
<div class="main posts-index">
  <div class="container">
    <%= form_with model: @post do |f| %>

    <p>
      <%= f.label :name, 'name'%>
      <%= f.text_field :name %>
    </p>

    <p>
      <%= f.label :category, 'カテゴリ' %>
      <%= f.collection_check_boxes(:category_ids, Category.all, :id, :name, { checked: @post.category_ids.map(&:to_param) }) do |category| %> 
        <%= category.label do %>
          <%= category.check_box %>
          <%= category.text %>
        <% end %>
      <% end %>
    </p>

    <%= f.submit '変更を保存' %>
  <% end %>
  </div>
</div>

その他

Railsはマイグレーションファイルの生成やdb:migrateを少しでもミスると、特に本番デプロイ時にDBがおかしくなったりするから気をつけてね。一度でもdb:migrateしたら、もうそのマイグレーションファイルは使えないし、schemaを直接書き換えたりしてもダメだよ。

こちらも参考になりそう?
【Rails】form_with/form_forについて【入門】

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

本番環境の設定ファイルをふっ飛ばした話

はじめに

Qiita夏祭り企画参加の記事です。
テーマページ②:システム開発における過去の失敗と乗り越えた方法について共有しよう!
こちらのテーマに参加します。

アプリケーション詳細

Rails製の本番稼働しているアプリがありました。
そのアプリは.envファイルに本番で使っている環境変数が定義されていました。
.envファイルは本番の接続情報を持っているためgit管理対象外となっていました。
ローカルに環境構築をする用に.env.exampleはgit管理で用意されていました。

中身は以下のようなもの

.env.example
# Railsの設定
RAILS_ENV=development

# Bitbucket APIの設定
BITBUCKET_KEY=XXXXXXXXXXXXXXXXXX
BITBUCKET_SECRET=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Bitbucketと連携するためにAPIキーが設定されていました。
環境構築時には以下コマンドを実行してください、というのがREADMEに書いてありました。
cp .env.example .env
.env.exampleをコピーして.envファイルを作成し任意のAPIキーに上書きしてテストする、というようなもの。

何をしたか

本番環境の設定ファイルをふっ飛ばしました。
本番デプロイ時に寝ぼけていたのか
cp .env.example .env
これを実行し本番の設定ファイルがexampleに置き換わり、本番で設定していたAPIキーが分からなくなりました。
デプロイ作業中は気付かず、デプロイした部分の動作確認をして「なんか動かないなー」となってハッと気が付き、血の気が引きました。
このアプリケーションは元々自分が作ったものではなく、どのユーザーの何のAPIキーを使っていたのかも知りませんでした。
当時本アプリを作った担当者はもう退職済みで確認も取れない状況でした。

解決方法

過去の情報を必死に引っ張り出しましたがどこにも情報は残っていませんでした。
必死に調査したところ、うまく動いているところと動いていないところがあることが発覚。
本アプリはwebとバッチに分かれており、バッチの方のみ再起動したため、バッチの方は上書きしてしまった.envを参照しwebの方は上書き前の.envを参照しているようでした。
webのソースの方にログを吐く処理を追加しダンプ

p ENV['BITBUCKET_KEY']
p ENV['BITBUCKET_SECRET']

そうしたところ前の.envの設定値が出力された。
私は急いでそれをメモし.envファイルに適用、バッチの再起動を実施。
無事にバッチも動き出しました。

おわりに

教訓: 寝ぼけながらデプロイしない

やってしまったとしても焦らず解決策を探す。
今回の場合は仮に前の値が見つからなかったとしても、新しくbotユーザーを作成しそのユーザーのAPIキーを設定すればうまくいくだろうし、必ずしも復旧させる必要はなかったかもしれません。
肝が冷えたお話でした。

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

インクリメンタルサーチ

はじめに・目的

  • search controllerのhtml viewでの検索機能だと、ワード全て入力して検索しないといけない。
  • jsonを使ったインクリメンタルサーチだと、非同期で検索可能。
  • テーブル内で検索が頻繁に行われるカラムにインデックスを設定することで検索の高速化を図る。
  • 今回はtweetsテーブルのtextカラムにインデックスを貼ることで、データの検索を高速化する。
terminal
$ rails g migration AddIndexToTweets
マイグレーションファイル
class AddIndexToTweets < ActiveRecord::Migration
  def change
    add_index :tweets, :text, length: 32
  end
end

→migrate実行

1.ルーティングなどAPI側の準備

Formatごとの条件分岐

app/controlers/tweets_controller.rb
def search
    @tweets = Tweet.search(params[:keyword])
    respond_to do |format|
      format.html
      format.json
    end
end
#投稿情報を取得したら、jbuilderを使ってJavaScript側に返す。
#検索結果は、複数の投稿情報を表示させます。
#そのため、複数の投稿情報が格納された配列を返すようなjbuilderの記述にする必要があります

json.jbuilderの作成

image.png

app/views/tweets/search.json.jbuilder
json.array! @tweets do |tweet|
  json.id tweet.id
  json.text tweet.text
  json.image tweet.image
  json.user_id tweet.user_id
  json.nickname tweet.user.nickname
  json.user_sign_in current_user
end

JSON形式のデータを配列で返したい場合は、上記のようにarray!を使用。

2. テキストフィールドを作成

views/tweets/index.html.erb
<%= form_with(url: search_tweets_path, local: true, method: :get, class: "search-form") do |form| %>
  <%= form.text_field :keyword, placeholder: "投稿を検索する", class: "search-input" %>
  <%= form.submit "検索", class: "search-btn" %>
<% end %>

3.テキストフィールドに文字入力するたびに、イベント発火

  • search.js File作成 image.png
app/assets/javascripts/search.js
$(function() {
  $(".search-input").on("keyup", function() {
    var input = $(".search-input").val();
  });
});
//キーを離したら処理をさせたいときはkeyupメソッドを使用

4.イベント時に非同期通信できるよう$.ajaxメソッドの記載

app/assets/javascripts/search.js
$(function() {
  $(".search-input").on("keyup", function() {
    var input = $(".search-input").val();
    $.ajax({
      type: 'GET',              //HTTPメソッドはGET
      url: '/tweets/search',    //rails routesで確認
      data: { keyword: input },
      dataType: 'json'
    })
  });
});

5.非同期通信の結果を得て、HTMLを作成

app/assets/javascripts/search.js
     .done(function(tweets) {
      search_list.empty();
      if (tweets.length !== 0) {        //tweetsの数が0でない時、
        tweets.forEach(function(tweet){ //forEachメソッド
          appendTweet(tweet);
        });
      }
      else {
        appendErrMsgToHTML("一致するツイートがありません");
      }
    })
    .fail(function() {    //error時のaleart実装
      alert('error');
    });
  });
});
  • forEachは、与えられた関数を配列に含まれる各要素に対して一度ずつ呼び出します
search.js変数・メソッド定義
  var search_list = $(".contents.row");

  function appendTweet(tweet) {
    if(tweet.user_sign_in && tweet.user_sign_in.id == tweet.user_id){
      var current_user = `<li>
                            <a href="/tweets/${tweet.id}/edit" data-method="get" >編集</a>
                          </li>
                          <li>
                            <a href="/tweets/${tweet.id}" data-method="delete" >削除</a>
                          </li>`
    } else {
      var current_user = ""
    }

    var html = `<div class="content_post" style="background-image: url(${tweet.image});">
                  <div class="more">
                    <span><img src="/assets/arrow_top.png"></span>
                    <ul class="more_list">
                      <li>
                        <a href="/tweets/${tweet.id}" data-method="get" >詳細</a>
                      </li>
                      ${current_user}
                    </ul>
                  </div>
                  <p>${tweet.text}</p><br>
                  <span class="name">
                    <a href="/users/${tweet.user_id}">
                      <span>投稿者</span>${tweet.nickname}
                    </a>
                  </span>
                </div>`
    search_list.append(html);
  }

  function appendErrMsgToHTML(msg) {
    var html = `<div class='name'>${ msg }</div>`
    search_list.append(html);
  }

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