20190915のRubyに関する記事は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で続きを読む

Ruby+Selenium+GlideでFNO(東京)を楽しむ術

まずはじめに

FNO前に投稿しようと思っていましたが、、、すいません

自己紹介

初投稿なので自己紹介します。
某ファッションテック企業でサーバーサイドエンジニアをしているものです。
普段は主にRuby書いていて、少しSwiftも触っています。

この記事について

FNOはFASHION’S NIGHT OUT(https://www.vogue.co.jp/fno/2019/tokyo/#/about)のことで
毎年9月ごろにVOGUEが開催しているファッションイベントです。

限定アイテムなどが発売されるなか
FNOの醍醐味といえば
ノベルティ
だと思っています。(個人的な意見になります)

効率よくまわって楽しみたいなと思うのですが、
公式だと
地図を見るのに毎度ページ更新して確認して
というのがめんどくさいなと感じていました。

そこで今回はSeleniumu を使用してスクレイピングをして
CSVを生成し、Glideで読み込みます!

実装

準備&&環境

chromeをダウンロードしてください。

$ ruby -v                                                                      
ruby 2.5.3p105 (2018-10-18 revision 65156) [x86_64-darwin18]

スクレイピング

selenium-webdriverをインストール

gem install selenium-webdriver

chromeを開き、指定のURLに飛びます。
表示の時間を待ってから、スクロールする必要があります。(時間とスクロールは適当です)

require 'selenium-webdriver'
require 'csv'
require 'pry'

driver = Selenium::WebDriver.for :chrome
driver.navigate.to 'https://www.vogue.co.jp/fno/2019/tokyo/shoplist/#/item/3'
sleep(5)
driver.execute_script('window.scroll(0,1000);')
sleep(5)
spots = driver.find_element(:id, 'cat_result').text.split(' ')[0].to_i

必要なデータをCSVで抽出して行きます。

要素はxpathで取得しています。
xpathの取得方法に関しては下記を参考に。
https://qiita.com/ywindish/items/5a992c49387d81df900e

今回各wrap内のリンク先に住所等が書いてあるため、
もう一つDriverでchromeを起動します。
同じDriverでも問題ないですが、
JavaScriptの関係で再度待機時間などが必要になることなどから
別でchromeを開いています。

CSV.open('fno2019_tokyo.csv', 'w') do |csv|
  csv << %w[id name text image location link]
  driver.find_elements(:class, 'wrap').each.with_index(1) do |wrap, index|
    name = wrap.find_element(:xpath, "//*[@id='shopWrap']/li[#{index}]/a/div/div[2]/p[2]").text
    text = wrap.find_element(:xpath, "//*[@id='shopWrap']/li[#{index}]/a/div/div[2]/p[3]").text
    image = wrap.find_element(:xpath, "//*[@id='shopWrap']/li[#{index}]/a/div/div[1]/img").attribute('src')
    detail_link = wrap.find_element(:xpath, "//*[@id='shopWrap']/li[#{index}]/a").attribute('href')
    driver2 = Selenium::WebDriver.for :chrome
    driver2.navigate.to detail_link
    sleep(3)
    driver2.quit
    csv << %W[#{index} #{name} #{text} #{image} #{location} #{detail_link}]
    break if index == spots
  end
end

Driverを終了します。

driver.quit

Glide

生成したCSVをGoogle Driveにアップロードして
スプレッドシートで開きます。

Glide(https://go.glideapps.com/)を開き、
先ほど生成したスプレッドシートを読み込むだけです。

あとは好みでデザインを変更できます。

参考

https://www.seleniumqref.com/api/webdriver_gyaku.html
https://morizyun.github.io/web/selenium-cheat-sheet.html

最後に

下記が作成したものです。
https://x9kav.glideapp.io

神戸、名古屋、大阪の開催も10月以降にありますので
参考にしていただければ幸いです。

来年はドリンク、食事、それ以外のノベルティを
画像認識もしくは文字列判別で分類したいなと思っているところです。

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

Protocol Buffers / Ruby Generated Code(和訳)

このページは Protocol Buffers 公式リファレンス Ruby Generated Codeの和訳です。原文はCreative Commons Attribution 4.0 Licenseで公開されており、ソースコードはApache Licenseで公開されています。この訳文もそれにならいます。

Ruby Generated Code

このページでは、プロトコルバッファコンパイラが任意のプロトコル定義に対して生成するメッセージオブジェクトのAPIについて説明します。 このドキュメントを読む前に、proto3言語ガイドを読むことをお勧めします。

現時点ではまだproto3だけしかサポートされていません。proto2のサポートも計画されていますが、まだ利用できません1

Rubyのプロトコルコンパイラはメッセージスキーマを定義するDSLからRubyのソースファイルを生成します。DSLは引き続き変更される可能性があります(特にproto2サポートなどの機能の追加時)が、 このガイドでは、DSLではなく生成されたメッセージのAPIのみを説明します。

コンパイラ呼び出し

プロトコルバッファコンパイラは--ruby_out=というコマンドラインフラグをつけて実行することでRubyのコードを生成します。--ruby_out=オプションにはコンパイラにRubyコードを出力させたいディレクトリを直接指定します。コンパイラはそれぞれの.protoファイルの入力に対して、.rbファイルを作成します。出力ファイルの名前は.protoファイルの名前から取られますが、二点違いがあります。

  • 拡張子 (.proto)は_pb.rbで置き換えられます。
  • (--proto_path=-Iのコマンドラインオプションで指定された)protoのパスは、(--ruby_out=フラグで指定された)出力パスに置き換えられます。

たとえば、次のようなコマンドを実行したとします。

protoc --proto_path=src --ruby_out=build/gen src/foo.proto src/bar/baz.proto

コンパイラはsrc/foo.protosrc/bar/baz.protoの入力から、build/gen/foo_pb.rbbuild/gen/bar/baz_pb.rbの2つの出力ファイルを生成します。 コンパイラは必要に応じてディレクトリbuild/gen/barを自動的に作成しますが、buildまたはbuild/genは作成しません。それらはすでに作成済みである必要があります。

パッケージ

.protoファイルで定義されたパッケージ名は生成されたメッセージ型のモジュール構造を生成するために使われます。

このようなファイルがある場合、

package foo_bar.baz;

message MyMessage {}

プロトコルコンパイラはFooBar::Baz::MyMessageという名前のメッセージ型を出力します。

メッセージ型

このようなシンプルなメッセージ宣言に対して、

message Foo {}

プロトコルバッファコンパイラはFooという名前のクラスを生成します。生成されたクラスはObjectクラスを継承しています(protoで共通の基底クラスはありません)。C++やJavaとは違って、Rubyが生成したコードは.protoファイルのoptimize_forオプションの影響を受けません。実際のところ、Rubyコードの最適化対象は常にコードサイズとなっています

Fooサブクラスを作成すべきではありません。生成されたクラスはサブクラス用に設計されていないため、「脆弱な基底クラス」の問題2を引き起こす可能性があります。

Rubyのメッセージクラスは各フィールドに対するアクセサを定義します。また、次にあげる標準メソッドを提供します。

  • Message#dup, Message#clone: このメッセージのシャローコピーを行い、新しく作られたコピーを返します。
  • Message#==: 2つのメッセージの完全な等価比較を行います。
  • Message#hash: メッセージの値のシャローハッシュ値を計算します。
  • Message#to_hash, Message#to_h: メッセージオブジェクトをRubyのHashオブジェクトに変換します。最上位のメッセージだけが変換されます。
  • Message#inspect: このメッセージを表す可読性のある表現の文字列を返します。
  • Message#[], Message#[]=: 文字列の名前でフィールドを取得または設定します。将来的には、これはおそらくget/set拡張にも使用されるでしょう。

このメッセージクラスはまた次のような静的メソッドも定義します。(通常のメソッドだと.protoファイルで定義されるものと競合する可能性があるので、なるべく静的メソッドで実装するようにされています)

  • Message.decode(str): このメッセージのプロトコルバッファバイナリをデコードし、新しいインスタンスを返します。
  • Message.encode(proto): このクラスのメッセージオブジェクトをバイナリ文字列にシリアライズします。
  • Message.decode_json(str): このメッセージのJSON文字列をデコードして、新しいインスタンスとして返します。
  • Message.encode_json(proto): このクラスのメッセージオブジェクトをJSON文字列にシリアライズします。
  • Message.descriptor: このメッセージオブジェクトのGoogle::Protobuf::Descriptorを返します。

このメッセージを作る場合、コンストラクタのフィールドで簡単に初期化できます。次にメッセージの作成と使用の例を示します。

message = MyMessage.new(:int_field => 1,
                        :string_field => "String",
                        :repeated_int_field => [1, 2, 3, 4,
                        :submessage_field => SubMessage.new(:foo => 42))
serialized = MyMessage.encode(message)

message2 = MyMessage.decode(serialized)
raise unless message2.int_field == 1

メッセージは別のメッセージの中にも宣言できます。 例: message Foo { message Bar { } }

この場合、BarクラスはFooクラスの中で宣言されるので、Foo::Barとして参照できます。

フィールド

メッセージ型の各フィールドには、フィールドに対してget/setするアクセサメソッドがあります。したがって、fooフィールドを指定すると、次のように記述できます。

message.foo = get_value()
print message.foo

フィールドを値を入れるたびに、そのフィールドで宣言された型に対して値が型チェックされます。 値の型が間違っている(または範囲外の)場合、例外が発生します。

単数フィールド (proto3)

単数のプリミティブなフィールド(数値、文字列、真偽値)の場合、セットする値はそのフィールドへの正しい型であるべきであり、適切な範囲内でなくてはなりません。

  • 数値: 値はFixnumBignumもしくはFloatであるべきです。 セットする値はそのフィールドで正確に表現できる型である必要があります。なのでint32のフィールドに1.0を入れても構いませんが、1.2は入れられません。
  • 真偽型フィールド: 値はtrueもしくはfalseでなくてはなりません。 他の値の場合、暗黙的にtrue/falseに変換されることはありません。
  • バイト配列フィールド: セットする値はStringオブジェクトである必要があります。 プロトコルバッファライブラリは文字列を複製し、ASCII-8BITエンコーディングに変換し、freezeします。
  • 文字列フィールド: セットする値はStringオブジェクトである必要があります。 プロトコルバッファライブラリは文字列を複製し、UTF8エンコーディングに変換し、freezeします。

自動変換を実行してくれるようおな、自動の#to_s, #to_iなどの呼び出しはありません。もし必要であれば、自分で値を変換する必要があります

proto3は、単数非メッセージフィールドが明示的に設定されているかどうか、確認する方法を提供しないため、戻り値が0 / false / ""である場合は、その値がどこかで設定されたかデフォルト値のままということになります。

単数メッセージフィールド

フィールドの型がメッセージの場合、未設定であればフィールドはnilを返すため、メッセージが明示的に設定されたかどうかをいつでも確認できます。 値を明示的にnilに設定して、そのフィールドをクリアすることもできます。

if message.submessage_field.nil?
  puts "submessage フィールドは未設定です"
else
  message.submessage_field = nil
  puts "submessageフィールドはクリアされました"
end

メッセージをセットするには、正しい型で生成されたメッセージオブジェクトである必要があります。
メッセージをセットする際に、メッセージを循環させることもできます。

例です。

// foo.proto
message RecursiveMessage {
  RecursiveMessage submessage = 1;
}

# test.rb

require 'foo'

message = RecursiveSubmessage.new
message.submessage = message

もしこれをシリアライズすると、ライブラリは循環を検知して、シリアライズを失敗させます。

Repeatedフィールド

RepeatedフィールドはGoogle::Protobuf::RepeatedFieldのカスタムクラスを使って表現されます。このクラスはRubyのArrayのように振る舞い、Enumerableモジュールをmix-inしています。通常のRuby配列とは異なり、RepeatedFieldは特定の型から初期化され、配列のメンバーは全て正しい型でなくてはなりません。型と範囲はメッセージフィールドと同様にチェックされます。

int_repeatedfield = Google::Protobuf::RepeatedField.new(:int32, [1, 2, 3])

# TypeErrorが発生
int_repeatedfield[2] = "not an int32"

# RangeErrorが発生
int_repeatedfield[2] = 2**33

message.int32_repeated_field = int_repeatedfield

# 許可されない。通常のRuby配列は型を強制できない
message.int32_repeated_field = [1, 2, 3, 4]

# これは通る。様子が安全な型の配列にコピーされるため
message.int32_repeated_field += [1, 2, 3, 4]

RepeatedField型は、通常のRuby配列と同じメソッドをすべてサポートしています。repeated_field.to_aでいつものArrayクラスに変換できます。

map フィールド

mapフィールドは、RubyのHashクラスのように動作する特別なクラス(Google::Protobuf::Map)を使って表されます。 通常のHashとは異なり、Mapは決まった型のキーと値で初期化され、Mapのすべてのキーと値は正しい型でなくてはなりません。 型と範囲は、メッセージクラスのフィールドやRepeatedFieldの要素と同様にチェックされます。

int_string_map = Google::Protobuf::Map.new(:int32, :string)

# mapに要素がない場合、nilを返します
print int_string_map[5]

# 値は文字列でなければいけないのでTypeErrorが発生します
int_string_map[11] = 200

# OK
int_string_map[123] = "abc"

message.int32_string_map_field = int_string_map

列挙型

Rubyには組み込みの列挙型がないため、値を定義する定数を持つ各列挙型のモジュールを作成します。

次のような.protoファイルがある場合:

message Foo {
  enum SomeEnum {
    VALUE_A = 0;
    VALUE_B = 5;
    VALUE_C = 1234;
  }
  optional SomeEnum bar = 1;
}

次のように列挙型を参照できます。

print Foo::SomeEnum::VALUE_A  # => 0
message.bar = Foo::SomeEnum::VALUE_A

列挙型のフィールドには数字もしくはシンボルをセットすることができます。 値を読み戻すときに、列挙型の値が既知の場合はシンボルになり、未知の場合は数値になります。 proto3では列挙型が取りうる値の集合は制限されていないので、事前に定義されていなくても、列挙型フィールドに任意の数字を割り当てることができます。

message.bar = 0
puts message.bar.inspect  # => :VALUE_A
message.bar = :VALUE_B
puts message.bar.inspect  # => :VALUE_B
message.bar = 999
puts message.bar.inspect  # => 999

# RangeError: 列挙型フィールドに対する未知のシンボル
message.bar = :UNDEFINED_VALUE

# 列挙型の値でswitchさせるのが便利です
case message.bar
when :VALUE_A
  # ...
when :VALUE_B
  # ...
when :VALUE_C
  # ...
else
  # ...
end

Enumモジュールは、次のユーティリティメソッドも定義します。

  • Enum#lookup(number): 数値からラベルを探して返します。もし存在しなければnilを返します。数値に
  • 相当するラベルが複数ある場合、最初に定義されたラベルを返します。
  • Enum#resolve(symbol): ラベルから数値を返します。存在しない場合はnilを返します。
  • Enum#descriptor: この列挙型のdescrptorを返します。

oneof

次のようなoneofを持ったメッセージ型があるとします。

message Foo {
  oneof test_oneof {
     string name = 1;
     int32 serial_number = 2;
  }
}

Fooに対応するRubyのクラスには、通常のフィールドと同じようなアクセサメソッドを持つnameserial_numberというメンバー変数が含まれます。 ただし、通常のフィールドとは異なり、一度に1つまでしか、oneofのフィールドにには設定できません。そのため、あるフィールドに値を入れると他のフィールドはクリアされます。

message = Foo.new

# フィールドにはデフォルト値が入っている
raise unless message.name == ""
raise unless message.serial_number == 0
raise unless message.test_oneof == nil

message.name = "Bender"
raise unless message.name == "Bender"
raise unless message.serial_number == 0
raise unless message.test_oneof == :name

# serial_numberにセットするとnameがクリアされる
message.serial_number = 2716057
raise unless message.name == ""
raise unless message.test_oneof == :serial_number

# serial_numberにnilを入れるとoneofフィールドがクリアされる
message.serial_number = nil
raise unless message.test_oneof == nil
  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

Ruby on Rails 『NO FILEのマイグレーションファイルを削除する方法』

状態

$ rails db:migrate 実行後のマイグレーションファイルを削除してしまい、NO FILEのファイルが出来上がった。

解決方法

migrationの状態を確認。

$ rails db:migrate:status

下記のように、migrationの一覧が表示されます。
upが$ rails db:migrate実行後。
downが$ rails db:migrate実行前。

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20190915023701  Create posts
   up     20190915065320  ********** NO FILE **********
  down    20190915080932  Devise create users

Migration IDをコピペし、ダミーファイルを作成。(後で削除するので、hogeのところは適当でいいです。)

$ touch db/migrate/20190915065320_hoge.rb

下記のように記述したダミーファイルを作っておく。

20190915065320_hoge.rb
class Hoge < ActiveRecord::Migration[6.0]
  def change
  end
end

$ rails db:migrate:status を実行してMigration Nameが付与されていることを確認。

Status   Migration ID    Migration Name
--------------------------------------------------
   up     20190915023701  Create posts
   up     20190915065320  Hog
  down    20190915080932  Devise create users

VERSION にMigration ID を代入して下コマンドを実行。

$ rails db:migrate:down VERSION=20190915065320

これでdown(rails db:migrate実行前)になります。

Status   Migration ID    Migration Name
--------------------------------------------------
   up     20190915023701  Create posts
  down    20190915065320  Hoge
  down    20190915080932  Devise create users

↓ 必要なくなったので削除。

$ rm db/migrate/db/migrate/20190915065320_hoge.rb

あとは先程と同じく、$ rails db:migrate:status を実行すると消えてるのが確認できる。

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20190915023701  Create posts
  down    20190915080932  Devise create users

 
以上がNO FILEのマイグレーションファイルを削除する方法でした。

そもそも、つまらないミスをしなければこのような作業はしなくていいのですが...笑

参考資料

railsのrakeで作成したmigrationファイルとstatus履歴を削除する

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

AtCoder初参加者のためのPOINTメモ

この記事を書くまでの経緯

5日ぐらい前に「AtCoderはじめます!」と宣言したところ、とってもありがたいことにインターン先の社員さんやAtCodeをすでにやっている友達が「こんな事前準備をしといた方がいいよ!」とアドバイスしてくれました。

そこで、これからAtCoderをはじめる自分と同じ状況の方に向けて、もらったアドバイスの要点を書いていきたいと思います!

この記事で「これやってたら一瞬だったのにやってなかったばかりにめちゃめちゃ時間を使ってしまった(もしくは解けなかった)」ということがなるべくなくなるようにしたい所存です。

初参戦するまでにやっておくこと

標準入力・出力のやり方の確認

AtCoderで問題を解くときにおそらくすべての問題で使うのが標準入力と標準出力です。

しかし、わたしが標準入力・出力という言葉を始めて聞いたときは正直あまりピンとこないというのが素直な感想でした。

おそらく普段プログラミングをしているときにあまり使う機会はないのではないでしょうか。

そのため、時間を事前に確認しておかないとめちゃめちゃ時間をくってしまうので注意です。

ただ、言語によって多少コードの書き方が異なるので実際のやり方はqiitaの記事などで調べてみていただけたらと思います。

ちなみにRubyだと数字を一つ取得するとき(標準入力)の書き方はこんな感じです。

atcoder.rb
num = gets.to_i

If文やfor文の書き方の確認

また、if文・for文もAtCoderで問題を解く際によく使います。

これは普段の開発中にもよく使うのであまり問題はないかもしれませんが、本番中に「あれ?」とならないようにするためにも確認しておいた方がよいとのことでした。

ちなみに、ちょっとメタい話になってしまいますが、A・B問題ではfor文の重複はあまり問題視されないもののC問題からは扱うデータの量が大きくなるためfor文の重複をすると「処理に時間かかりすぎ!」言われることが多いそうです。

なのでfor文はできるたけ少なくしてコードを書いた方がよいらしいので頭の片隅に置いておきたいです。

過去問を解く

最後に、AtCoderのサイトで今までに出た問題が公開されているので自分の参加する言語で過去問を見ておくとよいとのことでした。

↓過去問が見れるページ

https://kenkoooo.com/atcoder/#/table/

過去問のページでは回答・解説はもちろん、他の人が実際に回答したものも見れるためそちらも参考にするとよいとのことでした。

また、過去問を解くのにこちらのQiitaの記事もおすすめとのこと。

https://qiita.com/drken/items/fd4e5e3630d0f5859067

まだざっとしか見れていませんが解説がめちゃめちゃ分かりやすいです。

ただ、回答の言語がC#?C+?(ちょっとどっちか忘れてしまいました)なので、それ以外で参加される方はコードの部分は参考程度に見て、解き方とやコメントの部分をしっかり読んで理解しておくとよいとのことです。

いざ出陣!Atcoderの問題を解くときにやること

問題を解くときは自分のローカルエディタで動作確認

Atcoderでは一度自分の回答を提出してしまうと、間違えていたときに結果が悪くなってしまうペナルティーがあるため、提出前にローカルエディタで確認しておくべし、とのことでした。

提出前に問題で与えられた入力例を全部確認!

また、エディタを使うのと同じ理由で問題で与えられた入力例はすべてチェックすべし!とのことでした。

特にAtCoderで一番簡単なA問題は1,2行で書けることもあるため、すべての入力例での動作確認するのが面倒くさくてそのまま提出したくなってしまいますが、しっかり確認していきたいです。

おまけ

もちろん個人のレベルにもよるのですが、インターン先の社員さんに「初参戦なら2問解けたら万々歳!」と言っていただけたので、まずは今日の初参戦で2問解ききることを目標に頑張りたいと思います!

それではお読みいただきありがとうございました!

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

【Ruby】文字列をハッシュ化

関連

サンプルコード

# frozen_string_literal: true

require 'digest'

plain_text = 'password'

puts 'MD5:    ' + Digest::MD5.hexdigest(plain_text)
puts 'RMD160: ' + Digest::RMD160.hexdigest(plain_text)
puts 'SHA1:   ' + Digest::SHA1.hexdigest(plain_text)
puts 'SHA256: ' + Digest::SHA256.hexdigest(plain_text)
puts 'SHA384: ' + Digest::SHA384.hexdigest(plain_text)
puts 'SHA512: ' + Digest::SHA512.hexdigest(plain_text)

# => MD5:    5f4dcc3b5aa765d61d8327deb882cf99
#    RMD160: 2c08e8f5884750a7b99f6f2f342fc638db25ff31
#    SHA1:   5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8
#    SHA256:  5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8
#    SHA284: a8b64babd0aca91a59bdbb7761b421d4f2bb38280d3a75ba0f21f2bebc45583d446c598660c94ce680c47d19c30783a7
#    SHA512: b109f3bbbc244eb82441917ed06d618b9008dd09b3befd1b5e07394c706a8bb980b1d7785e5976ec049b46df5f1326af5a2ea6d103fd07c95385ffab0cacbc86

参考

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

【Ruby】AWS SSMパラメータストアの値を取得する

関連

準備

aws-sdk-ssmをインストールする。

下記コマンドを実行するか、Gemfileに追記する。

aws-sdkをインストールしてもいいが、結構サイズが大きいので基本的には必要なものだけインストールするようにした方がいいと思う。

gem install aws-sdk-ssm

or

gem 'aws-sdk-ssm'

サンプルコード

今回はAWS SSM パラメータストアに以下のようなパラメータが作成してある。

パラメータ名
sample-parameter HOGEHOGE!!
require 'aws-sdk-ssm'

# 認証情報
# `~/.aws/credentials`に認証情報が設定されている場合、
# または環境変数`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`に認証情報が設定されている場合は
# `access_key_id`と`secret_access_key`は不要
credentials = {
  access_key_id: 'xxxxxxxxxxxxxxxxxxxx',
  secret_access_key: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
  region: 'ap-northeast-1'
}

# SSMクライアントを生成
ssm_client = Aws::SSM::Client.new(credentials)

# リクエスト
request = {
  name: 'sample-parameter', # パラメータ名
  with_decryption: true     # 暗号化されている場合は復号し、暗号化されていない場合は何もしない
}
response = ssm_client.get_parameter(request)

pp response
# => #<struct Aws::SSM::Types::GetParameterResult
#     parameter=
#      #<struct Aws::SSM::Types::Parameter
#       name="sample-parameter",
#       type="SecureString",
#       value="HOGEHOGE!!",
#       version=1,
#       selector=nil,
#       source_result=nil,
#       last_modified_date=2019-09-13 00:28:02 +0900,
#       arn="arn:aws:ssm:ap-northeast-1:xxxxxxxxxxxx:parameter/sample-parameter">>

puts response.parameter.value
# => HOGEHOGE!!

参考

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

[学習確認用] Ruby 配列の中からランダムな値を一つ抜き取る

配列の中からランダムな値を一つ抜き出す。

number = [1, 3, 4, 5, 11, 12]
nunber_count = number.count
puts number_count

1.times do
     puts number[rand(number_count)]
end

number_count の所は数字でもいけるが配列の数より大きい値を入れると空の値を抜き出してしまうから今回は配列の数をランダムの数に入れている。

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

#Ruby で 処理速度を平均化したい。sleep で必ず一定時間以上かかる処理を作る。

処理にかかった時間を得て、最低時間に満たなかったら足りない分を sleep させる

def some_action
  # some action
end

start_time = Time.now.to_f

some_action

done_time = Time.now.to_f

under_time = 0.1

diff_time = under_time - (done_time - start_time)

sleep diff_time if diff_time.positive? }

ベンチマークの例

require 'benchmark'

[*1..10].map { Benchmark.realtime { start_time = Time.now.to_f; sleep rand(0.01..0.10); done_time = Time.now.to_f; under_time = 0.1; diff_time = under_time - (done_time - start_time); sleep diff_time if diff_time.positive?; } }

# => [0.10333599988371134,
 0.10502699995413423,
 0.10417600022628903,
 0.10169599996879697,
 0.1034500002861023,
 0.10172299994155765,
 0.10186100006103516,
 0.10312700038775802,
 0.10354600008577108,
 0.10065300017595291]

Original by Github issue

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

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

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で続きを読む