20200711のRubyに関する記事は16件です。

TECH CAMP学習 個人アプリ作成③

ユーザー情報編集機能の追加

まずはターミナルにて以下のコードを入力します。

terminal.
$ rails g controller users

続いてルーティングで下記を入力します。

routes.rb
 devise_for :users
 root "photos#index"
 resources :users, only: [:edit, :update]

resourcesの意味は日本語では資源をいう意味で、usersの中で編集と更新の機能を利用するという解釈でよいと思います。

続いてコントローラーを編集します。

users_controller.rb
def edit
end

def update
   if current_user.update(user_params)
      redirect_to root_path
   else
      render :edit
   end
end

private

def user_params
   params.require(:user).permit(:name, :email)
end

①current_userはdeviseのヘルパーメソッドで、ログイン中のユーザー情報を取得できます。
②redirect_toは本来受け取ってるパスとは別のパスへ転送します。上記ではroot_pathに転送するということです。
③上記が失敗した場合、renderはeditを呼び出します。
④privateはプライベートメソッドで、クラス外から呼び出すことのできないメソッドです。
メリットしては
・classの外部から呼び出されたら困るメソッドを隔離
・可読性、classの外部から呼び出されたメソッドを探すときにprivate以下の部分は目を通さなくてよくなる。
⑤user_params以下はストロングパラメーターで、指定したキーを持つパラメーターのみを受け取るようにするものです。
⑥ストロングパラメーターの中でuserを要求(require)して:name,:emailの許可(permit)を得る。詳細を確認するにはupdateの直後にbinding.pryを使用する。

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

Rails5でECサイトを作る⑦ ~Address、Genreモデル編~

はじめに

架空のベーカリーで買い物できるECサイトを作るシリーズ、Rails5でECサイトを作る⑥の続きです。
今回はアプリ本体の実装に戻り、購入したパンの送り先住所を管理するAddressモデルと、商品をまとめるGenreモデルの周辺を作っていきます。

ソースコード

https://github.com/Sn16799/bakeryFUMIZUKI

Modelのアソシエーション

association.jpg

Address

controller

app/controllers/addresses_controller.rb
class AddressesController < ApplicationController

  before_action :authenticate_customer!
  before_action :set_customer

  def edit
    @address = Address.find(params[:id])
    if @address.customer_id != current_customer.id
      redirect_back(fallback_location: root_path)
      flash[:danger] = 'お探しのページにアクセスできません。'
    end
  end

  def index
    @address = Address.new
    @addresses = @customer.addresses
  end

  def create
    @address = @customer.addresses.build(address_params)
    if @address.save
      flash[:success] = '新しい住所を登録しました!'
      redirect_to addresses_path
    else
      @addresses = @customer.addresses
      flash[:danger] = '入力内容をご確認ください。各入力欄は2文字以上で記入されていますか?'
      render :index
    end
  end

  def update
    @address = Address.find(params[:id])
    if @address.update(address_params)
      flash[:success] = '住所情報を更新しました!'
      redirect_to addresses_path
    else
      flash[:danger] = '入力内容をご確認ください。各入力欄は2文字以上で記入されていますか?'
      render :edit
    end
  end

  def destroy
    @address = Address.find(params[:id])
    @address.destroy
    flash[:info] = '登録した住所を削除しました。'
    redirect_to addresses_path
  end

  private

  def set_customer
    @customer = current_customer
  end

  def address_params
    params.require(:address).permit(:post_code, :address, :addressee)
  end
end

index画面

app/views/addresses/index.html.erb
<div class="col-lg-10 offset-lg-1 offset-1 space">
  <div class="container-fluid">
    <h2>
      <span style="display: inline-block;">配送先</span>
      <span style="display: inline-block;">登録/一覧</span>
    </h2>
  </div>
  <!-- 新規住所フォーム -->
  <div class="container space">
    <h3>新しく住所を登録</h3>
    <%= form_with(model: @address, local: true, class: 'container-fluid') do |f| %>
    <div class="form-group">
      <%= f.label :郵便番号(ハイフンなし) %>
      <%= f.text_field :post_code, class: 'form-control' %>
    </div>
    <div class="form-group">
      <%= f.label :住所 %>
      <%= f.text_field :address, class: 'form-control' %>
    </div>
    <div class="form-group">
      <%= f.label :宛名 %>
      <%= f.text_field :addressee, class: 'form-control' %>
    </div>
    <div class="action w-25 offset-lg-11">
      <%= f.submit '登録する', class: 'btn btn-danger' %>
    </div>
    <% end %>
  </div>

  <!-- 住所一覧 -->
  <div class="container space">
    <h3>住所一覧</h3>
    <div class="row">
      <div class="col-lg-3">
        <strong>郵便番号</strong>
      </div>
      <div class="col-lg-3">
        <strong>住所</strong>
      </div>
      <div class="col-lg-3">
        <strong>宛名</strong>
      </div>
    </div>
    <% @addresses.each do |address| %>
    <div class="row">
      <div class="col-lg-3">
        <%= address.post_code %>
      </div>
      <div class="col-lg-3">
        <%= address.address %>
      </div>
      <div class="col-lg-3">
        <%= address.addressee %>
      </div>
      <div class="col-lg-3">
        <%= link_to "編集する", edit_address_path(address), class:"btn btn-danger" %>
        <%= link_to "削除する", address_path(address), method: :delete, class:"btn btn-danger" %>
      </div>
    </div>
    <% end %>
  </div>
</div>

edit画面

app/views/addresses/edit.html.erb
<div class="col-lg-10 offset-lg-1 space">
  <div class="container">
    <h2>配送先編集</h2>
    <%= form_with(model: @address, local: true) do |f| %>
    <div class="form-group">
      <%= f.label :郵便番号(ハイフンなし) %>
      <%= f.text_field :post_code, autofocus: true, class: 'form-control' %>
    </div>
    <div class="form-group">
      <%= f.label :住所 %>
      <%= f.text_field :address, class: 'form-control' %>
    </div>
    <div class="form-group">
      <%= f.label :宛名 %>
      <%= f.text_field :addressee, class: 'form-control' %>
    </div>
    <div class="action w-25 offset-lg-11">
      <%= f.submit '更新する', class: 'btn btn-danger' %>
    </div>
    <% end %>
  </div>
</div>

Genre

controller

app/controllers/genres_controoler.rb
class GenresController < ApplicationController

  def show
    @genre = Genre.find(params[:id])
    @genres = Genre.where(validity: true)
    @products = @genre.products.page(params[:page]).per(9)
  end

  private

  def genre_params
    params.require(:genre).permit(:name,:id)
  end
end

index

ファイル名はindexとしていますが、商品ジャンルの一覧画面というものはなく、いくつかのページで部分テンプレートとしてこのファイルを呼び出し、ジャンルごとの絞り込み表示をできるようにします。実質的にはサイドバーです。

app/views/genres/_index.html.erb
<div id="sidebar" class="col-lg-2">
  <table class='table'>
  <thead>
    <tr>
      <th>ジャンル検索</th>
    </tr>
  </thead>
  <tbody>
    <% genres.each do |genre| %>
    <tr>
      <td><%= link_to  genre.name, genre_path(genre) %></td>
    </tr>
    <% end %>
    <tr>
      <td><%= link_to "⇒ すべての商品を見る", products_path, class: 'dark-blue-letter' %></td>
    </tr>
  </tbody>
</table>
</div>

show画面

商品ジャンルの詳細画面は、例えば「食パン」「総菜パン」といったジャンルに属した商品を絞り込み表示する画面にします。

app/views/genres/show.html.erb
<div class="col-lg-10 space">
  <div class="container">
  <h2><%= @genre.name %>一覧(全<%= @genre.products.count %>種)</h2>
</div>
  <div class="container">
    <% @genre.products.each do |gp| %>
      <%= render 'products/box', product: gp %>
    <% end %>
  </div>
</div>
<%= render 'genres/index', genres: @genres %>

※部分テンプレート
app/views/products/_box.html.erb
<div class="col-lg-4 product-box space">
  <%= link_to product_path(product) do %>
    <div class="row">
      <h4><%= product.name %></h4>
    </div>
    <div class="row">
      <%= attachment_image_tag(product, :image, :fill, 220, 180, fallback:  'no_img.jpg') %>
    </div>
  <% end %>
  <div class="row">
    <h4><%= price_include_tax(product.price) %></h4>
  </div>
</div>

データベース上は税抜価格しか保存されていないのですが、画面上の表示では税込価格にしたい。そんな時は、ヘルパーメソッドを利用します。

ヘルパー
app/helpers/application_helper.rb
  def price_include_tax(price)
    price = price * 1.08
    "#{price.round}円"
  end

後記

AddressもGenreも、いたって標準的なCRUD機能のみなので、ほとんど迷わずにできたと思います。難しくなるのはこれから……。注文機能のあるOrderモデルは、以前作った時にはかなり苦労させられましたが、レスポンシブ対応の画面にして、より簡潔なコードで再現できるのでしょうか? 次回へ続く!

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

Ruby on Rails アプリケーション新規作成コマンド

アプリケーション新規作成

アプリケーションを作成するコマンドは
rails new アプリケーション名

例:
rails new myapp

オプション指定

アプリケーション新規作成時にオプションを指定することができる。

Rails バージョン指定

バージョンを指定してアプリケーションを作成する場合は
rails バージョン new アプリケーション名

例:
rails _5.2.3_ new myapp

データベース指定

データベースを指定してアプリケーションを作成する場合は
rails new アプリケーション名 -d データベースの種類

例:
rails new myapp -d mysql

データベースの指定がない場合、データベースはデフォルトのsqliteとなる。

テストを作成しない

テストを作成せずにアプリケーションを作成する場合は
rails new アプリケーション名 -T

.gitignoreファイルを作成しない

.gitignoreファイルを作成せずにアプリケーションを作成する場合は
rails new アプリケーション名 -G

その他のオプション

上記以外にも様々なオプションが存在する。
次のコマンドでヘルプを表示し、全てのオプションコマンドとその内容を確認できる。
rails new -h

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

【AWS SDK】EC2自動構築用スクリプト

目標

AWS SDK(AWS SDK for Ruby)を利用してEC2自動構築を行う。

はじめに

AWS SDKの基礎知識をまとめた後、実際の動作をAWS SDK for Rubyを利用したEC2自動構築で試してみます。

AWS SDK

AWSが提供しているAPIの一種で、AWSがサポートする様々なプログラミング言語にライブラリとしてインポートして利用します。
現在以下の言語がサポートされており、各種プログラムからのAWSリソース操作を可能にします。

・C++
・Go
・Java
・JavaScript
・.NET
・Node.js
・PHP
・Python
Ruby(本記事ではこれを利用)

AWS CLI、SDK利用時の認証

AWS CLI(OSコマンドライン上やシェルスクリプト、PowerShell上で利用)、AWS SDK(各種プログラミング言語上で利用)を利用する際には認証情報が必要となります。
認証情報の設定方法には以下3つの方法が存在しており、参照優先度が異なります。

項番 優先度 認証情報の設定箇所
1 OS環境変数
2 認証情報ファイル
3 インスタンスプロファイル(IAMロール認証)

①OS環境変数
環境変数のAWS_ACCESS_KEY_ID および AWS_SECRET_ACCESS_KEYという環境変数を利用して認証情報の設定が可能です。
認証設定方法の中で最も優先度が高いです。
例えば以下のように設定を行います。

# For LINUX
export AWS_ACCESS_KEY_ID=your_access_key_id
export AWS_SECRET_ACCESS_KEY=your_secret_access_key

# For Windows
set AWS_ACCESS_KEY_ID=your_access_key_id
set AWS_SECRET_ACCESS_KEY=your_secret_access_key

②認証情報ファイル
OSユーザのホームディレクトリ内の「aws」ディレクトリに存在する「credentials」というファイルが認証情報ファイルです。
そのファイル内のaws_access_key_idとaws_secret_access_keyにアクセスキーとシークレットアクセスキーを投入します。

# 認証情報ファイル
~/.aws/credentials
[default]
aws_access_key_id = your_access_key_id
aws_secret_access_key = your_secret_access_key

③インスタンスプロファイル
インスタンスプロファイルとは、IAMロールを利用した認証で利用される実行環境ことです。
IAMロール作成時に自動作成されます。
IAMロールを納めるための容器であり、EC2にアタッチする時に必要なコネクターの役割をするようです。
IAMロールを利用するこの認証方法はAWS SDK及びAWS CLI利用時のベストプラクティスであり、アクセスキーとシークレットアクセスキーを利用する上記2つの方法より認証情報漏洩のリスクが軽減されるため推奨されております。
ちなみに、認証方法としての優先度が最も低いため、環境変数や認証情報ファイル内の情報に注意を払う必要があります。

インスタンスプロファイルの詳細に関しては、以下サイトが参考になりました。
EC2にIAMRole情報を渡すインスタンスプロファイルを知っていますか?

作業の流れ

項番 タイトル
1 AWS SDK for Rubyのセットアップ
2 EC2自動構築用スクリプトのセットアップ
3 動作検証

手順

1.AWS SDK for Rubyのセットアップ

①AWS SDK for Rubyインストール
かなり時間かかるので注意…

gem install aws-sdk

②アクセスキーとシークレットアクセスキーの発行
アクセスキーとシークレットアクセスキーが未発行の場合は新規作成する必要があります。
手順は以下を参考
【AWS CLI】Red Hat Enterprise Linux 8でAWS CLIを使用可能にする(3. アクセスキーIDとシークレットアクセスキーの発行)

③認証情報ファイルの編集
今回はAWS SDKの認証情報を認証情報ファイル内に設定する形で保存します。
認証情報ファイル~/.aws/credentials内のaws_access_key_idaws_secret_access_keyにアクセスキーとシークレットアクセスキーを記載してください。

# 認証情報ファイル編集
vi ~/.aws/credentials
[default]
aws_access_key_id = your_access_key_id
aws_secret_access_key = your_secret_access_key

2.EC2自動構築用スクリプトのセットアップ

①EC2自動構築用スクリプトをAWS SDKをセットアップした環境に配備
スクリプト内の各種環境依存の変数(<ami_id><keypair_name><security_group_id><instance_type><az_name><subnet_id><userdata_pass>)は環境に従って書き換えをします。

また、<userdata_pass>に指定したファイルの中にはEC2に設定したいユーザデータ処理を書いておきます。

ファイル名: ec2_create.rb
# **********************************************************************************
# 機能概要: EC2の自動構築
# スクリプト用法: ruby <スクリプトパス> <インスタンス名>
# **********************************************************************************

unless ARGV.size() == 1
  puts "The number of arguments is incorrect."
  exit
end

require 'aws-sdk'
require 'base64'

# EC2の構成要素定義(以下変数を環境に応じて編集)
image_id = '<ami_id>'                         # AMIID
key_name = '<keypair_name>'                   # キーペア名
security_group_ids = '<security_group_id>'    # セキュリティグループID
instance_type = '<instance_type>'             # インスタンスタイプ
availability_zone = '<az_name>'               # 利用AZ
subnet_id = '<subnet_id>'                     # 利用サブネットID
user_data = '<userdata_pass>'                 # ユーザデータのファイルパス

# ユーザデータ設定
if File.exist?(user_data)
  file = File.open(user_data)
  script = file.read
  file.close
else
  script = ''
end
encoded_script = Base64.encode64(script)

# EC2リソース操作用インスタンス作成
ec2 = Aws::EC2::Resource.new

# EC2作成実施
instance = ec2.create_instances({
  image_id: image_id,
  min_count: 1,
  max_count: 1,
  key_name: key_name,
  security_group_ids: [security_group_ids],
  user_data: encoded_script,
  instance_type: instance_type,
  placement: {
    availability_zone: availability_zone
  },
  subnet_id: subnet_id
})

# インスタンスが利用可能になるまで待機
ec2.client.wait_until(:instance_running, {instance_ids: [instance[0].id]})

# インスタンス名(Nameタグ)を付与
instance_name = ARGV[0]
instance.batch_create_tags({ tags: [{ key: 'Name', value: instance_name }]})

puts "#{instance_name}(#{instance[0].id})が作成されました!"

3.動作検証

<前提>
以下のパラメータでEC2を作成します。

# EC2の構成要素定義(以下変数を環境に応じて編集)
image_id = 'ami-067152a7c26866dcb'        # AMIID
key_name = 'mykeypair'                    # キーペア名
security_group_ids = 'sg-64a59718'        # セキュリティグループID
instance_type = 't2.medium'               # インスタンスタイプ
availability_zone = 'ap-northeast-1a'     # 利用AZ
subnet_id = 'subnet-41d23b09'             # 利用サブネットID
user_data = '/tmp/userdata'               # ユーザデータのファイルパス

また、EC2に設定予定のユーザデータは以下の内容

$ cat /tmp/userdata
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd

①スクリプト実行
インスタンス名を第一引数に指定してスクリプト実行します。

$ ruby ec2_create.rb test_server
test_server(i-0a4fd9cfe1613a8d6)が作成されました!

②結果確認
インスタンス名、インスタンスタイプ、サブネットID、キーペア名、AZ名、AMIIDがスクリプト内での設定値通り設定されています。
tempsnip.png

付与されているセキュリティグループもOK
tempsnip.png

ユーザデータも内容適切でした。
image.png

一応OSログイン確認と、ユーザデータが正常に実行されているかも確認してみます。

# httpdがユーザデータ実行により起動している
$ systemctl status httpd
● httpd.service - The Apache HTTP Server
   Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; vendor preset: disabled)
   Active: active (running) since Sat 2020-07-11 03:03:01 UTC; 12min ago
     Docs: man:httpd.service(8)
 Main PID: 21821 (httpd)
   Status: "Total requests: 0; Idle/Busy workers 100/0;Requests/sec: 0; Bytes served/sec:   0 B/sec"
   CGroup: /system.slice/httpd.service
           tq21821 /usr/sbin/httpd -DFOREGROUND
           tq21831 /usr/sbin/httpd -DFOREGROUND
           tq21832 /usr/sbin/httpd -DFOREGROUND
           tq21833 /usr/sbin/httpd -DFOREGROUND
           tq21834 /usr/sbin/httpd -DFOREGROUND
           mq21835 /usr/sbin/httpd -DFOREGROUND

Jul 11 03:03:01 ip-172-31-40-241.ap-northeast-1.compute.internal systemd[1]: Starting The Apache HTTP Server...
Jul 11 03:03:01 ip-172-31-40-241.ap-northeast-1.compute.internal systemd[1]: Started The Apache HTTP Server.

# また、enabledにもなっている
$ systemctl is-enabled httpd
enabled

全て設定値通りに作成されているのでOKとします!

参考サイト

各種サービスにおけるAWS SDK For Rubyを利用した実装例が確認できます。
AWS SDK for Ruby コード例

所感

AWS CLIは業務でよく使っていたのですが、AWS SDKを使ったのはこれが初でした。
AWS CLIよりコーディングとしての実装はかなり楽に感じます(例えばインスタンスのステータス待ちなどは、AWS CLIだとForループで何秒間隔で状態確認をするみたいな実装をしていたのですが、AWS SDKだとwait_untilメソッドを呼ぶだけの1行コードで済んでしまいました)。
今回は以上です。見て頂きありがとうございました。

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

【AWS SDK】EC2自動構築用スクリプト開発

目標

AWS SDK(AWS SDK for Ruby)を利用してEC2自動構築を行う。

はじめに

AWS SDKの基礎知識をまとめた後、実際の動作をAWS SDK for Rubyを利用したEC2自動構築で試してみます。

AWS SDK

AWSが提供しているAPIの一種で、AWSがサポートする様々なプログラミング言語にライブラリとしてインポートして利用します。
現在以下の言語がサポートされており、各種プログラムからのAWSリソース操作を可能にします。

・C++
・Go
・Java
・JavaScript
・.NET
・Node.js
・PHP
・Python
Ruby(本記事ではこれを利用)

AWS CLI、SDK利用時の認証

AWS CLI(OSコマンドライン上やシェルスクリプト、PowerShell上で利用)、AWS SDK(各種プログラミング言語上で利用)を利用する際には認証情報が必要となります。
認証情報の設定方法には以下3つの方法が存在しており、参照優先度が異なります。

項番 優先度 認証情報の設定箇所
1 OS環境変数
2 認証情報ファイル
3 インスタンスプロファイル(IAMロール認証)

①OS環境変数
環境変数のAWS_ACCESS_KEY_ID および AWS_SECRET_ACCESS_KEYという環境変数を利用して認証情報の設定が可能です。
認証設定方法の中で最も優先度が高いです。
例えば以下のように設定を行います。

# For LINUX
export AWS_ACCESS_KEY_ID=your_access_key_id
export AWS_SECRET_ACCESS_KEY=your_secret_access_key

# For Windows
set AWS_ACCESS_KEY_ID=your_access_key_id
set AWS_SECRET_ACCESS_KEY=your_secret_access_key

②認証情報ファイル
OSユーザのホームディレクトリ内の「aws」ディレクトリに存在する「credentials」というファイルが認証情報ファイルです。
そのファイル内のaws_access_key_idとaws_secret_access_keyにアクセスキーとシークレットアクセスキーを投入します。

# 認証情報ファイル
~/.aws/credentials
[default]
aws_access_key_id = your_access_key_id
aws_secret_access_key = your_secret_access_key

③インスタンスプロファイル
インスタンスプロファイルとは、IAMロールを利用した認証で利用される実行環境ことです。
IAMロール作成時に自動作成されます。
IAMロールを納めるための容器であり、EC2にアタッチする時に必要なコネクターの役割をするようです。
IAMロールを利用するこの認証方法はAWS SDK及びAWS CLI利用時のベストプラクティスであり、アクセスキーとシークレットアクセスキーを利用する上記2つの方法より認証情報漏洩のリスクが軽減されるため推奨されております。
ちなみに、認証方法としての優先度が最も低いため、環境変数や認証情報ファイル内の情報に注意を払う必要があります。

インスタンスプロファイルの詳細に関しては、以下サイトが参考になりました。
EC2にIAMRole情報を渡すインスタンスプロファイルを知っていますか?

作業の流れ

項番 タイトル
1 AWS SDK for Rubyのセットアップ
2 EC2自動構築用スクリプトのセットアップ
3 動作検証

手順

1.AWS SDK for Rubyのセットアップ

①AWS SDK for Rubyインストール
かなり時間かかるので注意…

gem install aws-sdk

②アクセスキーとシークレットアクセスキーの発行
アクセスキーとシークレットアクセスキーが未発行の場合は新規作成する必要があります。
手順は以下を参考
【AWS CLI】Red Hat Enterprise Linux 8でAWS CLIを使用可能にする(3. アクセスキーIDとシークレットアクセスキーの発行)

③認証情報ファイルの編集
今回はAWS SDKの認証情報を認証情報ファイル内に設定する形で保存します。
認証情報ファイル~/.aws/credentials内のaws_access_key_idaws_secret_access_keyにアクセスキーとシークレットアクセスキーを記載してください。

# 認証情報ファイル編集
vi ~/.aws/credentials
[default]
aws_access_key_id = your_access_key_id
aws_secret_access_key = your_secret_access_key

2.EC2自動構築用スクリプトのセットアップ

①EC2自動構築用スクリプトをAWS SDKをセットアップした環境に配備
スクリプト内の各種環境依存の変数(<ami_id><keypair_name><security_group_id><instance_type><az_name><subnet_id><userdata_pass>)は環境に従って書き換えをします。

また、<userdata_pass>に指定したファイルの中にはEC2に設定したいユーザデータ処理を書いておきます。

ファイル名: ec2_create.rb
# **********************************************************************************
# 機能概要: EC2の自動構築
# スクリプト用法: ruby <スクリプトパス> <インスタンス名>
# **********************************************************************************

unless ARGV.size() == 1
  puts "The number of arguments is incorrect."
  exit
end

require 'aws-sdk'
require 'base64'

# EC2の構成要素定義(以下変数を環境に応じて編集)
image_id = '<ami_id>'                         # AMIID
key_name = '<keypair_name>'                   # キーペア名
security_group_ids = '<security_group_id>'    # セキュリティグループID
instance_type = '<instance_type>'             # インスタンスタイプ
availability_zone = '<az_name>'               # 利用AZ
subnet_id = '<subnet_id>'                     # 利用サブネットID
user_data = '<userdata_pass>'                 # ユーザデータのファイルパス

# ユーザデータ設定
if File.exist?(user_data)
  file = File.open(user_data)
  script = file.read
  file.close
else
  script = ''
end
encoded_script = Base64.encode64(script)

# EC2リソース操作用インスタンス作成
ec2 = Aws::EC2::Resource.new

# EC2作成実施
instance = ec2.create_instances({
  image_id: image_id,
  min_count: 1,
  max_count: 1,
  key_name: key_name,
  security_group_ids: [security_group_ids],
  user_data: encoded_script,
  instance_type: instance_type,
  placement: {
    availability_zone: availability_zone
  },
  subnet_id: subnet_id
})

# インスタンスが利用可能になるまで待機
ec2.client.wait_until(:instance_running, {instance_ids: [instance[0].id]})

# インスタンス名(Nameタグ)を付与
instance_name = ARGV[0]
instance.batch_create_tags({ tags: [{ key: 'Name', value: instance_name }]})

puts "#{instance_name}(#{instance[0].id})が作成されました!"

3.動作検証

<前提>
以下のパラメータでEC2を作成します。

# EC2の構成要素定義(以下変数を環境に応じて編集)
image_id = 'ami-067152a7c26866dcb'        # AMIID
key_name = 'mykeypair'                    # キーペア名
security_group_ids = 'sg-64a59718'        # セキュリティグループID
instance_type = 't2.medium'               # インスタンスタイプ
availability_zone = 'ap-northeast-1a'     # 利用AZ
subnet_id = 'subnet-41d23b09'             # 利用サブネットID
user_data = '/tmp/userdata'               # ユーザデータのファイルパス

また、EC2に設定予定のユーザデータは以下の内容

$ cat /tmp/userdata
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd

①スクリプト実行
インスタンス名を第一引数に指定してスクリプト実行します。

$ ruby ec2_create.rb test_server
test_server(i-0a4fd9cfe1613a8d6)が作成されました!

②結果確認
インスタンス名、インスタンスタイプ、サブネットID、キーペア名、AZ名、AMIIDがスクリプト内での設定値通り設定されています。
tempsnip.png

付与されているセキュリティグループもOK
tempsnip.png

ユーザデータも内容適切でした。
image.png

一応OSログイン確認と、ユーザデータが正常に実行されているかも確認してみます。

# httpdがユーザデータ実行により起動している
$ systemctl status httpd
● httpd.service - The Apache HTTP Server
   Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; vendor preset: disabled)
   Active: active (running) since Sat 2020-07-11 03:03:01 UTC; 12min ago
     Docs: man:httpd.service(8)
 Main PID: 21821 (httpd)
   Status: "Total requests: 0; Idle/Busy workers 100/0;Requests/sec: 0; Bytes served/sec:   0 B/sec"
   CGroup: /system.slice/httpd.service
           tq21821 /usr/sbin/httpd -DFOREGROUND
           tq21831 /usr/sbin/httpd -DFOREGROUND
           tq21832 /usr/sbin/httpd -DFOREGROUND
           tq21833 /usr/sbin/httpd -DFOREGROUND
           tq21834 /usr/sbin/httpd -DFOREGROUND
           mq21835 /usr/sbin/httpd -DFOREGROUND

Jul 11 03:03:01 ip-172-31-40-241.ap-northeast-1.compute.internal systemd[1]: Starting The Apache HTTP Server...
Jul 11 03:03:01 ip-172-31-40-241.ap-northeast-1.compute.internal systemd[1]: Started The Apache HTTP Server.

# また、enabledにもなっている
$ systemctl is-enabled httpd
enabled

全て設定値通りに作成されているのでOKとします!

参考サイト

各種サービスにおけるAWS SDK For Rubyを利用した実装例が確認できます。
AWS SDK for Ruby コード例

所感

AWS CLIは業務でよく使っていたのですが、AWS SDKを使ったのはこれが初でした。
AWS CLIよりコーディングとしての実装はかなり楽に感じます(例えばインスタンスのステータス待ちなどは、AWS CLIだとForループで何秒間隔で状態確認をするみたいな実装をしていたのですが、AWS SDKだとwait_untilメソッドを呼ぶだけの1行コードで済んでしまいました)。
今回は以上です。見て頂きありがとうございました。

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

[Rails] link_toのリンク先を別タブで表示させたい

Railsでlink_toを使うときに別タブで表示させたいと思い、実装したのでメモとして残しておきます。

手順

まず link_to で表示させたい文字列とリンク先URLを指定。

<%= link_to "文字列", "リンク先URL" %>

別タブで表示させるため、target: :_blank を追加

<%= link_to "文字列", "リンク先URL", target: :_blank %>

これで別タブで開けるようになる。
しかし、これだとパフォーマンスとセキュリティの面で問題が。。。

この問題を回避するためには rel="noopener noreferrer" をつけるといいみたい。

<%= link_to "文字列", "リンク先URL", target: :_blank, rel: "noopener noreferrer" %>

これで安心して別タブを開けます。

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

新規登録時、ActionMailerでメール送信機能

概要

ActionMailerを使って新規登録時にwelcomeメールを送ります。

前提

deviseを用いての、ログイン機能の実装。

導入手順

1. ActionMailerを利用する設定

welcomeメールの送信元はGmailを使う記載方法。
config/environments/development.rbにてActionMailerの設定方法の記述。

development.rb
Rails.application.configure do

#---中略---#

 config.action_mailer.raise_delivery_errors = true
  config.action_mailer.delivery_method = :smtp
  config.action_mailer.smtp_settings = {
    port:                 587,
    address:              'smtp.gmail.com',
    domain:               'gmail.com',
    user_name:            '送信元アドレス',
    password:             'アプリパスワード',
    authentication:       :plain,
    enable_starttls_auto: true
  }

アプリパスワードは2段階認証を設定の上16桁のパスワードを発行し、アプリパスワードに記載する。
以下、参考にさせていただきました。
Google メールアプリケーション用のパスワードを取得する
Google 二段階認証設定をONにする方法

2. Mailerクラス作成

$ rails g mailer UserNotice

3. Mailerクラス編集

app/mailers/user_notice_mailer.rb
class UserNoticeMailer < ApplicationMailer
  def send_signup_email(user)
    @user = user
    mail to: @user.email, subject: "会員登録が完了しました。"
  end
end

4. メール本文作成

app/views/user_notice_mailer/send_signup_email.text.erb
ようこそ <%= @user.name %> 様

この度はアカウント登録頂きましてありがとうございます。

5. Userモデル編集

app/models/user.rb
#---追加---#

  after_create :send_welcome_mail

  def send_welcome_mail
    UserNoticeMailer.send_signup_email(self).deliver
  end

after_create を使うことで、Userが新規作成された後にメールを送信するためのメソッドを呼び出すことができます。

以上で、welcomeメールが送信されるはずです。

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

Digdag公式ドキュメントからDigdagを学ぶ-アーWorkflow definition

目標

Digdagの公式サイトのドキュメントのWorkflow definitionの翻訳+α
DigdagのRubyを使ってRailsにバッチを作るまでが最後の目標
http://docs.digdag.io/workflow_definition.html

#目次

Getting started
Architecture
Concepts
Workflow definition
Scheduling workflow
Operators
Command reference
Language API -Ruby
REST API
Internal architecture
Release Notes

Workflow definition

Workflow definition: *.dig files

digdagワークフローは.dig拡張子が付いた名前のファイルで定義されます。
ファイルの名前はワークフローの名前です。

たとえば、hello_worldワークフローを作成すると、hello_world.digファイルが作成されます。 ファイルの内容は次のようになります。

hello_world.dig
timezone: Asia/Tokyo

+step1:
  sh>: tasks/shell_sample.sh

+step2:
  py>: tasks.MyWorkflow.step2
  param1: this is param1

+step3:
  rb>: MyWorkflow.step3
  require: tasks/ruby_sample.rb

timezoneパラメータは、ワークフローのタイムゾーンを構成するために使用され、セッションのタイムスタンプ変数とスケジューリングに影響を与えます。 デフォルトのタイムゾーンはUTCです。 他の有効なタイムゾーンの例には、America/Los_Angeles、Europe/Berlin、Asia/Tokyoなどがあります。

+記号はタスクを意味

+記号で始まるキー名はタスクです。 タスクは上から順に実行されます。 タスクは、別のタスクの子としてネストできます。 上記の例では、+step2+mainタスクの子として+step1の後に実行されます。

operators>

type>:commandまたは_type:NAMEパラメーターを持つタスクがアクションを実行します。 シェルスクリプトの実行、Pythonメソッド、電子メールの送信など、さまざまな種類のオペレーターを選択できます。組み込みオペレーターのリストについては、オペレーターのページを参照してください。

foo>:barは以下の二つのパラメーターと同じです。

_type: foo
_command: bar

Using ${variables}

ワークフローは$ {...}構文を使用して変数を埋め込むことができます。
組み込み変数を使用するか、独自の変数を定義できます。
組み込み変数については以下のドキュメント参照
http://docs.digdag.io/workflow_definition.html

Calculating variables

$ {...}構文で基本的なJavaScriptスクリプトを使用して変数を計算できます。
一般的な使用例は、タイムスタンプを別の形式にフォーマットすることです。 Digdagには、時間計算のためのMoment.jsがバンドルされています。

timezone: Asia/Tokyo

+format_session_time:
  echo>: ${moment(session_time).format("YYYY-MM-DD HH:mm:ss Z")}

+format_in_utc:
  echo>: ${moment(session_time).utc().format("YYYY-MM-DD HH:mm:ss")}

+format_tomorrow:
  echo>: ${moment(session_time).add(1, 'days').format("LLL")}

+get_execution_time:
  echo>: ${moment().format("YYYY-MM-DD HH:mm:ss Z")}
2020-07-10 22:05:34 +0900 [INFO] (0017@[0:default]+my_workflow+format_session_time): echo>: 2020-07-10 09:00:00 +09:00
2020-07-10 09:00:00 +09:00
2020-07-10 22:05:35 +0900 [INFO] (0017@[0:default]+my_workflow+format_in_utc): echo>: 2020-07-10 00:00:00
2020-07-10 00:00:00
2020-07-10 22:05:35 +0900 [INFO] (0017@[0:default]+my_workflow+format_tomorrow): echo>: July 11, 2020 9:00 AM
July 11, 2020 9:00 AM
2020-07-10 22:05:36 +0900 [INFO] (0017@[0:default]+my_workflow+get_execution_time): echo>: 2020-07-10 22:05:36 +09:00
2020-07-10 22:05:36 +09:00

Defining variables

変数定義方法

1. YAMLでの_exportパラメーターの使用
2. APIを使用してプログラムで変数を設定
3. 変数を使用したセッションの開始

Using _export: parameter

YAMLファイルでは、_export:指示者が変数を定義します。
これはデータベースのホスト名などの静的構成をロードするのに役立ちます。

タスクに_export指示者がある場合、スコープ内で変数を定義するためタスクとその子は変数を使用できます。

my_workflow.dig
_export:
  foo: 1

+prepare:
  sh>: echo foo:${foo} bar:${bar}

+analyze:
  _export:
    bar: 2

  +step1:
    sh>: echo foo:${foo} bar:${bar}

+dump:
  sh>: echo foo:${foo} bar:${bar}
結果
2020-07-10 22:18:11 +0900 [INFO] (0017@[0:default]+my_workflow+prepare): sh>: echo foo:1
foo:1
2020-07-10 22:18:11 +0900 [INFO] (0017@[0:default]+my_workflow+analyze+step1): sh>: echo foo:1 bar:2
foo:1 bar:2
2020-07-10 22:18:12 +0900 [INFO] (0017@[0:default]+my_workflow+dump): sh>: echo foo:1
foo:1

すべてのタスクでfoo=1を使用できますが、bar=2を使用できるのは+step1(および+analyze)だけです。

Using API

言語APIを使用してプログラムで変数を設定できます。 たとえば、Python APIはdigdag.env.exportとdigdag.env.storeを提供します。
今回の内容にはRubyAPIを中心に説明したいと思います。APIについては別章で扱うのでこちらでも詳細説明はしません。

import digdag

class MyWorkflow(object):
  def prepare(self):
    digdag.env.store({"my_param": 2})

  def analyze(self, my_var):
    print("my_var should be 2: %d" % my_var)

Starting a session with variables

新しいワークフローセッションを開始するときに変数を設定できます。変数を設定するには、-p KEY = VALUEを複数回使用します。
ドキュメントにはサンプルがないので。単純に外部からもらうパラメーターをコンソルに出力するワークフローを作成します。

my_workflow.dig
+print_my_var1:
  sh>: echo my_var1:${my_var1}

+print_my_var2:
  sh>: echo my_var2:${my_var2}

my_var1とmy_bar2は実行時パラメーターから取得できます。以下のように実行してみましょう〜

digdag run my_workflow.dig --rerun -p my_var1=foo -p my_var2=bar
結果
2020-07-11 10:29:33 +0900 [INFO] (0017@[0:default]+my_workflow+print_my_var1): sh>: echo my_var1:foo
my_var1:foo
2020-07-11 10:29:33 +0900 [INFO] (0017@[0:default]+my_workflow+print_my_var2): sh>: echo my_var2:bar
my_var2:bar

!include another file

YAMLファイルを小さなファイルに分割して、複雑なワークフローを整理できます。
!includeを使用して分割されたワークフローを自分のワークフローに含めることができます。

my_workflow.dig
+task1:
    !include : 'tasks/task1.dig'
+task2:
    !include : 'tasks/task2.dig'
task1.dig
+task1:
    sh>: echo タスク1です
task2.dig
+task2:
    sh>: echo タスク2です

my_workflow.digを実行するとtask1.dig task2.digのタスクが実行されるのがわかります。

run
$digdag run my_workflow.dig --rerun
2020-07-11 10:40:39 +0900 [INFO] (0017@[0:default]+my_workflow+task1+task1): sh>: echo タスク1です
タスク1です
2020-07-11 10:40:40 +0900 [INFO] (0017@[0:default]+my_workflow+task2+task2): sh>: echo タスク2です
タスク2です

Parallel execution

_parallel:true がグループに設定されている場合、グループ内のタスクは並行して実行されます。

my_workflow.dig
+prepare:
  # +data1, +data2, and +data3 run in parallel.
  _parallel: true

  +data1:
    sh>: echo data1
  +data2:
    sh>: echo data2
  +data3:
    sh>: echo data3
+analyze:
  sh>: echo analyze

data1, data2, data3が並列で出力されています。

実行結果
$ digdag run my_workflow.dig --rerun
2020-07-11 10:50:45 +0900 [INFO] (0018@[0:default]+my_workflow+prepare+data2): sh>: echo data2
2020-07-11 10:50:45 +0900 [INFO] (0017@[0:default]+my_workflow+prepare+data1): sh>: echo data1
2020-07-11 10:50:45 +0900 [INFO] (0019@[0:default]+my_workflow+prepare+data3): sh>: echo data3
data1
data2
data3
2020-07-11 10:50:45 +0900 [INFO] (0019@[0:default]+my_workflow+analyze): sh>: echo analyze
analyze

_background:true がタスクまたはグループに設定されている場合、タスクまたはグループは前のタスクと並行して実行されます。次のタスクは、バックグラウンドタスクまたはグループの完了を待ちます。

my_workflow.dig
+prepare:
  +data1:
    sh>: echo data1

  # +data1 and +data2 run in parallel.
  +data2:
    _background: true
    sh>: echo data2

  # +data3 runs after +data1 and +data2.
  +data3:
    sh>: echo data3

+analyze:
  sh>: echo analyze

data1,data2が並列で実行されます。

結果
$ digdag run my_workflow.dig --rerun

2020-07-11 11:00:06 +0900 [INFO] (0018@[0:default]+my_workflow+prepare+data2): sh>: echo data2
2020-07-11 11:00:06 +0900 [INFO] (0017@[0:default]+my_workflow+prepare+data1): sh>: echo data1
data2
data1
2020-07-11 11:00:06 +0900 [INFO] (0018@[0:default]+my_workflow+prepare+data3): sh>: echo data3
data3
2020-07-11 11:00:06 +0900 [INFO] (0018@[0:default]+my_workflow+analyze): sh>: echo analyze

Retrying failed tasks automatically

_retry:N(Nは整数:1、2、3、…)パラメーターがグループに設定されている場合、1つ以上の子タスクが失敗したときに、グループを最初から再試行します。

my_workflow.dig
+prepare:
  # If +erase_table, +load_data, or +check_loaded_data fail, it retries from
  # +erase_table again.
  _retry: 3

  +erase_table:
    sh>: echo erase_table

  +load_data:
    sh>: echo load_data
  +check_loaded_data:
    sh>: tasks/error.sh

+analyze:
  sh>: echo analyze
error.sh
#!/bin/bash
exit 0

最初の実行とRetry3件の4件のエラーが発生しました。Retry途中で正常に実行されたらエラー数は減ると思います。

結果
$ digdag run my_workflow.dig --rerun
2020-07-11 11:21:21 +0900 [INFO] (0017@[0:default]+my_workflow+prepare+erase_table): sh>: echo erase_table
erase_table
2020-07-11 11:21:21 +0900 [INFO] (0017@[0:default]+my_workflow+prepare+load_data): sh>: echo load_data
load_data
2020-07-11 11:21:22 +0900 [INFO] (0017@[0:default]+my_workflow+prepare+check_loaded_data): sh>: tasks/error.sh
2020-07-11 11:21:22 +0900 [ERROR] (0017@[0:default]+my_workflow+prepare+check_loaded_data): Task failed with unexpected error: Command failed with code 1
java.lang.RuntimeException: Command failed with code 1
        at io.digdag.standards.operator.ShOperatorFactory$ShOperator.runTask(ShOperatorFactory.java:143)
        at io.digdag.util.BaseOperator.run(BaseOperator.java:35)
        at io.digdag.core.agent.OperatorManager.callExecutor(OperatorManager.java:315)
        at io.digdag.cli.Run$OperatorManagerWithSkip.callExecutor(Run.java:705)
        at io.digdag.core.agent.OperatorManager.runWithWorkspace(OperatorManager.java:257)
        at io.digdag.core.agent.OperatorManager.lambda$runWithHeartbeat$2(OperatorManager.java:137)
        at io.digdag.core.agent.LocalWorkspaceManager.withExtractedArchive(LocalWorkspaceManager.java:25)
        at io.digdag.core.agent.OperatorManager.runWithHeartbeat(OperatorManager.java:135)
        at io.digdag.core.agent.OperatorManager.run(OperatorManager.java:119)
        at io.digdag.cli.Run$OperatorManagerWithSkip.run(Run.java:687)
        at io.digdag.core.agent.MultiThreadAgent.lambda$null$0(MultiThreadAgent.java:127)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
2020-07-11 11:21:22 +0900 [INFO] (0017@[0:default]+my_workflow+prepare+erase_table): sh>: echo erase_table
erase_table
2020-07-11 11:21:22 +0900 [INFO] (0017@[0:default]+my_workflow+prepare+load_data): sh>: echo load_data
load_data
2020-07-11 11:21:23 +0900 [INFO] (0017@[0:default]+my_workflow+prepare+check_loaded_data): sh>: tasks/error.sh
2020-07-11 11:21:23 +0900 [ERROR] (0017@[0:default]+my_workflow+prepare+check_loaded_data): Task failed with unexpected error: Command failed with code 1
java.lang.RuntimeException: Command failed with code 1
        at io.digdag.standards.operator.ShOperatorFactory$ShOperator.runTask(ShOperatorFactory.java:143)
        at io.digdag.util.BaseOperator.run(BaseOperator.java:35)
        at io.digdag.core.agent.OperatorManager.callExecutor(OperatorManager.java:315)
        at io.digdag.cli.Run$OperatorManagerWithSkip.callExecutor(Run.java:705)
        at io.digdag.core.agent.OperatorManager.runWithWorkspace(OperatorManager.java:257)
        at io.digdag.core.agent.OperatorManager.lambda$runWithHeartbeat$2(OperatorManager.java:137)
        at io.digdag.core.agent.LocalWorkspaceManager.withExtractedArchive(LocalWorkspaceManager.java:25)
        at io.digdag.core.agent.OperatorManager.runWithHeartbeat(OperatorManager.java:135)
        at io.digdag.core.agent.OperatorManager.run(OperatorManager.java:119)
        at io.digdag.cli.Run$OperatorManagerWithSkip.run(Run.java:687)
        at io.digdag.core.agent.MultiThreadAgent.lambda$null$0(MultiThreadAgent.java:127)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
2020-07-11 11:21:23 +0900 [INFO] (0017@[0:default]+my_workflow+prepare+erase_table): sh>: echo erase_table
erase_table
2020-07-11 11:21:23 +0900 [INFO] (0017@[0:default]+my_workflow+prepare+load_data): sh>: echo load_data
load_data
2020-07-11 11:21:24 +0900 [INFO] (0017@[0:default]+my_workflow+prepare+check_loaded_data): sh>: tasks/error.sh
2020-07-11 11:21:24 +0900 [ERROR] (0017@[0:default]+my_workflow+prepare+check_loaded_data): Task failed with unexpected error: Command failed with code 1
java.lang.RuntimeException: Command failed with code 1
        at io.digdag.standards.operator.ShOperatorFactory$ShOperator.runTask(ShOperatorFactory.java:143)
        at io.digdag.util.BaseOperator.run(BaseOperator.java:35)
        at io.digdag.core.agent.OperatorManager.callExecutor(OperatorManager.java:315)
        at io.digdag.cli.Run$OperatorManagerWithSkip.callExecutor(Run.java:705)
        at io.digdag.core.agent.OperatorManager.runWithWorkspace(OperatorManager.java:257)
        at io.digdag.core.agent.OperatorManager.lambda$runWithHeartbeat$2(OperatorManager.java:137)
        at io.digdag.core.agent.LocalWorkspaceManager.withExtractedArchive(LocalWorkspaceManager.java:25)
        at io.digdag.core.agent.OperatorManager.runWithHeartbeat(OperatorManager.java:135)
        at io.digdag.core.agent.OperatorManager.run(OperatorManager.java:119)
        at io.digdag.cli.Run$OperatorManagerWithSkip.run(Run.java:687)
        at io.digdag.core.agent.MultiThreadAgent.lambda$null$0(MultiThreadAgent.java:127)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
2020-07-11 11:21:24 +0900 [INFO] (0017@[0:default]+my_workflow+prepare+erase_table): sh>: echo erase_table
erase_table
2020-07-11 11:21:24 +0900 [INFO] (0017@[0:default]+my_workflow+prepare+load_data): sh>: echo load_data
load_data
2020-07-11 11:21:24 +0900 [INFO] (0017@[0:default]+my_workflow+prepare+check_loaded_data): sh>: tasks/error.sh
2020-07-11 11:21:24 +0900 [ERROR] (0017@[0:default]+my_workflow+prepare+check_loaded_data): Task failed with unexpected error: Command failed with code 1
java.lang.RuntimeException: Command failed with code 1
        at io.digdag.standards.operator.ShOperatorFactory$ShOperator.runTask(ShOperatorFactory.java:143)
        at io.digdag.util.BaseOperator.run(BaseOperator.java:35)
        at io.digdag.core.agent.OperatorManager.callExecutor(OperatorManager.java:315)
        at io.digdag.cli.Run$OperatorManagerWithSkip.callExecutor(Run.java:705)
        at io.digdag.core.agent.OperatorManager.runWithWorkspace(OperatorManager.java:257)
        at io.digdag.core.agent.OperatorManager.lambda$runWithHeartbeat$2(OperatorManager.java:137)
        at io.digdag.core.agent.LocalWorkspaceManager.withExtractedArchive(LocalWorkspaceManager.java:25)
        at io.digdag.core.agent.OperatorManager.runWithHeartbeat(OperatorManager.java:135)
        at io.digdag.core.agent.OperatorManager.run(OperatorManager.java:119)
        at io.digdag.cli.Run$OperatorManagerWithSkip.run(Run.java:687)
        at io.digdag.core.agent.MultiThreadAgent.lambda$null$0(MultiThreadAgent.java:127)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
2020-07-11 11:21:25 +0900 [INFO] (0017@[0:default]+my_workflow^failure-alert): type: notify
error: 
  * +my_workflow+prepare+check_loaded_data:
    Command failed with code 1 (runtime)
  * +my_workflow+prepare+check_loaded_data:
    Command failed with code 1 (runtime)
  * +my_workflow+prepare+check_loaded_data:
    Command failed with code 1 (runtime)
  * +my_workflow+prepare+check_loaded_data:
    Command failed with code 1 (runtime)

次のように、間隔を_retryに設定もできます

+prepare:
  _retry:
    limit: 3
    interval: 10
    interval_type: exponential

■limit: 再試行回数
■interval:再実行間隔時間(秒)
■interval_type: constantかexponential constant の場合は再実行間隔は一定になります。
exponentialの場合は再実行間隔は2 x(retry_count-1) として再試行ごとに増加します。上記の例では最初の再試行間隔は10秒、2番目は20秒、3番目は40秒です。

Sending error notification

# ワークフローが失敗する時に実行される
_error:
  sh>: tasks/runs_when_workflow_failed.sh

+analyze:
  sh>: tasks/analyze_prepared_data_sets.sh

エラーのタイミングでmail>operatorを利用してメール送信可能
詳しい説明はオペレーターの説明でやります〜
http://docs.digdag.io/operators/mail.html

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

enumを日本語化して、連動したラジオボタンを生成する方法

やりたいこと

以下のようなenumをラジオボタンで表現したい。

enum loadtype: { normal: 0, trail: 1, beach: 2, track: 3 }

かつ、選択肢は日本語で出るようにしたい。

enumの日本語化について

こちらの記事の内容に従って日本語化します。
https://qiita.com/tanutanu/items/d44a92425188a4489ec6

日本語化したenumに連動したラジオボタンを記述

ViewのFormで以下のように記載

<%= f.collection_radio_buttons :loadtype, Post.loadtypes_i18n, :first, :last  %>

コードの意味は以下の通り

<%= f.collection_radio_buttons [column名], [表示対象のコレクション], [value], [textに表示する文字列]  %>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

emunを日本語化して、連動したラジオボタンを生成する方法

やりたいこと

以下のようなEmunをラジオボタンで表現したい。

enum loadtype: { normal: 0, trail: 1, beach: 2, track: 3 }

かつ、選択肢は日本語で出るようにしたい。

enumの日本語化について

こちらの記事の内容に従って日本語化します。
https://qiita.com/tanutanu/items/d44a92425188a4489ec6

日本語化したenumに連動したラジオボタンを記述

ViewのFormで以下のように記載

<%= f.collection_radio_buttons :loadtype, Post.loadtypes_i18n, :first, :last  %>

コードの意味は以下の通り

<%= f.collection_radio_buttons [column名], [表示対象のコレクション], [value], [textに表示する文字列]  %>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails]DBにデータが登録されない場合の対処法

はじめに

フォーム入力内容をDBに保存したいがハマったのでアウトプット

環境

Rails 5.0.7.2
ruby 2.5.1
mysql 14.14

問題点

フォーム入力内容を保存しようとすると、パラメータに保存されているがDBに保存されない。

対処方法

保存するメソッドの後ろに!をつけて原因を確認。

すると、validation failed:User must exist(エラー内容は異なる場合がありそう)

原因

アソシエーションが組まれている際に、該当の外部キーが入っておらず、バリデーションで弾かれているのが原因。

optional: trueを記述。

goal.rb
class Goal < ApplicationRecord

  validates :name, presence: true, uniqueness: true
  validates :time, presence: true, uniqueness: true
  validates :days, presence: true, uniqueness: true
  belongs_to :user, optional: true #ここを編集
end

optional: trueとは、belongs_toの外部キーのnilを許可するというもの。

これでDBに保存できると思います。
参考にしてください!

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

Rubyの継承について

プログラミングの勉強日記

2020年7月11日 Progate Lv.175
RubyⅤ

継承

 クラスを1から作ることもできるが、すでにあるクラスを利用してそのクラスを元に新しいクラスを作れる。そうすることで共通部分をまとめることができ、効率的にコードが書ける。
 あるクラスをもとにして新たなクラスを作ることを継承という。class 新しいクラス名 < 元となるクラス名で他のクラスを継承して新しいクラスを定義することができる。新しいクラスは「子クラス」、元となるクラスは「親クラス」という。
 親クラスのインスタンスメソッドが引き継がれる。

food.rb
require "./menu"
class Food < Menu
end

子クラスのインスタンスは、継承すると子クラスは親クラスのインスタンスメソッドを引き継ぐ。

index.rb
food1=Food.new
puts food1.name
puts food2.info
menu.rb
class Menu
  attr_accessor :name 
  #処理
  def info
    return "#{self.name} #{self.price}円"
  end
end

インスタンス変数の追加

 子クラスにインスタンス変数を追加するためには今まで通りattr_accessorを使う。

menu.rb
class Menu
  attr_accessor :name 
  attr_accessor :price
end

FoodクラスはMenuクラスを継承しているので。「name, price, calorie」の3つのインスタンス変数を持つ。

food.rb
class Food < Menu
  #Foodクラスにcalorieを追加する
  attr_accessor :calorie
end

同様にしてインスタンスメソッドも追加することができる。

オーバーライド

 親にあるメソッドと同じ名前のメソッドを子クラスで定義すると、メソッドを上書きすることができる。このことをオーバーライドという。

index.rb(メソッドを呼び出す)
food1~Food.new(...)
food1.calorie=700
puts food1.info
menu.rb(親クラス)
class Menu
  #処理
  def info
    #処理
  end
end
food.rb(子クラス)
class Food < Menu
  #処理
  #メソッドの上書き(上書きしたメソッドが呼び出される)
  def info
    #処理
  end
end

 子クラスのインスタンスは子クラスで定義したメソッドを優先して呼び出す。よって、子クラスと親クラスに同じ名前のメソッドがある場合は、子クラスのメソッドの内容が上書きされる。initializeメソッドでもオーバーライドができる。

super

 オーバーライドしたメソッドの中でsuperとすることで、親クラスの同じ名前のメソッドを呼び出せる。superではメソッドを呼び出しているので、親クラスのメソッドに合わせてsuperに対して引数を渡す必要がある。

menu.rb
class Menu
  attr_accessor :name 
  attr_accessor :price
  def initialize(name:, price:)
    self.name=name
    self.price=price
  end
end
food.js
class Food < Menu
  attr_accessor :calorie
  def initialize(name:, price:, calorie:)
    super(name: name, price: price)
    self.calorie=calorie
  end
end

0711.png

Dateクラス

 日付を扱うクラス。DateクラスはRubyがすでに用意しているクラスで、requireでdateを読み込むことでクラスを定義しなくても使える。すでに用意されているクラスは他とはrequireの書き方が異なる

index.rb
# "/date"ではない!
reruire "date"

Dateクラスのインスタンス

 Date.newでインスタンスを生成できる。Date.todayで今日の日付のインスタンスを作れる。

index.rb
require "date"
#引数に「年・月・日」を渡してDateメソッドを作成
date1=Date.new(2020,7,8)
puts date
コンソール
2020-07-08

Dateクラスのインスタンスメソッド

 多くのインスタンスメソッドがある。日曜日かどうかを真偽値で渡すのはsunday?メソッド。

index.rb
require "date"
date1=date1.new(2020,7,11)
puts date1.sunday?
コンソール
false

クラスメソッド

 クラスに対して呼び出すメソッドのこと。Date.todaytodayのとこ。クラスメソッドはdef クラス名.メソッド名とすることで定義できる。インスタンスメソッドと異なり、メソッド名の前にクラス名を書く。
 クラスメソッドの呼び出しは、クラス名.メソッド名とする。

menu.rb
class Menu
  #処理
  #今日の日付が日曜日かどうかを真偽値で返す
  def Menu.is_discount_day?  
    #今日の日付の情報を持つDateインスタンス
    today=Date.today
    return today.sunday
  end
end
puts Menu.is_discount_day?

インスタンスメソッドの中でクラスメソッドを呼び出す

menu.rb
class Menu
  #処理
  def get_total_price  #インスタンスメソッド
    if Menu.discount_day?  #クラスメソッドの呼び出し
      #処理
    end
  end
end

0711-1.png

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

Rubyのクラスとインスタンスについて

プログラミングの勉強日記

2020年7月11日 Progate Lv.175
RubyⅣ

クラスとインスタンス

 ここでは簡単にクラスとインスタンスについて説明する。プログラミングでメニューという「もの」を作るためには、まずその設計図が必要でとなる。設計図をクラス、「もの」をインスタンスという。(設計図(クラス)をもとに「もの」(インスタンス)を生成)なので、インスタンスを生成するためにはクラスを用意し、クラスからインスタンスを作成、そしてインスタンスに情報を追加する。

クラスの定義

 クラスはclass クラス名で定義できる。クラスは必ず大文字ではじめ、endを忘れずに書く。

index.rb
class Menu
end

attr_accessor

 インスタンス変数を直接変更して操作ができるようにするもの。情報を持たせるためにattr_accessor シンボルと書く。1つのクラスに対して複数のインスタンス変数を使うこともできる。

index.rb
class Menu
  attr_accessor :name
end

インスタンスの生成

 クラスを元に新しくインスタンスを生成するためにはクラス名.newとする。変数名=クラス名.newとすることで生成したインスタンス変数を代入できる。
 インスタンスに情報を持たせるためには、クラスで用意したインスタンス変数に値を代入する。インスタンス.変数名=値でインスタンス変数に値をセットすることができる。

index.rb
class Menu
  #処理
end
#変数menu1にMenuクラスからのインスタンスを生成し、代入する
menu1=Menu.new
menu1.name="すし"

クラスの中でメソッドを使う

 クラスの中で定義したメソッドはインスタンスに対して使うようにして呼び出す。インスタンス.メソッド名で呼び出す。クラスの中で定義したメソッドを呼び出すものをインスタンスメソッドという。

index.rb
class Menu
  attr_accessor :name
  attr_accessor :price
  #クラスの中でメソッドを定義
  def show
    puts "menu"
  end
end
menu1=Menu.new
#クラスの中で定義したメソッドの呼び出し
menu1.show
コンソール
menu

インスタンスメソッド

 クラスの中で定義したインスタンスに対して呼び出すメソッドのこと。引数を受け取ったり戻り値を返したりすることができる。インスタンスが持つ情報である「インスタンス変数」とインスタンスに対して呼び出すインスタンスメソッドはクラスの中で定義する。

index.rb
class Menu
  #処理
  def show(data)
    return "#{data}です"
  end
end
menu1=Menu.new
puts menu1.show("メニュー")
コンソール
メニューです

インスタンスメソッドの中でインスタンス変数を使う

 インスタンスメソッドの中では特殊な変数selfを用いてself.変数名とすることで、インスタンス変数を扱える。インスタンスメソッドでは変数selfに呼び出したインスタンス自身が代入されている。

index.rb
class Menu
  #処理
  def show_name
    puts "#{self.name}です"
  end
end
menu1=Menu1.new
menu1.name="すし"
#インスタンスメソッドの呼び出し
menu1.show_name
コンソール
すしです

initializeメソッド

 インスタンスを生成した直後に処理を実行できる。クラス名.newでインスタンスを生成した直後に自動で呼び出される。

index.rb
class Menu
  #処理
  def initialize
    puts "メニュー"
  end
end
#Menu.newが実行されると自動でinitializeメソッドを呼び出す
menu1=Menu.new
コンソール
メニュー

 インスタンスメソッドの中ではself.変数名でインスタンス変数を扱うことができ、self.変数名=値でインスタンス変数に値を代入する。

index.rb
class Menu
  #処理
  def initialize
    self.name="すし"
  end
end
menu1=Menu.new
puts menu1.name
コンソール
すし

initializeメソッドの変数

 initializeメソッドにも引数を渡すことができる。クラス.newに対して引数を渡すことでその値を渡せる。もちろんキーワード引数を用いることもできる。

index.rb
class Menu
  #処理
  def initialize(message)
    puts message
  end
end
menu1=Menu.new("Hello")
コンソール
Hello

require

 ファイルの読み込みを行う。(main文がわかれていて、別のファイルのクラスを読み込みたいときに使う)

index.rb
requie "./menu"
menu1=Menu.new(name: "すし"
menu.rb
class Menu
  attr_accessor :name
  attr_accessor :price
end

gets.chomp

 入力を受け付けるときに使う。このコードが実行されるとコンソールが入力待機状態になる。変数=gets.chompとすることでエンターキーを押されるまで入力された値を変数に代入できる。

index.rb
puts "名前の入力"
name=gets.chomp
puts "あなたの名前は#{name}です"
コンソール
名前の入力
田中(入力する)
あなたの名前は田中です

 数値を受け取る場合は、gets.chomp.to_iとすることで入力された内容を数値に変換できる。

index.rb
puts "数字の入力"
number=gets.chomp.to_i #入力された内容を数値に変換
puts "#{number}の2倍は#{number*2}です"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

個人アプリ作成#1

雛形の作成

rails バージョン new app名 -d MySQL で作成

Git hub Desk topと連携してコードの保存

トップページの作成

スクリーンショット 2020-07-11 3.25.35.png
この後に、投稿一覧を作りたいと思う。

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

[Rails]carrierwaveを使って画像を保存する

タイトルの通りになります。

ゴールは画像を表示させるまでとなります。

gemを導入

初めはgemを入れます。
バージョンを指定しなければ最新のものがインストールされます。

Gemfile
gem 'carrirewave'

ターミナルでいつものよろしくお願いします。

$ bundle install

アップローダファイルの生成

次にアップローダファイルを作成しましょう。

$ rails generate uploader Image
create  app/uploaders/image_uploader.rb

これでapp/uploadersの中にimage_uploader.rbが生成されていると思います。

ちなみにImageの部分はマウントするモデル名だとか,
わかりやすい名前。私はよくImageで生成しています。

image_uploader.rb
class ImageUploader < CarrierWave::Uploader::Base
 # Include RMagick or MiniMagick support:
  # include CarrierWave::RMagick
  # include CarrierWave::MiniMagick
  # process resize_to_fit: [300, 200]

  # version :thumb do
  #   process :resize_to_fit => [50, 50]
  # end

  # 以下省略
end

このファイルの中で画像アップロードに関する設定ができます。例えば、アップロードする画像の拡張子の制限だったり、画像サイズのリサイズだったりいろんなことができます。

モデルに書く

画像アップロードさせたいモデルに先ほど作成したファイルをマウントしてあげましょう。

今回はhogeモデルのimageカラムに画像を登録させたいケースで考えます。

hoge.rb
class hoge < ApplicationRecord
  mount_uploader :image, ImageUploader
end

登録するためのFormを作る

各ページ必要な情報を渡してあげます。

hoges_controller.rb
class ArticlesController < ApplicationController

  def new
    @article = Article.new
  end

  def create
    @article = Article.new(user_params)
    if @article.save
      redirect_to user_path(@article)
    else
      render :new
    end
  end

  private
  def article_params
    params.require(:article).permit(:image)
  end
end
/hoges/new.html
<%= form_with model: @hoge, local: true do %>
    <div class="field">
        <%= f.label :image %>
        <%= f.file_field :image %> <!--画像はfile_field -->
    <div class="action">
        <%= f.submit %>
    </div>
<% end %>

アップロードした画像の表示

画像の表示は基本的には、以下のように記述すると表示されます。

<%= image_tag @article.image.url %>
<%= image_tag @article.image.to_s %> 

終わりに

最後まで読んでいただきありがとうございます。
お勉強している方に少しでも力になればと思います。

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

NokogiriをRspecでテストする。

Nokogiriに渡す引数をmock化してテストしたので残す。

FUGA_FUGA = ['企業', '秘密']
def hogegiri_method(fuga)
  HOGE_COUNT = 5
  HOGE_COUNT.times do |time|
    html = Nokogiri::HTML(OpenURI.open_uri(url))
    a, b, c = 正規表現わちゃわちゃ
    break if FUGAFUGA.include?(a)
  end
  { a: a, b: b, c: c }
end

色々見せられないコードなのでクソみたいなコードだが、、、今回mockとして偽物の戻り値を作りたいのが

html = Nokogiri::HTML(OpenURI.open_uri('内緒'))

ここ

だけどNokogiriの戻り値を直接and_returnで設定するのは難しい。

  • 戻り値がNokogiri::HTML::Documentといって単純なhtmlとかを返していない
  • method内部でnokogiri特有の戻り値にしか使えないmethodを使っている。

など

つまり、nokogiriには仕事をさせつつURLにはアクセスせず目的のデータを取らないといけない。

じゃ、どうするか

OpenUri.open_uri

こいつの戻り値をmock化してNokogiriの引数に渡すべき値を渡す。

Nokogiriの引数には解析したいHTMLがどうやら入るらしい。
それなら、本番環境ではアクセスして取ってくるURL先のHTMLをコピってhtmlfile作ってそこにペーストしてそいつを戻り値のmockにしてあげればいいのだ!!なんて簡単なことに記事づかなかったのだ。

ファイナルアンサー

spec/test.html
HTMLコピペ
allow(OpenURI).to receive(:open_uri).and_return(File.new("#{Rails.root}/spec/test.html"))

完成
これでまるでurlにアクセスして取ってきたように、methodが動作します。
さらに、and_returnの引数をnil,File.new("#{Rails.root}/spec/test.html")みたいにやると今回作ったmethodのロジックとして値を取得できなかったら最高5回はループするので、roopしていることも確認できますね。

インターン始めたから個人開発じゃ絶対しないようなことばっかしてるから毎回詰まりそうになるけど何とか教えてもらったり粘ったりしてガンバている

ちょっと成長したかも

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