20190915のRailsに関する記事は21件です。

Rubyにおけるクラス/モジュール定義関連の仕組みと、Ruby on Railsにおけるautoloadの仕組み

Ruby における話

モジュールとクラスの違い

モジュールとクラスの違いとして次の項目が挙げられます。

  • どちらもクラスインスタンス(またはモジュールインスタンス)を作れる
  • どちらもメソッドを定義できる
  • モジュールはオブジェクトを作れない
  • クラスはオブジェクトを作れる
irb(main):001:0> module Parent
irb(main):002:1>   class Child
irb(main):003:2>   end
irb(main):004:1> end
=> nil

# どちらもクラスインスタンスを作れる
irb(main):005:0> Parent.class
=> Module
irb(main):006:0> Parent::Child.class
=> Class

# モジュールはオブジェクトを作れない
irb(main):008:0> Parent.new
Traceback (most recent call last):
        2: from /home/vagrant/.rbenv/versions/2.5.5/bin/irb:11:in `<main>'
        1: from (irb):8
NoMethodError (undefined method `new' for Parent:Module)
# クラスはオブジェクトを作れる
irb(main):009:0> Parent::Child.new
=> #<Parent::Child:0x00005587aeab9c90>

モジュールとクラスが定義されると Object に定数が追加される

モジュールやクラスが定義済であるかどうかを判別する方法として defined? メソッドがあります。

未定義の場合は defined?nil を返し、定義されていると "constant" を返します。

# モジュールが未定義の場合、defined? は nil を返し、定義済の場合は "constant" を返す
$ irb
irb(main):001:0> defined?(Hoge)
=> nil
irb(main):002:0> module Hoge; end
=> nil
irb(main):003:0> defined?(Hoge)
=> "constant"

# クラスの場合も同様
$ irb
irb(main):001:0> class Hoge; end
=> nil
irb(main):002:0> defined?(Hoge)
=> "constant"

ここで、定義済の場合に "constant" が返ることと関連がありますが、モジュールとクラスが定義された時に ruby 内部では Object クラスインスタンスに定数(constant) が追加され、定義されたモジュールまたはクラスのクラスインスタンスが値として格納されます。

# モジュールが定義されると Object クラスインスタンスに定数が追加され、クラスインスタンスが値として格納される
$ irb
irb(main):001:0> Object.const_get("Hoge")
Traceback (most recent call last):
        2: from /home/vagrant/.rbenv/versions/2.5.5/bin/irb:11:in `<main>'
        1: from (irb):1
NameError (uninitialized constant Hoge)
irb(main):002:0> module Hoge; end
=> nil
irb(main):003:0> Object.const_get("Hoge")
=> Hoge
irb(main):004:0> Object.const_get("Hoge").class
=> Module

# クラスの場合も同様
$ irb
irb(main):001:0> class Hoge; end
=> nil
irb(main):002:0> Object.const_get("Hoge")
=> Hoge
irb(main):003:0> Object.const_get("Hoge").class
=> Class

クラスやモジュールがネストされた場合は :: が繋がった名前になります。
また Object クラスインスタンスの他に、子となるクラスやモジュールが定義されると、親となるクラスインスタンスにも定数として定義されます。

irb(main):001:0> module Parent
irb(main):002:1>   class Child
irb(main):003:2>   end
irb(main):004:1> end
=> nil
irb(main):005:0> defined?(Parent::Child)
=> "constant"
irb(main):006:0> Object.const_get('Parent::Child')
=> Parent::Child
irb(main):007:0> Parent.const_get('Child')
=> Parent::Child

このようにモジュールやクラスが定義されると Object クラスインスタンスに定数が追加され、defined? メソッドや Object.const_get("Hoge") を実行することで定義済であるかどうかを確認することが出来ます。

  • クラスやモジュールが未定義である場合
    • defined?nil を返す
    • Object.const_get は NameError となる
  • クラスやモジュールが定義されている場合
    • defined?"constant" を返す
    • Object.const_get がクラスインスタンスを返す

require と load の違い

require は外部ファイルに記載された ruby コードを読み込むことが出来るメソッドです。(module メソッドは Kernel モジュールで定義されています(参考))

クラスやモジュールが定義されている場合は、require を実行することで定義されたものが使えるようになります。
require するファイルに p メソッド等の、文字列を出力するコードが書かれている場合は実行されます。

print.rb
class Print
end

p "print.rb"
$ irb
irb(main):001:0> Print
Traceback (most recent call last):
        2: from /home/vagrant/.rbenv/versions/2.5.5/bin/irb:11:in `<main>'
        1: from (irb):1
NameError (uninitialized constant Print)
irb(main):002:0> require './print.rb'
"print.rb"
=> true
irb(main):003:0> Print
=> Print

require により同じファイルが 2 度読み込まれることはありません。
これはグローバル変数 $LOAD_PATH(又は $"$-I) に読み込んだファイルが保存され、読み込み済みかどうかを判定しているためです。
また、require に指定されたファイルはグローバル変数 $: の配列に書かれた順に探索され、最初に見つかったファイルが読み込まれます。

# require で読み込まれたファイルのパスはグローバル変数 $" に保存される
irb(main):007:0> pp $".select {|p| p =~ %r{/print.rb\Z}}
[]
=> []
irb(main):008:0> require './print.rb'
"print.rb"
=> true
irb(main):009:0> pp $".select {|p| p =~ %r{/print.rb\Z}}
["/<PATH_TO_CURRENT_DIRECTORY>/print.rb"]
=> ["/<PATH_TO_CURRENT_DIRECTORY>/print.rb"]

# require に指定したファイル名はグローバル変数 $: で指定されたリストから順に探索される
irb(main):002:0> pp $:
["/home/vagrant/.rbenv/rbenv.d/exec/gem-rehash",
 "/home/vagrant/.rbenv/versions/2.5.5/lib/ruby/gems/2.5.0/gems/did_you_mean-1.2.0/lib",
 "/home/vagrant/.rbenv/versions/2.5.5/lib/ruby/site_ruby/2.5.0",
 "/home/vagrant/.rbenv/versions/2.5.5/lib/ruby/site_ruby/2.5.0/x86_64-linux",
 "/home/vagrant/.rbenv/versions/2.5.5/lib/ruby/site_ruby",
 "/home/vagrant/.rbenv/versions/2.5.5/lib/ruby/vendor_ruby/2.5.0",
 "/home/vagrant/.rbenv/versions/2.5.5/lib/ruby/vendor_ruby/2.5.0/x86_64-linux",
 "/home/vagrant/.rbenv/versions/2.5.5/lib/ruby/vendor_ruby",
 "/home/vagrant/.rbenv/versions/2.5.5/lib/ruby/2.5.0",
 "/home/vagrant/.rbenv/versions/2.5.5/lib/ruby/2.5.0/x86_64-linux"]
=> ["/home/vagrant/.rbenv/rbenv.d/exec/gem-rehash", ...]
# グローバル変数 $: にカレントディレクトリは無いため、ファイル名だけでは読み込めない
irb(main):010:0> require 'print.rb'
Traceback (most recent call last):
        4: from /home/vagrant/.rbenv/versions/2.5.5/bin/irb:11:in `<main>'
        3: from (irb):10
        2: from /home/vagrant/.rbenv/versions/2.5.5/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require'
        1: from /home/vagrant/.rbenv/versions/2.5.5/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require'
LoadError (cannot load such file -- print.rb)

一方で、 load は何度実行してもファイルが読み込まれます。
また load に指定したファイル名は拡張子 .rb.so が補完されず、読み込んだファイルのパスは $" に追加されません。

require メソッドはライブラリの読み込みに使われ、load は設定ファイルの読み込みに使われることが想定されています。

my_class.rb
class MyClass
  def self.hello
    p 'hello'
  end
end
irb(main):001:0> load './my_class.rb'
=> true
irb(main):003:0> load './my_class.rb'
=> true
irb(main):004:0> MyClass.hello
"hello"
=> "hello"

# ここで my_class.rb が更新された場合は load により変更が反映される
#   my_class.rb の更新内容は self.hello メソッドが返す文字列が "hello" -> "hello world" に変わったことです
irb(main):006:0> load './my_class.rb'
=> true
irb(main):007:0> MyClass.hello
"hello world"
=> "hello world"

# `load` により読み込んだファイルのパスは `$"` に追加されない
$ irb
irb(main):001:0> load './my_class.rb'
=> true
irb(main):002:0> $".select {|p| p =~ /my_class.rb\Z/}
=> []
irb(main):003:0> require './my_class.rb'
=> true
irb(main):004:0> $".select {|p| p =~ /my_class.rb\Z/}
=> ["/<PATH_TO_CURRENT_DIRECTORY>/my_class.rb"]

このように requireload メソッドを使うことで外部ファイルを読み込むことが出来ますが次のような違いがあります。

  • requireload もグローバル変数 `$:" に指定されたパスの配列から順に探索する
  • require はライブラリの読み込み、load は設定ファイルの読み込みに使うよう想定されている
    • require で指定したファイルは拡張子 .rb.so が補完される
    • laod で指定したファイルは拡張子は補完されない
  • require は同じファイルを 1 度だけ読み込む
    • 既に読み込んだファイルはグローバル変数 $" に保存される
  • load は無条件に読み込む
    • 読み込んだファイルはグローバル変数 $" に保存されない

Ruby on Rails における話

bundle したファイルが require される仕組み

参考として、Rails が Bundler でインストールした gem を require せずに使えている仕組みを説明します。

app/config/application.rb にその処理が書かれています。

app/config/application.rb
Bundler.require(*Rails.groups)

Rails.groups は RAILS_ENV により以下のように設定されます。

# RAILS_ENV=development の場合
irb(main):009:0> Rails.groups
=> [:default, "development"]

# RAILS_ENV=production の場合
irb(main):002:0> Rails.groups
=> [:default, "production"]

尚、Gemfile に require: false が指定された場合は Bundler.require により require する対象から外れます。(参照)

autoload の仕組み

Rails6 から自動読み込みの新しいモード Zeitwerk が追加されました。
ここでは Rails6 より前の Classic モードにおける自動読み込み(参照
)について説明します。

autoload とは Ruby on Rails におけるソースコードに変更が加えられた時に自動読み込みする機能です。

例えば User モデルが app/models/user.rb で定義されている場合、require を指定せずに Rails アプリケーション内で User を呼び出すことが出来ます。

$ bin/rails c
# 未定義である状態で `User` モデルを使おうとすると、定義が自動で読み込まれて定義済となる
irb(main):001:0> Object.constants.select{|p| p == :User}
=> []
irb(main):002:0> User.column_names
=> ["id", "name", "created_at", "updated_at"]
irb(main):003:0> Object.constants.select{|p| p == :User}
=> [:User]

# グローバル変数 $" にパスは追加されない
irb(main):004:0> $".select{|p| p =~ /user.rb\Z/}
=> []

この自動読み込みは development 環境ではデフォルト有効になっており、production 環境では無効化されています。(Rails5 でのデフォルト設定。Rails4 でのデフォルト設定は異なるようです。)

自動読み込みする対象となるファイルは config.autoload_paths に格納されています。
これは require で使う $LOAD_PATH とは異なる値が格納されています。

デフォルトで config.autoload_paths に設定されているのは次のディレクトリです。(Rails5 でのデフォルト設定)

  • app 配下の第1ディレクトリ
  • app/*/concerns ディレクトリ
  • test/mailer/previews ディレクトリ

Rails における自動読み込み処理の順序は次のとおりです。

尚、Rails における autoload ではクラスインスタンスやモジュールインスタンスは定数として取り扱います。
そのため以降の自動読み込みの説明においてもクラスインスタンスやモジュールインスタンスは定数として説明します。
(定数に対して autoload が働くため、クラスやモジュールではない一般的な定数も同じ仕組みで autoload されます)

  1. Ruby インタプリタにおける定数が未定義の場合に発生する const_missing をフックして Rails における自動読み込みをトリガーする
    • ruby インタプリタは module, class キーワードの後ろに置かれる定数が未定義であれば定義を行う
    • ruby インタプリタにより定義済と判定された定数は Rails の自動読み込みは行われない
  2. 呼び出し元のモジュール名を基準として、定数の名前を特定する
    • 無名(self.name == nil)のモジュールで定義された定数の場合は、呼び出し元のモジュールは "Object" となる
    • 定数が登場した箇所のネストに基づく名前となる
  3. 定数の名前からファイル名を特定する (定数とファイル名は 定数名.underscore == ファイル名 の関係)
  4. ファイルが見つかった場合、load 又は require によりファイルが読み込まれる
    • ENV["NO_RELOAD"] が指定されている場合は require が使われ、それ以外は laod が使われる
  5. ファイルが見つからないが、自動読み込み対象のモジュールである場合は該当する名前のモジュールを定義する
    • 自動読み込み対象のモジュールは autoload_paths 配下にあるディレクトリを指す
    • 例えば、app/my_dir/my_class.rb がある場合 MyDir という定数は自動読み込み対象のモジュールとして、Rails の自動読み込みによりモジュールとして定義される
  6. ファイルが見つからず、自動読み込み対象のモジュールでもない場合は、親となるモジュールを基準として読み込み処理を繰り返す(上の2.から実行する)

参考: autoload は AutoSupport::Dependencies にて const_missing メソッドをオーバーライドすることで実装されています。

例えばアプリケーションに app/models/user.rb が存在する場合に、コントローラ内で User を指定すると自動読み込みがどの順番で行われるか見ていきます。

app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def index
    @posts = User.current_user.posts
  end
end

上の例で定数 User が自動読み込みされる場合を考えてみます。
すると、次の順序で自動読み込みが行われます。

  1. Ruby インタプリタは Object クラスインスタンスに User が存在しないとして const_missing を呼び出す
  2. Rails が const_missing をフックして自動読み込みをトリガーする
  3. 呼び出し元のモジュール名を基準として、定数の名前を特定する
    • 定数が登場した箇所のネストに基づく名前となる
      • PostsController::User
  4. 定数の名前からファイル名を特定する (定数とファイル名は 定数名.underscore == ファイル名 の関係)
    • posts_controller/user.rb を autoload_paths から探す
  5. ファイルが見つからず、自動読み込み対象のモジュールでもない場合は、親となるモジュールを基準として読み込み処理を繰り返す
    • ::User
  6. 定数の名前からファイル名を特定する (定数とファイル名は 定数名.underscore == ファイル名 の関係)
    • user.rb を autoload_paths から探す
  7. ファイルが見つかった場合、load 又は require によりファイルが読み込まれる
    • app/models/user.rb が見つかり load される

以上が Rails における自動読み込みの仕組みです。

  • Rails における自動読み込み(autoload)は ActiveSupport::Dependencies にて実装されている
  • autoload の対象となるファイルは config.autoload_paths から検索できるものである
  • ファイルは load により読み込まれる (require は環境変数を指定した時に使われる)
  • ファイルではなくディレクトリであった場合は、自動読み込みモジュールとして定義される
  • 定数が登場した箇所のネストに基づく名前を使った検索で見つからなかった場合は、親の名前空間を基準として検索が続けられる

更新されたファイルを再度読み込む仕組み

先に説明した autoload のアルゴリズムはあくまで未定義の定数を再読み込みする仕組みです。

Rails アプリケーションを開発していて RAILS_ENV=development でアプリケーションを動作させた時に、ファイルが更新されると自動で変更が反映されていることに気が付くと思います。

これは Rails アプリケーションのファイル更新をウォッチする config.file_watcher により、ファイルが更新されたことをトリガーとして、autoload されたファイル一覧が空に初期化され、定数の削除が行われることで実現します。

つまり、定義済の状態を強制的に未定義の状態にすることで、次に定数が読み込まれるタイミングで autoload が実行されることが期待され、autoload により最新のファイルが読み込まれることで実現されています。

reload! の仕組み

reload! は定数の再読み込みを行うメソッドです。

development モードで動作する Rails アプリケーションは、ファイルが変更されるとクラスやモジュールを自動的に再読み込みします。(config.cache_classes が false なら自動読み込みが有効になり、config.cache_classes が true なら自動読み込みが無効になる)

しかし Rails console では逆に一貫した状態であることが望まれるため、config.cache_classes の値によらず再読み込みが行われません。

そこで reload! メソッドを実行することで強制的に再読み込みすることが出来ます。

これは Rails の内部で autoload されたファイル一覧が空に初期化され、また各クラスインスタンスに登録された定数が削除されるため強制的に再読み込みが出来ます。

注意点として既に変数にクラスインスタンスが代入されている場合、再読み込みが行われても変数内の状態は更新されません。

app/models/user.rb
class User < ApplicationRecord
  def self.num
    1
  end
end
irb(main):001:0> user = User
=> User (call 'User.connection' to establish a connection)
irb(main):002:0> user.num
=> 1

# ここで、User.num が 2 を返すように修正する
irb(main):003:0> reload!
Reloading...
=> true

# 既に変数に保存されたクラスインスタンスは変更されない
irb(main):004:0> user.num
=> 1
# 新たに参照されたクラスインスタンスは変更される
irb(main):005:0> User.num
=> 2
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS secrets.ymlからcredentials.ymlへ

目次

  1. はじめに
  2. secrets.ymlとは?
  3. credentials.ymlとは?
  4. 具体的な変更
  5. 変更によって変わった手順
  6. 参考にしたwebサイト、書籍
  7. 参考になりそうなwebサイト、書籍

はじめに

私は、プログラミングをの勉強を初めてまだ2ヶ月の初心者であります。自分のアウトプットも兼ねてまだまだ技術は未熟ではありますが、私以外の初めて間もない方々の役に立つようなことができればと思いまして、投稿をさせていただきます。不備等ございましたら、恐れ入りますがコメントにてご指摘いただければ幸いでございます。

secrets.ymlとは?

Rails5.2より前のバージョンにおいて、秘匿情報(secret_key_baseや外部APIのアクセスキー等)を記述していたファイルのことです。このファイルにはデフォルトでアプリケーションのsecret_key_baseが含まれていました。

credentials.yml.encとは?

Rails5.2以降のバージョンにおいて用意された秘匿情報を保存しておくための新しいファイルです。Rails5.2からは暗号化されたcredentials.yml.encを扱います。しかし、secret.ymlも使うことはできるので、アップグレードの際にすぐに用意しなければならないというわけではありません。
また、credentials.yml.encの内容は暗号化されています。復号のやり方としては、master keyを利用して復号化することができます。デフォルトのmaster keyはconfig/master.keyを参照しましょう。編集の際は、rails credentials:editコマンドを使います。Rails5.2において生成される.gitignoreには、.config/master.keyが標準で含まれているので、gitリポジトリに誤って入れてしまうのを防ぐことができるようになっています。

具体的な変更点

  • config/credentials.yml.encを追加し、secrets.ymlの記述の際のように、各環境ごとに設定値をそれぞれ分ける仕様ではなくなった
  • 開発やテストのシーン等、秘匿情報がの復号化の必要ない場面では求められなくなった 以上の2点が大きく変わった点かと思われます。

参考にしたwebサイト

AWS利用上のセキュリティを確保する方法
【Rails5.2】credentials.yml.encとmaster.keyでのデプロイによる今までとの変更点
Rails5.2から導入されたcredentials.yml.encを極める
Rails5.2から追加された credentials.yml.enc のキホン
Rails5.2からsecrets.yml*が廃止されcredentials.yml.encに統合されるよ
credentials.yml.encによるアクセスキーの管理方法
Rails セキュリティガイド
「Rails5.2」 credentials.yml.encを生成する方法
Railsのcredentials.yml.encは、どういった運用ができるか?
credentials.yml.encを環境ごとに使い分ける【Ruby on Rails】
Rails5.2 暗号化(Credentials)関連の単語メモ
Rails 5.2 の credentials.yml.enc に登録してあるデータを呼び出す方法
Rails の config/credentials.yml.enc を使ってみる
Rails5.2のcredential管理を試してみた
credentials.yml.enc やめたい…
Rails 4.1 の secret.yml とは…
Rails 5.2の本番デプロイ時に secrets.yml でハマった
secrets.ymlや環境変数をRails 5.2のEncrypted Credentialsに移行する

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

【Rails】Turbolinksが原因でGoogle Analyticsが正常に動かない時の対処法

Ruby on RailsでWebアプリを作っていたら、Turbolinksでどハマりしてしまったのでメモ。

Google AnalyticsがTurbolinksの影響で正常に動かない場合の対処法を備忘録としてまとめてみました。

Rails5で作ったWebアプリのGoogle Analyticsが正常に動かない

力価計算.comという小児薬の服用量を計算するWebアプリを作ったのですが、Google Analyticsが正常に動いてないことに気づきました。

ページ遷移したときにGoogle AnalyticsのPVがカウントされない

Webアプリ上でページ遷移した時に、PVのカウントがされていない。

例えば、トップページから計算ページへに移動するとき、トップページのPVは測定できるのに計算ページのPVが測れていませんでした。

Google Analyticsが正常に動かなかった原因はTurbolinks

Google Analyticsが正常に動かなかった原因は、Turbolinksだったことが発覚。

Turbolinksはページの表示速度が早くなるというメリットがありますが、どうやらJavascriptの動きを邪魔してしまうことがあるらしい。

Google AnalyticsのコードはJavascriptで書かれているので、TurbolinksがGoogle Analyticsのコードの動きを邪魔してしまったようでした。

Turbolinksが原因でGoogle Analyticsが正常に動かない場合の対処法

解決法は、以下の通りです。

1:application.html.erbにGoogle Analyticsのコードを貼り付ける

まず以下のコードをapplication.html.erbにコピペ。

application.html.erb
<% if Rails.env.production? %>
 <!-- Global site tag (gtag.js) - Google Analytics -->
  <script async src="https://www.googletagmanager.com/gtag/js?id=#{ENV['Your Google 
  Analytics Tracking Code']}"></script>
  <script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('Your Google Analytics Tracking Code');
 </script>
<% end %>

2:application.jsにturbolinks対策のコードを貼り付け

次に、以下のコードをapplication.jsに貼り付けます。

Your Google Analytics Tracking Codeには、自分のトラッキングコード(UA-xxxxxxxx-xxx)を書いてください。

application.html.js
document.addEventListener('turbolinks:load', function(event){
  if(typeof(gtag) == 'function'){
    gtag('config', 'Your Google Analytics Tracking Code', {
      'page_title' : event.target.title,
      'page_path': event.data.url.replace(window.location.protocol + "//" + window.location.hostname, "")
    });
  }
})

参照:https://github.com/turbolinks/turbolinks/issues/73

これで解決するはず。

ちなみに最初の行にあるturbolinks:load は、Turbolinksによってページが表示されるタイミングで発生するイベントです。

これでも動かなかった場合

実は僕の場合、これでも動かなかったのですが、rails assets:precompileのコマンドをターミナルで打ち込んだら正常に動くようになりました。

あと、最初はgoogle-analytics-railsというgemでGoogleアナリティクスを導入していたのですが、どうしても正常に動きませんでした。

なので、google-analytics-railsを使っていて、Google アナリティクスが動かない場合は、先ほど紹介したような感じで普通にGoogle Analyticsのトラッキングコードを貼り付けると良いと思います。

まとめ

Turbolinksは便利だけど厄介ですね。

少しずつ勉強していこうとと思います。

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

Railsチュートリアル2章

Gem

railsにはgemファイルというライブラリがある。

Gemfile内に使用したいgemを記述、開発環境用などグループ分けすることも可能らしい。

記述しただけでは意味がないのでインストールを行う。

$ bundle install --without production

bundleに記述した通りにインストールが実行される。--withoutを使うと上記だと
production(本番環境)のgemを除いたローカルgemがインストールされます。

数が多かったりするとうまく動かない。(エラーになる)
その時は

$ bundle update 

を行う。

バリデーション

app/models/micropost.rb
class Micropost < ApplicationRecord
validates :content, length: {maximum: 140}
end

バリデーションというのは

text入力内容や記述内容が要件を満たしているか、妥当性を確認すること

上記では
:content、投稿する内容の
:length長さが
{maximum: 140}最大140文字
というバリデーションが記述されています。

投稿する内容が140文字を越えるとエラーメッセージが表示されます。

他にもMVCなどについて詳しく書いてあるのですが
ほぼコピペになってしまいそうなので以上です。。。

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

Railsでチーム内共通の定数を利用する方法

Railsでチーム共通の定数を管理する

システムを作成していると、開発チームで共通の定数などを利用したいことがあると思います。

各画面共通の値やパラメーター等を共通で利用できれば、「あの画面では”1”の値が印刷済なのに、こっちの画面では”1”の値が印刷済」になっているとかいう状態を防ぐことができます。

もちろん、DBから値を取ってくるということもできますが、「チリも積もれば山となる」
極力DBへ負荷を避けるという利点もありますね:start:

コンスト用のファイルを用意

config/initializer/constants.rbに次のようなファイルを用意します。

#constants.rb
#サーバーサイドを通さず、クライアントサイド側で利用したい定数は別途jsファイルなどにまとめましょう

module CommonConst

    ## クライアント名
    CLIENT_NAME = "○○社総合人事システム"
    ## 業務区分
    GYOMU_KBN = {
        "1" => "給与管理",
        "2" => "庶務事務管理",
        "3" => "勤怠管理"
    }.freeze

end

呼び出し方

例えば、viewファイルで次のように呼び出すことができます。

<nav class="navbar navbar-default">
    <div class="container-fluid">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbarEexample">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
      </button>
      <!-- ↓ここ共通定数の呼び出し -->
            <a class="navbar-brand" href="/"><%= CommonConst::CLIENT_NAME %>総合人事システム</a>
        </div>

        <div class="collapse navbar-collapse" id="navbarEexample">
            <ul class="nav navbar-nav">
                <li class="active"><a href="#">メニューA</a></li>

            </ul>
        </div>
    </div>
</nav>

また、共通定数を利用した、セレクトボックスやラジオボタンの作成など、フォームコントロールを作成することもできます。

<h1 class="text-center">チームで決まったコンストを呼び出そう</h1>

<div class="form-group">
    <div>
        <%= label_tag '業務区分',nil,class:"label label-default" %>
        <%= select_tag('select_gyomu',options_for_select((CommonConst::GYOMU_KBN.invert),class:'option form-control',selected:"1"),include_blank:true)%>
    </div>
</div>

↓が呼び出し結果の画像です。

ナビゲーションバーの、
「○○社総合人事システム」と、「給与管理」という項目が出ているセレクトボックスが共通定数から利用しています。(値は適当です)

このように、Railsでも結構簡単に、定数を利用できます。
とはいえ、Rubyにおける定数は取り扱いを注意しないと、値を変えられたり、参照先を変えることができるので、注意が必要です

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

Railsチュートリアル1章

railsチュートリアルを始めたので復習

$ rails server

ローカル環境にサーバーが立ち上がる

$ rails _5.1.6_ new hello_app

railsのバージョン5.1.6に固定しhello_appを作成。

Git

$ git init

gitのセットアップ。リポジトリの初期化。
rails new hello_appしたならhello_appのルートディレクトリで行う。
hello_appのファイルを入れる貯蔵庫を作りますよ〜的な感じ。

$ git add -A

現在のディレクトリにあるファイルが全てリポジトリに追加される。
貯蔵庫にファイルを入れますよ〜。

$ git log         #gitのログを表示
$ git status      #gitのステータスを表示
$ git commit -m "コメント"

最後にコミットします。 -mを使うとダブルクォーテーション内にコミットメッセージを
追加できます。-mを使用しなければエディタが開いて入力する形になります。
addで追加はしたけどリポジトリに反映がされていない状態?なのでcommitが必要になります!

リモートリポジトリの追加

$ git remote add 追加するリモートリポジトリ名 追加したいリポジトリ
$ git remote add origin git@bitbucket.org:ユーザー名/hello_app.git

bitbucketの場合はbitbucket内でリポジトリを作成し、リポジトリのソースの上の方に
リポジトリが表記されているのでコピペして上記のようにできればOKです。

$ git push -u origin --all

最後にpushを行うことでリモートリポジトリに追加されます!

Heroku

$ Heroku create 

herokuのサーバーにアプリケーションの実行場所を作成します。
ここに作成したアプリケーションをpushします。
この時生成されるアドレスをブラウザで開くことでデプロイしたアプリケーションを
表示できます。

$ git push Heroku master

gitでcommitされているリポジトリのmaster(ブランチ)をherokuにpushします。
これでデプロイ完了です。

HerokuではデータベースにSQLiteが使えないのでPostgreSQLを使用します。
なのでgemfileの書き換えなどで対処する必要があります。
開発環境(development)と本番環境(production)でgemの書き換えをします。

第1章の内容を第5章終了時に復習を兼ねて記載しました。
認識が違う部分がまだまだあるとは思いますが何かあればコメントなどお願い致します!

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

#Rails + shoryuken gem で Worker を起動するときは --rails フラグをつけるべし? ActiveJob の perform が実行されなくてハマった。 Workerは動いているのに。

--rails フラグをつけて worker を起動したら、ついに処理されるようになった。

bundle exec shoryuken --config config/shoryuken.yml --rails

Help

shoryuken --help

shoryuken [options]
    -c, --concurrency INT            Processor threads to use
    -d, --daemon                     Daemonize process
    -q, --queue QUEUE[,WEIGHT]...    Queues to process with optional weights
    -r, --require [PATH|DIR]         Location of the worker
    -C, --config PATH                Path to YAML config file
    -R, --rails                      Attempts to load the containing Rails project
    -L, --logfile PATH               Path to writable logfile
    -P, --pidfile PATH               Path to pidfile
    -v, --verbose                    Print more verbose output
    -V, --version                    Print version and exit
    -h, --help                       Show help

https://www.rubydoc.info/github/phstc/shoryuken

shoryuken.yml

aws:
  access_key_id:      <%= ENV['AWS_ACCESS_KEY_ID'] %>
  secret_access_key:  <%= ENV['AWS_SECRET_ACCESS_KEY'] %>
  region:             us-east-1
concurrency: 1
queues:
  - example1
  - example2
  - example3


shoryuken_worker.rb

class ShoryukenWorker
  include Shoryuken::Worker

  shoryuken_options queue: 'example1', auto_delete: true
  shoryuken_options queue: 'example2', auto_delete: true
  shoryuken_options queue: 'example3', auto_delete: true

  def perform(sqs_msg, name)
    puts "Hello, #{name}"
  end
end

initializers/shoryuken.rb

Shoryuken.configure_server do |config|
  # Replace Rails logger so messages are logged wherever Shoryuken is logging
  # Note: this entire block is only run by the processor, so we don't overwrite
  #       the logger when the app is running as usual.

  Rails.logger = Shoryuken::Logging.logger
  Rails.logger.level = :info

  # config.server_middleware do |chain|
  #  chain.add Shoryuken::MyMiddleware
  # end

  # For dynamically adding queues prefixed by Rails.env
  # Shoryuken.add_group('default', 25)
  # %w(queue1 queue2).each do |name|
  #   Shoryuken.add_queue("#{Rails.env}_#{name}", 1, 'default')
  # end
end

# config/initializers/shoryuken.rb
Shoryuken.active_job_queue_name_prefixing = true

config/application.rb

require_relative 'boot'

require 'rails/all'

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

module RailsActiveJobExample
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 6.0

    config.active_job.queue_adapter = ENV['QUEUE_ADAPTER'].present? ? ENV['QUEUE_ADAPTER'].to_sym : :sidekiq
    # config.active_job.queue_adapter = :shoryuken

    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration can go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded after loading
    # the framework and any gems in your application.
  end
end

make job

QUEUE_ADAPTER=shoryuken bundle exec rails runner 'ShoryukenJob.perform_later("ABC")'

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/2448

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

promptはrenderで消える

編集画面を作る際にpromptが消失した

MVCの流れで呼び出される時

renderで呼び出す時

実際のコード

.main__product__delivery__h2-choice
  = f.collection_select :delivery_method_id, @deliveryMethods, :id, :name, prompt: "---"
  = fa_icon "chevron-down", class: "main__product__delivery__sell-box__icon"

どうやら仕様らしい…?

Select prompt option disappears when validation fails in Rails
https://stackoverflow.com/questions/4060737/select-prompt-option-disappears-when-validation-fails-in-rails
You can achieve the results you want by setting :include_blank to the string you were using for prompt.

とのことなのでpromptからinclude_blankに書き換え

.main__product__delivery__h2-choice
  = f.collection_select :delivery_method_id, @deliveryMethods, :id, :name, include_blank: "---", 
  = fa_icon "chevron-down", class: "main__product__delivery__sell-box__icon"

"---"自体は存在していることが確認できたのであとは"---"が初期値として選択されていればOK!

最終的なコード

RubyonRails:collection_selectにデフォルト値を設定する
https://madogiwa0124.hatenablog.com/entry/2018/05/12/210801

こちらのサイトを参考にして

.main__product__delivery__h2-choice
  = f.collection_select :delivery_method_id, @deliveryMethods, :id, :name, { include_blank: "---", selected:"" }
  = fa_icon "chevron-down", class: "main__product__delivery__sell-box__icon"

結論

prompt: "---"を{ include_blank: "---", selected:"" }に書き換える!

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

【Rails】ActionMailer開発時にLetterOpnerWebが非常に便利だった。

はじめに

RailsでActionMailer実装時にお試しでletter_opner_webと言うのを使ってみました。
非常に手軽で便利だったので、使い方などの整理と振り返り用のメモです。
※Rails標準にも「MailerPreview」と言う機能があるみたいなのですが、テスト利用を想定して作られたものみたいだったので、こちらのgemを使うことにしました。

間違えている所などあれば、ご指摘・指南いただければ嬉しいです。
(未解決課題なんかもあり、、、)

Letter_opner_web

擬似的にメールの受信や閲覧ができる便利なgemです。
GitHubはコチラ

前提:開発環境での利用に特化
   ※テスト環境でも行けそうな気はするけど、それこそ「MailerPreview」か(・ω・)

デモのご紹介

letter_opner_webはデモサイトが用意されています。
実装前にどんなものか気になる方は触ってみてください。

デモサイト:http://letter-opener-web.herokuapp.com/

【 利用手順 】
①受信・閲覧のためのメール送信からスタート
下記画面で[Email]と[Message]を入力して[Send mail]をクリック!
※Emailは存在しないものでも問題ありません

②画面下部に成功メッセージを確認
下図のようなメッセージが出ていれば、送信に成功しています。

③letter_opner_web画面を開く
デモ画面の"Click here to see sent mails"のリンクをクリックすれば、letter_opner_webで実際に見れる画面が表示されます!
なお、EmailアドレスごとにメッセージBOXが存在するわけではなく、送信した全てのEmailアドレスのメール内容がここから確認できます。
(本番で使ったらセキュリティリスク高めな感じですね笑)

④テストメッセージ削除
確認が完了したら、念のため画面左上の[Clear]ボタンでメール削除しておきましょう。

こんな感じで、開発時にメールサーバなどを用意するのが手間だなと思ったら、まずはletter_opner_webでお試ししてみるのも良いかと^^
ありえないEmail宛のメッセージでも許容してくれるので、手軽さが増します。

設定方法(開発環境)

以下、letter_opner_webの設定方法をザクっとご説明。

Gemfile追加

gemファイルに設定を追加してbundle installを実行してください。

group :development  do 
  gem ' letter_opener_web '、'〜> 1.0 '
end

設定ファイル追加

開発環境の環境設定ファイルにメーラの設定を追加します。

config/environments/development.rb
config.action_mailer.default_url_options = {  host: 'localhost', port: 3000 }
ActionMailer::Base.delivery_method = :letter_opener

ルーティング設定

画面を持っているものなので、ルーティングの設定が必要になります。
下記のように設定追加してください。

config/routes.rb
Rails.application.routes.draw do
  # 足すのは↓の行だけです!
  mount LetterOpenerWeb::Engine, at: "/letter_opener" if Rails.env.development?
end

これでletter_opner_webの設定は終わり!
超簡単!あとは下記のアドレスにアクセス知れば確認ができます!!

letter_opner_web: http://localhost:3000/letter_opener

 ↓ 実際に開いてみるとこんな感じ ↓

動作確認

設定はできたものの出来ればメールが飛ぶか見てみたいですよね。
と言う事で、サクッとメールも飛ばしてみましょう。

ActionMailer作成

コンソールから、controller作るときと同じ要領でActionMailer作ります。
※公式ドキュメントには”SampleMailer”のように指定するように記述がありますが”Mailer"部分はジェネレートする際に勝手に付与されます。知らずにやるとSampleMailerMailerみたいに残念なフォルダやクラスが作成されるので注意です笑

 $ rails g mailer Sample
 Running via Spring preloader in process 15548
      create  app/mailers/sample_mailer.rb
      invoke  haml
      create    app/views/sample_mailer
      invoke  test_unit
      create    test/mailers/sample_mailer_test.rb
      create    test/mailers/previews/sample_mailer_preview.rb

なお、controllerと同じくrails d mailer Sampleで削除も可能です。

ActionMailerを編集する

①共通部
Application_mailers.rbは、ActionMailer全体で適用されます。
初期状態で以下のように送信元メールアドレスとメールフォームが設定されています。
もし、送信元メールアドレスを変更したいのであれば、下記のdefault from:を編集してください。
※各Mailerで個別に送信元を編集することもできるようです

app/mailers/application_mailers.rb
class ApplicationMailer < ActionMailer::Base
  default from: 'from@example.com'      # 送信元メールアドレス
  layout 'mailer'                       # メール全体に適用されるメールフォーム(Viewsのapplication.html.hamlなどと共通の原理かと)
end

②SampleActionMailer
ここで送信メソッドを定義します。メソッド名は自由に決めちゃって良いかと思います。
今回メソッド内で設定するのはメールヘッダ部分のみです。
(body部も書けますが、管理が煩雑になるので基本的には切り出すようです)

app/mailers/sample_mailer.rb
class SampleMailer < ApplicationMailer
  attr_accessor :email,:title,:name
  def sample_mail(email,title,name)      #仮引数に@付きのインスタンス変数は指定できませんので注意
    @email = email                       #インスタンス変数に格納
    @title = title                       #インスタンス変数に格納
    @name  = name                        #インスタンス変数に格納
    mail  to: @email,                    #メールの宛先を指定
          subject: "【テストメール】#{@title}" #メールのタイトルを指定
  end
end

※モデルを使用していないため、無理やり項目作っていますが、普通は@userとかかと。

メールフォーム(body部)を編集する

メールのbody部は、Viewsフォルダ配下に作成します。
rails gコマンドで実行した時点で、該当のActionMailerに対応するフォルダが、Viewsフォルダ配下に作成されています。このフォルダ配下にActionMailerで作成したメソッドと同名のファイルを作成し、body部を記述します。

記述方法は、基本的にviews作成時と同じ感じです。
(プレーンテキストとhtmlで切り替えできそうですが、htmlのみを対象としています。)

app/views/sample_mailer/sample_mail.html.haml
= "#{@name}さん"
%br
%p
  Hello World!!

設定自体はこれで完了です。
controllerなどからActionMailerを呼び出せばOK。
この部分ちょっと端折って、コンソールから検証してみます。
※今回端折りますが下図[4]のようにcontrollerなどに記載すればメール送信可能になります

コンソール.railsc
#引数を下準備
[1] pry(main)> email = "test@gmail.ccooom"
=> "test@gmail.ccooom"
[2] pry(main)> title = "Hello World"
=> "Hello World"
[3] pry(main)> name = "はむすけ"
=> "はむすけ"
#ActionMailerを実行してみる(最後に”.deliver”付けないと送信されません!!!)
[4] pry(main)> SampleMailer.sample_mail(email,title,name).deliver

実行結果を見ると、どう言う順で読み込まれてるか、何となくイメージが湧きますね。

コンソール.実行結果
Rendering sample_mailer/sample_mail.html.haml within layouts/mailer
Rendered sample_mailer/sample_mail.html.haml within layouts/mailer (9.2ms)
SampleMailer#sample_mail: processed outbound mail in 5075.2ms
Sent mail to test@gmail.ccooom (37.0ms)
Date: Sun, 15 Sep 2019 14:23:42 +0900
From: from@example.com
To: test@gmail.ccooom
Message-ID: <5d7dcade41695_3f143fbfadc14e5c744d@user-nameMacBook-Air.local.mail>
Subject: =?UTF-8?Q?=E3=80=90=E3=83=86=E3=82=B9=E3=83=88=E3=83=A1=E3=83=BC=E3=83=AB=E3=80=91Hello?=
 =?UTF-8?Q?_World?=
Mime-Version: 1.0
Content-Type: text/html;
 charset=UTF-8
Content-Transfer-Encoding: quoted-printable

<!DOCTYPE html>=0D
<html>=0D
<head>=0D
<meta content=3D'text/html; charset=3Dutf-8' http-equiv=3D'Content-Type'>=
=0D
<style>=0D
  /* Email styles need to be inline */=0D
</style>=0D
</head>=0D
<body>=0D
=E3=81=AF=E3=82=80=E3=81=99=E3=81=91=E3=81=95=E3=82=93=0D
<br>=0D
<p>=0D
Hello World!!=0D
</p>=0D
=0D
</body>=0D
</html>=0D

=> #<Mail::Message:70092500977860, Multipart: false, Headers: <Date: Sun, 15 Sep 2019 14:23:42 +0900>, <From: from@example.com>, <To: test@gmail.ccooom>, <Message-ID: <5d7dcade41695_3f143fbfadc14e5c744d@user-nameMacBook-Air.local.mail>>, <Subject: 【テストメール】Hello World>, <Mime-Version: 1.0>, <Content-Type: text/html>, <Content-Transfer-Encoding: quoted-printable>>
この結果、下記の画面が開きます。

成功はしているみたいですが、rails sしてないからletter_opner_web自体は開けません(:_;)
と言うことで、rails sしてから、適当なところでbinding.pryしてみましょう(あくまで面倒臭がる)
コンソール.binding.pry
    5: def index
 => 6:   binding.pry

[1] pry(#<PostsController>)> email = "test@gmail.ccooom"
=> "test@gmail.ccooom"
[2] pry(#<PostsController>)> title = "Hello World"
=> "Hello World"
[3] pry(#<PostsController>)> name = "はむすけ"
=> "はむすけ"
[4] pry(#<PostsController>)> SampleMailer.sample_mail(email,title,name).deliver
この状態でletter_opner_webにアクセスしてみましょう。今度はアクセスできるはず。

補足(課題込み)

letter_opner_webはプロジェクト単位でメールデータを保持しているようです。
上の図でrails cで試したメールも残っていることから、サーバ起動、停止などを繰り返しても消えることは無いようです。プロジェクトフォルダが肥大化しないよう注意が必要かなと思いました。

ちなみに送信メールの保存場所は「tmp/letter_opner/」配下です。
(デフォルトでgitignore対象となっています)
もし、保存場所を変えたい場合は、initilizerにletter_opner_web用のファイルを作成し、そこで定義すればOKみたいです。

config/initializers/letter_opner_web.rb
LetterOpenerWeb.configure do |config|
  config.letters_location = Rails.root.join('public', 'email')
  # 上記パスの中身"#<Pathname:/Users/user-name/projects/miniblog/public/mail>"
end

※当方うまく設定できてません。試したところ、こうなります。

<それぞれの挙動>
 メールデータ格納先
  →デフォルトの「tmp/letter_opner」のまま。フォルダ消しても蘇ってきます笑
 letter_opner_web参照先
  →設定が効いているのか、何も見えなくなる笑

…畜生め(TωT)!!!
と思いつつ、configの設定がうまくいっていないのか・・・?!
と思いrails sした時点で、configの設定状況を確認してみる(↓)。
うん、想定通りに読み込まれてる。指定のパスもcdで行ける。(思考停止)

console.
[1] pry(#<PostsController>)> LetterOpenerWeb.config.letters_location
=> #<Pathname:/Users/user-name/projects/miniblog/public/mail>
[2] pry(#<PostsController>)> LetterOpenerWeb.config
=> #<LetterOpenerWeb::Config:0x00007fb9d9ee2948
 @letters_location=#<Pathname:/Users/user-name/projects/miniblog/public/mail>>

ここに時間を割き過ぎるのもなので、一旦ここまで。
もう少しinitilizerとかconfigとか、理解が進めば思い返してみることもあるかも。。。

まとめ

手軽にメール機能を試せるのは、いいな〜と感じました。
ただ、色々試していると基礎知識がまだまだ足りないな〜と思い知らされます^^;
今後も、より深くrubyやrailsを知っていけるように頑張ります。。

参考

GitHub:letter_opner_web
Rails公式ガイド:Action Mailer の基礎
開発中にrailsから送信したメールを確認する

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

【Rails】ActionMailer実装時にLetterOpnerWebが非常に便利だった。

はじめに

RailsでActionMailer実装時にお試しでletter_opner_webと言うのを使ってみました。
非常に手軽で便利だったので、使い方などの整理と振り返り用のメモです。
※Rails標準にも「MailerPreview」と言う機能があるみたいなのですが、テスト利用を想定して作られたものみたいだったので、こちらのgemを使うことにしました。

間違えている所などあれば、ご指摘・指南いただければ嬉しいです。
(未解決課題なんかもあり、、、)

Letter_opner_web

擬似的にメールの受信や閲覧ができる便利なgemです。
GitHubはコチラ

前提:開発環境での利用に特化
   ※テスト環境でも行けそうな気はするけど、それこそ「MailerPreview」か(・ω・)

デモのご紹介

letter_opner_webはデモサイトが用意されています。
実装前にどんなものか気になる方は触ってみてください。

デモサイト:http://letter-opener-web.herokuapp.com/

【 利用手順 】
①受信・閲覧のためのメール送信からスタート
下記画面で[Email]と[Message]を入力して[Send mail]をクリック!
※Emailは存在しないものでも問題ありません

②画面下部に成功メッセージを確認
下図のようなメッセージが出ていれば、送信に成功しています。

③letter_opner_web画面を開く
デモ画面の"Click here to see sent mails"のリンクをクリックすれば、letter_opner_webで実際に見れる画面が表示されます!
なお、EmailアドレスごとにメッセージBOXが存在するわけではなく、送信した全てのEmailアドレスのメール内容がここから確認できます。
(本番で使ったらセキュリティリスク高めな感じですね笑)

④テストメッセージ削除
確認が完了したら、念のため画面左上の[Clear]ボタンでメール削除しておきましょう。

こんな感じで、開発時にメールサーバなどを用意するのが手間だなと思ったら、まずはletter_opner_webでお試ししてみるのも良いかと^^
ありえないEmail宛のメッセージでも許容してくれるので、手軽さが増します。

設定方法(開発環境)

以下、letter_opner_webの設定方法をザクっとご説明。

Gemfile追加

gemファイルに設定を追加してbundle installを実行してください。

group :development  do 
  gem ' letter_opener_web '、'〜> 1.0 '
end

設定ファイル追加

開発環境の環境設定ファイルにメーラの設定を追加します。

config/environments/development.rb
config.action_mailer.default_url_options = {  host: 'localhost', port: 3000 }
ActionMailer::Base.delivery_method = :letter_opener

ルーティング設定

画面を持っているものなので、ルーティングの設定が必要になります。
下記のように設定追加してください。

config/routes.rb
Rails.application.routes.draw do
  # 足すのは↓の行だけです!
  mount LetterOpenerWeb::Engine, at: "/letter_opener" if Rails.env.development?
end

これでletter_opner_webの設定は終わり!
超簡単!あとは下記のアドレスにアクセス知れば確認ができます!!

letter_opner_web: http://localhost:3000/letter_opener

 ↓ 実際に開いてみるとこんな感じ ↓

動作確認

設定はできたものの出来ればメールが飛ぶか見てみたいですよね。
と言う事で、サクッとメールも飛ばしてみましょう。

ActionMailer作成

コンソールから、controller作るときと同じ要領でActionMailer作ります。
※公式ドキュメントには”SampleMailer”のように指定するように記述がありますが”Mailer"部分はジェネレートする際に勝手に付与されます。知らずにやるとSampleMailerMailerみたいに残念なフォルダやクラスが作成されるので注意です笑

 $ rails g mailer Sample
 Running via Spring preloader in process 15548
      create  app/mailers/sample_mailer.rb
      invoke  haml
      create    app/views/sample_mailer
      invoke  test_unit
      create    test/mailers/sample_mailer_test.rb
      create    test/mailers/previews/sample_mailer_preview.rb

なお、controllerと同じくrails d mailer Sampleで削除も可能です。

ActionMailerを編集する

①共通部
Application_mailers.rbは、ActionMailer全体で適用されます。
初期状態で以下のように送信元メールアドレスとメールフォームが設定されています。
もし、送信元メールアドレスを変更したいのであれば、下記のdefault from:を編集してください。
※各Mailerで個別に送信元を編集することもできるようです

app/mailers/application_mailers.rb
class ApplicationMailer < ActionMailer::Base
  default from: 'from@example.com'      # 送信元メールアドレス
  layout 'mailer'                       # メール全体に適用されるメールフォーム(Viewsのapplication.html.hamlなどと共通の原理かと)
end

②SampleActionMailer
ここで送信メソッドを定義します。メソッド名は自由に決めちゃって良いかと思います。
今回メソッド内で設定するのはメールヘッダ部分のみです。
(body部も書けますが、管理が煩雑になるので基本的には切り出すようです)

app/mailers/sample_mailer.rb
class SampleMailer < ApplicationMailer
  attr_accessor :email,:title,:name
  def sample_mail(email,title,name)      #仮引数に@付きのインスタンス変数は指定できませんので注意
    @email = email                       #インスタンス変数に格納
    @title = title                       #インスタンス変数に格納
    @name  = name                        #インスタンス変数に格納
    mail  to: @email,                    #メールの宛先を指定
          subject: "【テストメール】#{@title}" #メールのタイトルを指定
  end
end

※モデルを使用していないため、無理やり項目作っていますが、普通は@userとかかと。

メールフォーム(body部)を編集する

メールのbody部は、Viewsフォルダ配下に作成します。
rails gコマンドで実行した時点で、該当のActionMailerに対応するフォルダが、Viewsフォルダ配下に作成されています。このフォルダ配下にActionMailerで作成したメソッドと同名のファイルを作成し、body部を記述します。

記述方法は、基本的にviews作成時と同じ感じです。
(プレーンテキストとhtmlで切り替えできそうですが、htmlのみを対象としています。)

app/views/sample_mailer/sample_mail.html.haml
= "#{@name}さん"
%br
%p
  Hello World!!

設定自体はこれで完了です。
controllerなどからActionMailerを呼び出せばOK。
この部分ちょっと端折って、コンソールから検証してみます。
※今回端折りますが下図[4]のようにcontrollerなどに記載すればメール送信可能になります

コンソール.railsc
#引数を下準備
[1] pry(main)> email = "test@gmail.ccooom"
=> "test@gmail.ccooom"
[2] pry(main)> title = "Hello World"
=> "Hello World"
[3] pry(main)> name = "はむすけ"
=> "はむすけ"
#ActionMailerを実行してみる(最後に”.deliver”付けないと送信されません!!!)
[4] pry(main)> SampleMailer.sample_mail(email,title,name).deliver

実行結果を見ると、どう言う順で読み込まれてるか、何となくイメージが湧きますね。

コンソール.実行結果
Rendering sample_mailer/sample_mail.html.haml within layouts/mailer
Rendered sample_mailer/sample_mail.html.haml within layouts/mailer (9.2ms)
SampleMailer#sample_mail: processed outbound mail in 5075.2ms
Sent mail to test@gmail.ccooom (37.0ms)
Date: Sun, 15 Sep 2019 14:23:42 +0900
From: from@example.com
To: test@gmail.ccooom
Message-ID: <5d7dcade41695_3f143fbfadc14e5c744d@user-nameMacBook-Air.local.mail>
Subject: =?UTF-8?Q?=E3=80=90=E3=83=86=E3=82=B9=E3=83=88=E3=83=A1=E3=83=BC=E3=83=AB=E3=80=91Hello?=
 =?UTF-8?Q?_World?=
Mime-Version: 1.0
Content-Type: text/html;
 charset=UTF-8
Content-Transfer-Encoding: quoted-printable

<!DOCTYPE html>=0D
<html>=0D
<head>=0D
<meta content=3D'text/html; charset=3Dutf-8' http-equiv=3D'Content-Type'>=
=0D
<style>=0D
  /* Email styles need to be inline */=0D
</style>=0D
</head>=0D
<body>=0D
=E3=81=AF=E3=82=80=E3=81=99=E3=81=91=E3=81=95=E3=82=93=0D
<br>=0D
<p>=0D
Hello World!!=0D
</p>=0D
=0D
</body>=0D
</html>=0D

=> #<Mail::Message:70092500977860, Multipart: false, Headers: <Date: Sun, 15 Sep 2019 14:23:42 +0900>, <From: from@example.com>, <To: test@gmail.ccooom>, <Message-ID: <5d7dcade41695_3f143fbfadc14e5c744d@user-nameMacBook-Air.local.mail>>, <Subject: 【テストメール】Hello World>, <Mime-Version: 1.0>, <Content-Type: text/html>, <Content-Transfer-Encoding: quoted-printable>>
この結果、下記の画面が開きます。

成功はしているみたいですが、rails sしてないからletter_opner_web自体は開けません(:_;)
と言うことで、rails sしてから、適当なところでbinding.pryしてみましょう(あくまで面倒臭がる)
コンソール.binding.pry
    5: def index
 => 6:   binding.pry

[1] pry(#<PostsController>)> email = "test@gmail.ccooom"
=> "test@gmail.ccooom"
[2] pry(#<PostsController>)> title = "Hello World"
=> "Hello World"
[3] pry(#<PostsController>)> name = "はむすけ"
=> "はむすけ"
[4] pry(#<PostsController>)> SampleMailer.sample_mail(email,title,name).deliver
この状態でletter_opner_webにアクセスしてみましょう。今度はアクセスできるはず。

補足(課題込み)

letter_opner_webはプロジェクト単位でメールデータを保持しているようです。
上の図でrails cで試したメールも残っていることから、サーバ起動、停止などを繰り返しても消えることは無いようです。プロジェクトフォルダが肥大化しないよう注意が必要かなと思いました。

ちなみに送信メールの保存場所は「tmp/letter_opner/」配下です。
(デフォルトでgitignore対象となっています)
もし、保存場所を変えたい場合は、initilizerにletter_opner_web用のファイルを作成し、そこで定義すればOKみたいです。

config/initializers/letter_opner_web.rb
LetterOpenerWeb.configure do |config|
  config.letters_location = Rails.root.join('public', 'email')
  # 上記パスの中身"#<Pathname:/Users/user-name/projects/miniblog/public/mail>"
end

※当方うまく設定できてません。試したところ、こうなります。

<それぞれの挙動>
 メールデータ格納先
  →デフォルトの「tmp/letter_opner」のまま。フォルダ消しても蘇ってきます笑
 letter_opner_web参照先
  →設定が効いているのか、何も見えなくなる笑

…畜生め(TωT)!!!
と思いつつ、configの設定がうまくいっていないのか・・・?!
と思いrails sした時点で、configの設定状況を確認してみる(↓)。
うん、想定通りに読み込まれてる。指定のパスもcdで行ける。(思考停止)

console.
[1] pry(#<PostsController>)> LetterOpenerWeb.config.letters_location
=> #<Pathname:/Users/user-name/projects/miniblog/public/mail>
[2] pry(#<PostsController>)> LetterOpenerWeb.config
=> #<LetterOpenerWeb::Config:0x00007fb9d9ee2948
 @letters_location=#<Pathname:/Users/user-name/projects/miniblog/public/mail>>

ここに時間を割き過ぎるのもなので、一旦ここまで。
もう少しinitilizerとかconfigとか、理解が進めば思い返してみることもあるかも。。。

まとめ

手軽にメール機能を試せるのは、いいな〜と感じました。
ただ、色々試していると基礎知識がまだまだ足りないな〜と思い知らされます^^;
今後も、より深くrubyやrailsを知っていけるように頑張ります。。

参考

GitHub:letter_opner_web
Rails公式ガイド:Action Mailer の基礎
開発中にrailsから送信したメールを確認する

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

Gemfile.lockを削除する時はエディタで表示して、ターミナルからrmするのが楽。

Gemfile.lockを削除してみましょう。

Railsの環境構築系で調べていると
こういう記事を見かけることがある。
わざわざコピペするのもgitで戻すのも面倒。

テキストエディタ

私はsublime textやVSCodeを使っていますが、
そちらでGemfile.lockを開く、
ターミナルからコマンドで削除。

という風にすると未保存のファイルとしてエディタには残り、
環境としてはGemfile.lockが消えている動作になる。
元に戻す時はcommand+sですぐに戻るので便利ですね。

最後に

使っている機能が何によるモノなのか?
分解して考えられるととても便利に開発出来るなあと
最近強く感じております。

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

rails popular error of top 10

Topic about top 10 error

tell us popular error and
way to prevent them

1.ActionController::RoutingError

this error get 404error

this means your app doesn't have URL.

2. NoMethodError: undefined method '[]' for nil:NilClass

  • avoid user&
street = user&.address&.street

3.ActionController::InvalidAuthenticityToken

CSRF (Cross Site Request Forgery)

word

  • gotchas 落とし穴
  • relevant

    • ex) errors are not relevant to most readers.
  • dig in

    • see detail
  • classic

    • very typical of its kind.
  • errant 間違った

    • errant users
  • keep in mind

ex)
You should also keep in mind that

  • notation 表記方法
  • retrieve
  • internal error 内部エラー

  • vulnerability 脆弱性

  • malicious 悪意のある

  • mitigate 半減させる

記事

https://dev.to/philnash/top-10-errors-from-1000-ruby-on-rails-projects-and-how-to-avoid-them-24m

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

bundle installとbundle updateの違い

はじめに

Railsチュートリアルを進めていく中で、両者のコマンドの違いがいまいち理解出来なかったので、備忘録として投稿します。

両者の違い

  • bundle install
    ケース別によって動きが変わるため、以下図にまとめました。
  • bundle update
    Gemfileを元にGemをインストールする。
    各Gemのバージョンと依存関係にあるGemとバージョンが最新になる。
    インストール後、Gemfile.lockを更新する。

本番環境でbundle updateは原則使用しない。
bundle updateを行うと各Gemとバージョンが最新化され、開発環境と同じ動作にならなくなる可能性があるため。

用途

  • bundle install
    新規開発スタート...必要なGemをインストールするため。
    本番環境へデプロイ...開発環境と同じGemをインストールするため。
  • bundle update
    開発環境でGemを追加・更新...インストールするGem及び依存関係にあるGemを最新化させるため。

参考記事

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

【Windows】RubyとRuby on Raisの環境構築でSQLite3まわりで苦労した

はじめに

結論から言うと、Ruby,Rails,SQLite3の各どのバージョンをダウンロードすればいいのかわからずエラー出しまくりだったので、参考までに残します。
実際にYay! You’re on Rails!がでたバージョン
(もっと良いバージョンがあるかもしれません)

環境

OS: Windows10 64bit

Ruby version: 2.4.7 (x64-mingw32)
Rails version: 5.2.3
SQLite3 version: 1.3.6

エラー内容

rails のインストールをし、rails new でプロジェクトを作成し、「rails s」を流したところエラーが出たのでGemfileの9行目くらいを

Gemfile
# Use sqlite3 as the database for Active Record
gem 'sqlite3', '~> 1.3.6'

に変えてインストールし直せば、エラーが解消されました。
どうやらデフォルトで入るSQLite3がRuby 2.4.7に対応していないようです。
Q&Aサイトに似たようなところで苦労されている方がいたので少しでも力になれば幸いです。

参考サイト

https://knkomko.hatenablog.com/entry/2019/02/23/110443
http://mashiroyuya.hatenablog.com/entry/windows10RoR

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

`rails new` せずに小さなRailsアプリを作成

Railsアプリを作る際は最初に rails new を実行する。すると大量のファイルが作られ、その中には様々な設定が最初から書かれている。はっきり言ってどれが何をしているのかよく分からない。

そこで rails new せずに、サーバー実行時のエラーを見ながら少しずつファイルや設定を加えてみた。 "Hello, world!\n" の14バイトのテキストを返すだけのアプリができるまでをまとめる。

TL;DR

必要なファイルまとめ

一般的なパス パスの定義元 書くべき処理
bin/rails railties/lib/rails/app_loader.rb 定数 APP_PATH を定義した後、 rails/commands を読み込む
config/application.rb ユーザー定義定数 APP_PATH Rails::Application を継承したクラスを定義する
config.ru rails server --config=XXX Railsアプリを初期化、実行する
(今回はルーティングも書いた)
app/controllers/xxx_controller.rb ルーティング・自動読み込み リクエストに応じて処理する

今回のアプリでは、上記ファイルは合計で20行しかない。

$ git ls-files | xargs wc -l
   5 .gitignore
   7 Gemfile
 135 Gemfile.lock
   5 app/controllers/mini_controller.rb
   3 bin/rails
   8 config.ru
   4 config/application.rb
 167 total

前準備

Windows Subsystem for Linux上のUbuntu 16.04で試している。

Rubyのバージョン選択

既にrbenvが入っているとする。新しくRuby 2.6.4が出ていたのでインストールする。あまり他に影響を与えたくないので、バージョン指定はこのシェルでのみ有効なようにする。

(cd $(rbenv root)/plugins/ruby-build/ && git pull)
rbenv install --skip-existing 2.6.4
rbenv shell 2.6.4

Gitリポジトリの作成

適宜保存するために作成しておく。

mkdir -p ~/projects/rails_mini_app
cd ~/projects/rails_mini_app

git init
git commit --allow-empty # 一応、空っぽの時点で保存しておく

Railsのインストール

bundle init
sed --in-place --expression='s/^# *gem/gem/' Gemfile

これで、gem rails をインストールするGemfileが完成する。(2019/09/14時点の最新版は6.0.0)

Gemfile
# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem "rails"

不要になったら跡形もなく消せるよう、gemは作業ディレクトリ内にインストールする。git管理は不要なので .gitignore で除外しておく。

bundle install --path=vendor/bundle

cat << EOF >> .gitignore
/.bundle
/vendor
EOF

Railsアプリ作成

初期状態で確認

早速railsサーバーを起動してみる。

bundle exec rails server
Usage:
  rails new APP_PATH [options]

Options:
  ...

Example:
    rails new ~/Code/Ruby/weblog

    This generates a skeletal Rails installation in ~/Code/Ruby/weblog.

rails new のヘルプが表示された。暗にこれを実行しろと言われている。

bin/rails の作成と、定数 APP_PATH の定義

ヘルプからでは必要なファイルがわからないので、「Railsの初期化プロセス」を読む。gem以外で最初のファイルは bin/rails であるらしい。

1.2 railties/lib/rails/app_loader.rb

exec_app の主な目的は、Railsアプリケーションにある bin/rails を実行することです。カレントディレクトリに bin/rails がない場合、 bin/rails が見つかるまでディレクトリを上に向って探索します。これにより、Railsアプリケーション内のどのディレクトリからでも rails コマンドを実行できるようになります。

ただし試したところ、適当なコードだと rails new のヘルプが表示されるままだった。ヘルプを回避するには APP_PATH という文字列を含める必要があった。(定数かどうかはここでは関係ない)

bin/rails
#!/usr/bin/env ruby
# APP_PATH

この状態でサーバーを起動すると、ヘルプは表示されなくなるが他のことも起きない。

rails/commands の読み込みと、 APP_PATH の指すファイル準備

サーバー起動処理(railsコマンドの実行)へ進むには、 rails/commands を読み込ませる必要がある。また、その中で定数 APP_PATH のファイルも読み込まれるので、一般的なパスである config/application.rbとりあえず空ファイルで作っておく。

bin/rails
#!/usr/bin/env ruby
APP_PATH = File.expand_path('../config/application', __dir__)
require 'rails/commands'

これでサーバーを起動するとNoMethodErrorが発生する。

/home/user/projects/rails_mini_app/vendor/bundle/ruby/2.6.0/gems/railties-6.0.0/lib/rails/commands/server/server_command.rb:142:in `block in perform': undefined method `root' for nil:NilClass (NoMethodError)
railties-6.0.0/lib/rails/commands/server/server_command.rb#L133-152
      def perform
        extract_environment_option_from_argument
        set_application_directory!
        prepare_restart

        Rails::Server.new(server_options).tap do |server|
          # Require application after server sets environment to propagate
          # the --environment option.
          require APP_PATH
          Dir.chdir(Rails.application.root) # ここでエラー

          if server.serveable?
            print_boot_information(server.server, server.served_url)
            after_stop_callback = -> { say "Exiting" unless options[:daemon] }
            server.start(after_stop_callback)
          else
            say rack_server_suggestion(using)
          end
        end
      end

Railsアプリの用意

Rails.application が無いのが問題らしい。 config/application.rb の中にRailsアプリを書く。

config/application.rb
class MiniApp < Rails::Application
end

これでサーバーを起動すると、親切に次の作業のヒントを出してくれる。

=> Booting WEBrick
=> Rails 6.0.0 application starting in development http://localhost:3000
=> Run `rails server --help` for more startup options
configuration config.ru not found
Exiting

なお、設定ファイルは --config オプションで指定できるので、 config.ru でなくてもいい。

config.ru の用意

空ファイルを用意するだけだと、サーバー起動時にエラーになる。

=> Booting WEBrick
=> Rails 6.0.0 application starting in development http://localhost:3000
=> Run `rails server --help` for more startup options
Exiting
Traceback (most recent call last):
    ...
    1: from config.ru:2:in `<main>'
/home/user/projects/rails_mini_app/vendor/bundle/ruby/2.6.0/gems/rack-2.0.7/lib/rack/builder.rb:146:in `to_app': missing run or map statement (RuntimeError)

run が必要らしいので、実際のアプリに存在する1行を加える。

config.ru
run Rails.application

これでサーバーを起動すると、またNoMethodErrorが発生する。

=> Booting WEBrick
=> Rails 6.0.0 application starting in development http://localhost:3000
=> Run `rails server --help` for more startup options
Exiting
Traceback (most recent call last):
    ...
/home/user/projects/rails_mini_app/vendor/bundle/ruby/2.6.0/gems/railties-6.0.0/lib/rails/commands/server/server_command.rb:80:in `log_to_stdout': undefined method `formatter' for nil:NilClass (NoMethodError)
railties-6.0.0/lib/rails/commands/server/server_command.rb#L76-86
      def log_to_stdout
        wrapped_app # touch the app so the logger is set up

        console = ActiveSupport::Logger.new(STDOUT)
        console.formatter = Rails.logger.formatter # ここでエラー
        console.level = Rails.logger.level

        unless ActiveSupport::Logger.logger_outputs_to?(Rails.logger, STDOUT)
          Rails.logger.extend(ActiveSupport::Logger.broadcast(console))
        end
      end

Railsアプリの初期化

loggerといえばいつも config/environments/<env>.rb で何か設定している覚えがある。今回は config.rurequire_relative 'config/environment' をしていないため、抜けている処理がそこにあると考えられる。探したら怪しい1行があったので、直接 config.ru に加える。

config.ru
Rails.application.initialize!

run Rails.application

これでついにサーバーが起動する。 log/tmp/ が作られるので、 .gitignore で除外しておくといい。

=> Booting WEBrick
=> Rails 6.0.0 application starting in development http://localhost:3000
=> Run `rails server --help` for more startup options
config.eager_load is set to nil. Please update your config/environments/*.rb files accordingly:

  * development - set it to false
  * test - set it to false (unless you use a tool that preloads your test environment)
  * production - set it to true

[%Y-%m-%d %H:%M:%S] INFO  WEBrick 1.4.2
[%Y-%m-%d %H:%M:%S] INFO  ruby 2.6.4 (2019-08-28) [x86_64-linux]
[%Y-%m-%d %H:%M:%S] INFO  WEBrick::HTTPServer#start: pid=XXXXX port=3000

しかしクライアントからアクセスすると500エラーになる。

[%Y-%m-%d %H:%M:%S] ERROR NameError: uninitialized constant ActionController::Base
    /home/user/projects/rails_mini_app/vendor/bundle/ruby/2.6.0/gems/actionpack-6.0.0/lib/action_dispatch/middleware/static.rb:78:in `ext'
    ...
127.0.0.1 - - [%d/%b/%Y:%H:%M:%S %Z] "GET / HTTP/1.1" 500 325
- -> /

Rails関連のgemの読み込み

普通のアプリと比較して抜けている処理のうち、 ActionController が読み込まれなさそうなものは、railsのgemの読み込みと考えられる。今回は機能が非常に少ないので rails/all ではなく action_controller/railtie のみを読み込む。

config/application.rb
require 'action_controller/railtie'

class MiniApp < Rails::Application
end

これでサーバーを起動してクライアントからリクエストすると、(当然ではあるが)RoutingErrorになる。

Started GET "/" for 127.0.0.1 at %Y-%m-%d %H:%M:%S %z

ActionController::RoutingError (No route matches [GET] "/"):

...
127.0.0.1 - - [%d/%b/%Y:%H:%M:%S %Z] "GET / HTTP/1.1" 404 0
- -> /

ルーティングとcontrollerを定義

普通は config/routes.rb に書くが、ファイルを作らず他の場所に書けないか試した。その結果、Railsアプリ初期化後なら設定が反映された。

config.ru
Rails.application.initialize!

Rails.application.routes.draw do
  root          to: 'mini#index', via: :all
  match '*any', to: 'mini#index', via: :all
end

run Rails.application

もちろんcontrollerが無いとエラーになるので、ルーティングに対応するものを作成しておく。 app/ 直下は自動読み込みの探索対象なので、ディレクトリ名は controllers でなくても構わない。

app/controllers/mini_controller.rb
class MiniController < ActionController::Base
  def index
    render plain: "Hello, world!\n"
  end
end

これでついにサーバーが 200 OK を返す。

Started GET "/" for 127.0.0.1 at %Y-%m-%d %H:%M:%S %z
Processing by MiniController#index as */*
  Rendering text template
  Rendered text template (Duration: 0.1ms | Allocations: 3)
Completed 200 OK in 308ms (Views: 307.8ms | Allocations: 1269)


127.0.0.1 - - [%d/%b/%Y:%H:%M:%S %Z] "GET / HTTP/1.1" 200 14
- -> /

ルーティングをRails初期化前に書くと無効なようで、development環境ではRailsのwelcome画面が表示された。

Started GET "/" for 127.0.0.1 at %Y-%m-%d %H:%M:%S %z
Processing by Rails::WelcomeController#index as */*
  Rendering vendor/bundle/ruby/2.6.0/gems/railties-6.0.0/lib/rails/templates/rails/welcome/index.html.erb
  Rendered vendor/bundle/ruby/2.6.0/gems/railties-6.0.0/lib/rails/templates/rails/welcome/index.html.erb (Duration: 129.8ms | Allocations: 415)
Completed 200 OK in 183ms (Views: 154.5ms | Allocations: 2171)


127.0.0.1 - - [%d/%b/%Y:%H:%M:%S %Z] "GET / HTTP/1.1" 200 399720
- -> /

これらのルーティングはこっそり加えられている。

railties-6.0.0/lib/rails/application/finisher.rb#L74-86
      initializer :add_builtin_route do |app|
        if Rails.env.development?
          app.routes.prepend do
            get "/rails/info/properties" => "rails/info#properties", internal: true
            get "/rails/info/routes"     => "rails/info#routes", internal: true
            get "/rails/info"            => "rails/info#index", internal: true
          end

          app.routes.append do
            get "/"                      => "rails/welcome#index", internal: true
          end
        end
      end

その他

動作しないこと

  • Rakeタスク全般
    • No Rakefile found (looking for: rakefile, Rakefile, rakefile.rb, Rakefile.rb)
  • bin/rails による実行
    • cannot load such file -- rails/commands (LoadError)
    • bundle exec を省くための設定が必要
      • 普通は config/boot.rb に記載して読み込み
  • ルーティング確認(bundle exec rails routes
    • config/routes.rb を直接参照するため

Gemの削減

gem railsaction* や active* などに依存しているが、そのほとんどは今回使用していない。エラーに出てきたのは railties なので、それに差し替える。

Gemfile
# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem "railties"

これでgemが43個から24個に減った。

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

S3に保存した画像を表示する(Rails)

概要

例えば、あるサービスで画像をアップロードする際にS3を使うことはよくあるだろう。
そのS3にアップロードした画像を表示させたいということは結構あるのでは?(私がそれをやりたい)
私がやりたいのは以下の条件である。

  • S3はパブリックアクセスをオフ
  • 通常S3にアクセスできるのは特定のプログラムからのみ

この条件で、サービスからのみ画像を表示するようにする方法を書きます。

実行環境

Ruby 2.6.3
AWS S3

手順

S3について

前述した通り、パブリックアクセスはオフにしておきます。

ポリシーはこれぐらいしか書いてません。

S3policy
{
    "Version": "2012-10-17",
    "Id": "Policyxxxxxxxxxxx",
    "Statement": [
        {
            "Sid": "Stmtxxxxxxxxxxx",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::xxxxxxxxxxxx:user/your_name"
            },
            "Action": [
                "s3:DeleteObject",
                "s3:GetObject",
                "s3:PutObject"
            ],
            "Resource": "arn:aws:s3:::your_bucket_name/*"
        }
    ]
}

S3の特定のオブジェクトにアクセスしようとすると以下の画面のようにAccessDeniedになります。

これを回避するために一時的に閲覧可能となる期限付きURLというものがあります。

期限付きURLについて

期限付きURLとは、文字通り一時的に閲覧可能となるURLで、指定した時間を経過すると閲覧できなくなるものです。
これを利用して、サービス利用者のみS3に保存された画像を閲覧できるようにします。

ここではRubyでのやり方を示しますが、他の言語でもAWSのSDKがあればできるはずです。

AWS S3 SDKのインストール

Gemfileを使った方法を書きますが、使わない方はgemでインストールしてください。
Gemfileに以下を書いてインストールします。

Gemfile
gem 'aws-sdk-s3'

期限付きURLの発行

以下の例ではプログラムからのみアクセスできるようにS3を設定しているのでCredentialを指定しています。
表示時間は60秒として設定しました。

s3 = Aws::S3::Resource.new(
      region: REGION_NAME, # 1. 利用しているリージョン
      credentials: Aws::Credentials.new(
        AWS_ACCESS_KEY, # 2. プログラムからアクセスできるユーザのアクセスキー
        AWS_SECRET_ACCESS_KEY # 3. プログラムからアクセスできるユーザのシークレットキー
      )
    )
signer = Aws::S3::Presigner.new(client: s3.client)
presigned_url = signer.presigned_url(:get_object,
        bucket: bucket_name, key: key, expires_in: 60)

Presigner.presigned_urlで期限付きURLを発行しています。
生成されたURLにアクセスすると閲覧できるはずです。

HTMLで画面に表示する場合には通常通りimgタグのsrcにこの期限付きURLを入れれば良いだけです。

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

[devise_token_auth]ヘルパーメソッドが使えない(undefined)ときの対処法

公式のドキュメントをちゃんと読めばわかる内容ですが、地味にはまったので書きます…

発生したエラー

ログインしたユーザーのみアクセス可能にしたpostsコントローラにリクエストを送ったところ以下のようなエラーが出ました。

NoMethodError (undefined method `authenticate_user!' for #<Api::V1::PostsController:0x00007f0a5806dee8>):

解決策

どうやらdevise_token_authのヘルパーメソッドを記述するときはroute.rbの内容次第で変更しなければならないようです。
僕のルートは

routes.js
Rails.application.routes.draw do
  devise_for :users

  namespace :api do
    namespace :v1 do
      mount_devise_token_auth_for 'User', at: 'auth', controllers: {
        registrations: 'api/v1/auth/registrations'
      }
      resources :posts
    end
  end
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end

という感じになっています。
namespaceが使われているので/api/v1/authとのようになります。
この時、ヘルパーメソッドを以下の様に変更しなくてはなりません

before after
before_action :authenticate_user! before_action :authenticate_api_v1_user!
current_user current_api_v1_user

まとめ

英語のドキュメントばかりで大変でしたが、この記事が日本の駆け出しエンジニアの助けになれば幸いです!
twitterやってるのでよければフォローしてください!
ID: @extreammoney

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

Sass(SCSS)で使用出来る@mixinについて

始めに

作成した個人アプリをリファクタリングする際Sassの機能の一つ@mixinを使用したのでその使用方法について説明します。

@mixin(ミックスイン)とは?

定義したスタイルを別のスタイルに@includeを使用して何度も呼び出す事が可能になるというものです。
よく使うスタイルを@mixinで定義しておけばどこからでも@includeを使用すれば呼び出せるので、コードが見易くなり、メンテナンスもしやすくなります。

記述方法

先ずは基本的な使用方法から@mixinの後にクラス名を定義して使用します。
その後使用したいファイルに@includeを使用して定義します。

mixin.scss
@mixin white {
  color: white;
  font-size: 2rem;
}
test.scss
.test{
  @include white;
  float: left;
}


実際の中身
.test {
  color: white;
  font-size: 2rem;
  float: left;
}

後はパーシャルファイルを読み込むのを忘れないよう@importをファイル内に記述します。

application.scss
  @import "modules/mixin";
  @import "test";

引数を使用した方法

引数を使用した方法の場合ha$引数名を()内に定義して使用します。

mixin.scss
@mixin centerwhite($size) {
  color: white;
  text-align: center;
  font-size: $size;
}

この様に定義する事で今回の場合はサイズ指定をする事が出来るようになります。
使用方法はクラス名の後の()の中に指定します。

test.scss
.test {
  @include centerwhite(2rem);
  float: left;
}

実際の中身
.test {
  color: white;
  text-align: center;
  font-size: 2rem;
  float: left;
}

2つ以上の引数の場合

()内に複数$引数名を使用する事で複数指定する事も可能になります。

mixin.scss
@mixin colorstyle($color,$size) {
  color: $color;
  font-size: $size;
}
test.scss
.test {
  @include colorstyle(white,2rem);
  float: left;
}

実際の中身
.test {
  color: white;
  font-size: 2rem;
  float: left;
}

この様に複数指定すれば、色やサイズなどの指定が容易に出来、メンテナンスもしやすくなる為、便利だと思います。

最後に

以上基本的な@mixinの使用方法でした。この他にも色々応用的な使用方法もありますので、今後使いこなせる様になれたらと思います。
以下、参考にさせて頂いた資料です。
https://www.webcreatorbox.com/tech/sass-mixin

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

[Rails]ajaxでpostアクションを叩いたらsessionが拾えなかった。

どうもこんばんは、Rails学習2〜3ヶ月くらいのChihaです。
タイトル通り、jQueryからajaxでアクションを叩いて動かすと、それまで定義してきたsessionの値が拾えなくなる現象に遭遇&解決したので、ざっくりと書きます。

開発環境

  • Ruby on Rails 5.2.2
  • Ruby 2.5.1
  • jQuery

概要

ajaxからのpostリクエストを叩くと、アプリ側に持たせていたsessionの値が取得できなくなった。

signup.js
//何かしらの処理後
$.ajax({ 
          url: "/signup",
          type: "POST",//コントローラのアクションをpostで叩く
          data: {
            //送るデータとか
          },
          async: false //今回は同期通信でした
        })
signup_controller.rb(例)
#前のページでの入力項目をsession保存
def before_page_content
  session[:birthyear] = profile_params[:birthyear]
  session[:birthmonth] = profile_params[:birthmonth]
  session[:birthday] = profile_params[:birthday]
end

#ajaxで叩くcreateアクション
#/signup(method: :post)
def create
  ※下のcreateが失敗する
  @profile = Profile.create(
  birthyear: session[:birthyear],
  birthmonth: session[:birthmonth],
  birthday: session[:birthday]
  )
end

こんな感じのコードでjsから直接postメソッドのアクションを叩くと、それまでに取得して来ることができたはずのsessionの値が全部nilになっていた。そりゃ当然失敗する。

ターミナル
ActionController::InvalidAuthenticityToken

エラーはこういうのが出ました。

解決策

signup.js
//何かしらの処理後
$.ajax({ 
          url: "/signup",
          type: "POST",
          data: {
            //送るデータとか
          },
          //下の記述を追記
          beforeSend: function(xhr) {
            xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'));
          },//ここまで
          async: false
        })

これで無事にcreateアクションでsessionの値を拾って処理ができるようになりました。

Rails not reloading session on ajax post
【Rails API】ActionController::InvalidAuthenticityTokenの解決方法
外部からPOSTできない?RailsのCSRF対策をまとめてみた

解決はこの辺りを参考にしました。
どうやら、RailsでのCSRF対策にjsからget以外のメソッドを叩いたのが悪かったようです。
【Rails API】ActionController::InvalidAuthenticityTokenの解決方法
詳しい理由とCSRFについてはこの記事に書かれてます。

トラブルの経緯

スクールでの開発にて、wizard形式(複数ページに渡りユーザー情報を入力させ、最後のページで一括createする形式)でのユーザー登録機能を実装していた時の話。
各ページで入力された値は、sessionとしてアプリ側に預ける形に(詳しくはそのうち)。
最終ページでクレジットの情報を登録する必要があったため、payjpを利用することに。

payjpでは、ユーザーが入力したクレジット情報をjavascript経由でpayjpに送り、トークンの情報を返す処理を行うことで、クレジット情報をRails側で使用せず、トークンを用いたユーザーのクレジットカード登録機能の実装、使用が可能となります。
こちらについては詳しくはpayjp.jsのリファレンス(カード情報のトークン化)で。

というわけで、最終ページでcreateではなく、payjp.jsでのトークン取得を経てcreateする形で実装することに。

つまり、jsでの処理を経由してpayjpからのトークンを取得したのち、トークンをjsから投げつつ、postメソッドのcreateアクションを叩く、という感じでの実装を考えた。

ここでjsからcreateアクションを叩くとsessionが一つも取得できなくなっていた。
要するに上記の症状が発生していた。

js側に記述を書き足すと解決した。

という流れになります。

かなりアバウトな感じになりましたが、詳しくは上記の参考記事にて。
クレジットカード登録を含めたwizard形式でのユーザー登録フォームの実装については、長くなりそうなので後日ということで。。。

最後に

引き続き気まぐれにトラブルや知った技術などを忘備録として書いていこうと思います。
まだまだ右も左もあやふやなド初心者ですので、より良いコード、間違った点などがあればご教授頂けると幸いです。

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

Windows WSL2に移行後 bundle exec rails xxx が激遅だったが解決

解決した方法を先に書くと
WindowsのCドライブなどに置いていたrailsプロジェクトをWSLのUbuntu配下に移動する

Mac使っていないマンとしてはWSL2を試してみたくなってWindos Insider Programに参加しました。
Windows Insider ProgramとWSLについては簡単にこちらのブログに書きました。
https://blog.higekick.net/wls2/

Rails Tutorialをやっているのですが、WSL2に移行後、bundle exec rails sとかが遅いことに気づきました。
確かに各種gitコマンドは速くなっている気がします。
なんでかなと思って予想したのがrailsのプロジェクトを置いているディレクトリの位置でした。
wsl2は仮想マシン上で動いているとかなんとか聞いたのですがWindows側から見た時の位置が以下のようになっていました。

パスとしてはこんな感じになっていました。

\\wsl$\Ubuntu

wsl1の時の位置はちょっと把握していないのですが、Networkの下とかじゃなかったですよね。
本題のrailsプロジェクトですが、WindowsのDドライブに置いてUbuntuのホームディレクトリからシンボリックリンクで参照できるようにしていました。
wsl2に移行後もちゃんとそれは維持されていて、wsl1と同じようにrailsプロジェクトのディレクトリを参照できて、$ code rails-tutorial-sampleapp/ とかでVS CODEで開いてRails Tutorialを楽しむことができました。

hige@thkpdE480:~$ pwd
/home/hige
hige@thkpdE480:~$ ls -la src
lrwxrwxrwx 1 hige hige 10 Sep  5  2018 src -> /mnt/d/src

しかしrailsの各種コマンドが遅いのですね。5分くらいでやっとrails serverが立ち上がる感じです。
そこまで待ってみた私もすごいのですが、なんでこんな遅いのか解決したかったです。

で、やっぱりこのネットワーク下になってしまった \\wsl$\Ubuntu のディレクトリからDドライブのプロジェクトを参照してしまっているからでは?と思ったのですよね。
そこで以下のようにホームディレクトリに配置したら速くなりました。

\\wsl$\Ubuntu\home\hige\src2\rails-tutorial-sampleapp

実際にはgitでcloneしなおしたのですが、railsプロジェクトなので移動後、railsが起動できるようになるまでどうやったのかメモしておきます。railsプロジェクトをcloneして自分の端末で開発できるようにするときはだいたいこうやるのかなと思っております。

  • Ubuntu下のディレクトリでcloneしなおす
$ git clone [リポジトリURL]
  • rubyをセットする
$ rbenv local 2.6.1
  • gemsをインストールする
$ bundle install --path vendor/bundle
  • DBをテスト環境(開発環境?)用にマイグレートする, テストデータを入れる
$ bundle exec rails db:migrate RAILS_ENV=test
$ bundle exec rails db:seed
  • テストして通ればOK
$ bundle exec rails test
  • サーバーが立ち上がってログインし、テストデータが見えればOKとする
$ bundle exec rails server

これらのbundle exec rails xxxコマンドが前みたいに普通の速さで実行できるようになりました。
wsl1の頃よりたぶん速くなっているんですよね!?確かに速くなっている気がします。
Macだとこんな苦労しなくても良さそうだけど、いろいろな制約でWindows使うしかない方とかたくさんいらっしゃると思います。共にがんばりましょう!

増税前にMac買ったほうがいいですかね... 2019/09/15

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

Herokuにデプロイをする時に詰まった箇所について

はじめに

RailsアプリケーションをHerokuにデプロイする際、自分が詰まった箇所について説明します。
また、herokuへのデプロイ方法は、最終行に記載した<4.デプロイ手順の参考記事>を参考にデプロイをしました。

それぞれのバージョンは以下の通りです。
MacOS 10.14.5
Ruby 2.5.1p57
Rails 5.2.3
heroku/7.26.2 darwin-x64 node-v11.14.0

画像
https://gyazo.com/847aa4661ae3b2f278ae2328d33d32dd

1.Herokuにログインする時

①現象

以下のように、herokuにログインするコマンドを実行しても、

$heroku login 

メールアドレスおよびパスワードを入力する画面いならない(おそらく、このような方法でログインすることもできるのだろう)。

動画(見えづらくて申し訳ありません(>人<;))
https://gyazo.com/e3873154a60890c7fa5aab07146c962c

②解決方法

以下のようなコマンドを入力すれば、解決できます。

$ heroku login --interactive

動画
https://gyazo.com/4829936b6a6626a7ea229383eecbeae9

③参考記事

https://teratail.com/questions/158638

2.Railsの設定

(1).Gemfileの設定

本番環境用にGemfileの設定しておかないと、以下のようなエラーが発生します。
(今回は、自分がアプリで使用したdeviseおよびjquery-railsの2つのgemを例にあげます。)

①エラー現象

以下のような画面が表示され、デプロイが失敗する。

deviseの場合、

remote:        Caused by:
remote:        NameError: uninitialized constant Devise

画像
https://gyazo.com/eb6608754ad0937c550eb45acdf848b5

jquery-railsの場合、

remote:        rake aborted!
remote:        Sprockets::FileNotFound: couldn't find file 'jquery' with type 'application/javascript'

画像
https://gyazo.com/71cc267fe306d82fdf6e65df2b2288a1

②解決方法

Gemfileの一番下に、以下のような設定をすると解決します(deviseおよびjquery-rails)。

group :development, :production, :test do
  gem 'devise'
  gem 'jquery-rails'
end

画像
https://gyazo.com/625adb044cecaff749c60f4f77fb317a

(2).テンプレートリテラル記法を用いる場合のproduction.rbの設定

①エラー現象

以下のようなエラー画面が表示され、デプロイが失敗する。

remote:        rake aborted!
remote:        Uglifier::Error: Unexpected character '`'

下の方に赤文字で以下のような表示がされる。

remote:  !     Precompiling assets failed.

全体の画像
https://gyazo.com/37b8b091eda6cec2c0514b54dea5a5c5

②解決方法

config/environments/production.rbにある以下のコードをコメントアウトする。

# config.assets.js_compressor = :uglifier

自分の場合、production.rbの26行目に書いてありました。
https://gyazo.com/b66d6c1a13d7b22a5105f88e1303f40d

そもそも、テンプレートリテラル記法とは?
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/template_strings

3.デプロイ成功後

(1).デプロイは成功後にアクセスするURLについて

①現象

herokuへのデプロイが成功すると、以下のような長いログの画面が表示される
https://gyazo.com/2d2e4ea40e66373aa6b62cb7df7f15cc
(画面が青い理由は、どこからどこまでがデプロイのログなのかを、自分で判断するためです。見えづらくて申し訳ありません(>人<;))。

そして、以下のような画面が表示される。

$ git push heroku master
 ・
 ・
 ・
remote: -----> Compressing...
remote:        Done: 36.9M
remote: -----> Launching...
remote:        Released v6
remote:        https://enigmatic-reaches-21915.herokuapp.com/ deployed to Heroku
remote: 
remote: Verifying deploy... done.
To https://git.heroku.com/enigmatic-reaches-21915.git
 * [new branch]      master -> master

そして、同時にherokuへのURLが生成されます。

remote:        Released v6
remote:        https://enigmatic-reaches-21915.herokuapp.com/ deployed to Heroku
remote: 
remote: Verifying deploy... done.
To https://git.heroku.com/enigmatic-reaches-21915.git
 * [new branch]      master -> master

②解決方法

アクセスできるURLは以下のような表示がされている方にアクセスすると解決します。

remote:        https://<個別のアプリの名前>.herokuapp.com/ deployed to Heroku

※自分は、今回デプロイするときのアプリの名前を指定してなかったので、

enigmatic-reaches-21915

という名前になっています。

③参考記事

https://qiita.com/kodai_0122/items/a1f01b18bb3e0ddde62a

(2).デプロイは成功し、正しいURLにアクセスしたが、エラー画面が表示される問題

①エラー現象

正しいURLにアクセスした時、以下のようなエラー画面が表示される。

"We're sorry, but something went wrong."

画像
https://gyazo.com/3a6a0c733d87efe0866c2e3f8d585a0f

②解決方法

デプロイをした後に、本番環境においてマイグレーションを行えば、解決できます。

③参考記事

https://i.gyazo.com/3a6a0c733d87efe0866c2e3f8d585a0f.png
https://qiita.com/KazuhoE/items/06d13ccd4c72fd31c5d9
https://teratail.com/questions/124183

4.デプロイ手順の参考記事

https://qiita.com/NaokiIshimura/items/eee473675d624a17310f
https://qiita.com/kazukimatsumoto/items/a0daa7281a3948701c39
https://sweets-engineer.com/heroku/#heroku
https://qiita.com/fuwamaki/items/f7752eb7a2727660239f

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