ECのウェブ担当者のメモ

ECサイトを運営管理している、WEB担当プログラマのメモ

スポンサーリンク

無料なSSL Let’s Encryptを使ってみた。EC2(amazon linux) + Nginx + Rails

無料で使えるSSL証明書があるということでLet’s Encryptを早速 試してみました。

Let’s Encryptの公式サイトはこちら

letsencrypt.org

環境

今回導入に試してみた、環境としてザックリと以下のようになります。

  • AWS EC2 (amazon linux)
  • Nginx
  • Ruby on Rails

作業手順

基本的には下記リンクの公式ページのに書いてあるので、 基本的にはそちらを参考にして頂ければと思います

letsencrypt.org

多少、つまづいたところもあるので、 詳しい作業手順を下記します。

letsencryptのダウンロード

まず、 letsencryptをダウンロードします。 ソースコードの配布はgitにあるので、gitからcloneします。

もし、サーバー等の環境にgitが入っていなければgit自体をインストールしてください。

Git - Gitのインストール

実際のコマンドは以下になります。

$ cd /usr/local
$ git clone https://github.com/letsencrypt/letsencrypt
$ cd ./letsencrypt

インストール

letsencryptのダウンロードが完了したら、続いてインストールします。 手順通りに実行すれば、以下のコマンドを実行すればよいのですが、warningが出ました。

$ ./letsencrypt-auto --help

WARNING: Amazon Linux support is very experimental at present...
if you would like to work on improving it, please ensure you have backups
and then run this script again with the --debug flag!

「Amazon Linuxはサポートしていないから、注意してね。もし実行するなら -debug オプション付けて実行してね。」 的な感じで言われるので、メッセージ通り以下を実行すると。

$ ./letsencrypt-auto --help --debug

(略)

Requesting root privileges to run letsencrypt...
   /root/.local/share/letsencrypt/bin/letsencrypt --help --debug
Traceback (most recent call last):
  File "/root/.local/share/letsencrypt/bin/letsencrypt", line 7, in <module>
    from letsencrypt.main import main
  File "/root/.local/share/letsencrypt/local/lib/python2.7/dist-packages/letsencrypt/main.py", line 11, in <module>
    import zope.component
  File "/root/.local/share/letsencrypt/local/lib/python2.7/dist-packages/zope/component/__init__.py", line 16, in <module>
    from zope.interface import Interface
ImportError: No module named interface

また、怒られました。

EC2でエラーが出るとか出ないとか。 このパターンだと、sudo付けてOK的な記事(ImportError: No module named cryptography.hazmat.bindings.openssl.binding · Issue #2544 · letsencrypt/letsencrypt · GitHub )があったので以下のコマンドを実行したら成功しました。

$ sudo ./letsencrypt-auto --help

--debug オプション いらないじゃん、、、

以下成功メッセージです。

Installation succeeded.
Requesting root privileges to run letsencrypt...
   /root/.local/share/letsencrypt/bin/letsencrypt --help

  letsencrypt-auto [SUBCOMMAND] [options] [-d domain] [-d domain] ...

The Let's Encrypt agent can obtain and install HTTPS/TLS/SSL certificates.  By
default, it will attempt to use a webserver both for obtaining and installing
the cert. Major SUBCOMMANDS are:

  (default) run        Obtain & install a cert in your current webserver
  certonly             Obtain cert, but do not install it (aka "auth")
  install              Install a previously obtained cert in a server
  renew                Renew previously obtained certs that are near expiry
  revoke               Revoke a previously obtained certificate
  rollback             Rollback server configuration changes made during install
  config_changes       Show changes made to server config during installation
  plugins              Display information about installed plugins

Choice of server plugins for obtaining and installing cert:

  --apache          Use the Apache plugin for authentication & installation
  --standalone      Run a standalone webserver for authentication
  (nginx support is experimental, buggy, and not installed by default)
  --webroot         Place files in a server's webroot folder for authentication

OR use different plugins to obtain (authenticate) the cert and then install it:

  --authenticator standalone --installer apache

More detailed help:

  -h, --help [topic]    print this message, or detailed help on a topic;
                        the available topics are:

   all, automation, paths, security, testing, or any of the subcommands or
   plugins (certonly, install, nginx, apache, standalone, webroot, etc)

これで、インストール完了です。

証明書の発行

続いて、証明書を発行していきます。
証明書発行用のコマンドとして、以下になります。

sudo ./letsencrypt-auto certonly --webroot -w /var/www/example -d example.com

しかし、その前に、Nginxの設定を確認しておく必要があります。

上記コマンドを実行すると、/var/www/exampleに .well-knownというファイルを作って、 以下のurlでアクセスしに来よとします。

http://example.com/.well-known

私の場合、もともと、Nginxの設定が入っていて、http://example.com/.well-knownを許可していなかったので、 別途、Nginxの設定で上記にアクセスできるようにしておく必要があります。

server {
  listen 80;
  server_name example.com;

  (略)

  location /.well-known/ {
    root /var/www/example;
  }
  
  (略)

例えば、上記のように、well-knownにアクセスできるような設定が済んでいる状態以下のコマンドを実行します。

sudo ./letsencrypt-auto certonly --webroot -w /var/www/example -d example.com

そうすると。以下のメッセージが表示されて

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/example.com/fullchain.pem. Your cert
   will expire on 2016-07-13. To obtain a new version of the
   certificate in the future, simply run Let's Encrypt again.
 - If you like Let's Encrypt, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

以下ディレクトリ配下に証明書も作ってくれています。

/etc/letsencrypt/live/example.com/

Nginxの設定

証明書まで出来上がったら、 Nginxのconfファイルを以下の様に編集します。

server {
  listen 443 ssl;
  server_name example.com;

  ssl on;
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  ssl_prefer_server_ciphers on;
  ssl_ciphers 'kEECDH+ECDSA+AES128 kEECDH+ECDSA+AES256 kEECDH+AES128 kEECDH+AES256 kEDH+AES128 kEDH+AES256 DES-CBC3-SHA +SHA !aNULL !eNULL !LOW !kECDH !DSS !MD5 !EXP !PSK !SRP !CAMELLIA !SEED';
  ssl_stapling on;
  ssl_session_cache builtin:1000 shared:SSL:10m;
  ssl_certificate      /etc/letsencrypt/live/example.com/fullchain.pem;
  ssl_certificate_key  /etc/letsencrypt/live/example.com/privkey.pem;

 location / {
  (略)
 }
}

ssl_certificate と ssl_certificate_keyにさっき作った証明書を指定します。

Nginxの再起動

設定が完了したら、Nginxを再起動します。

/etc/init.d/nginx reload

で実際にhttpsでアクセスしてみると。

無事に成功しました。

f:id:jun9632:20160414203704p:plain

証明書の更新

こちらの証明書、期間が3ヶ月なんです。短いですね。 けれども、ちゃんと更新用のコマンドが用意されているので、 定期的に更新してあげる必要があります。

なので 更新ようコマンドをcronに設定します。

0 5 * * * /usr/local/letsencrypt/letsencrypt-auto renew --force-renew && /etc/init.d/nginx reload

毎日1回、証明書を更新して、nginxをreloadする感じにしています。

まとめ

無料はやっぱりいいですね。 これまで、数千円~数万円かかっていたのが、無料でいけるのはありがたいです。

EV認証は対応していないようなので、企業向きでは無いのかもしれませんが、 セキュリティーに厳しくないところであれば、十分につかえるのではないでしょうか?

エンジニアにしてみたら、これからはSSLを設定していなと逆に手抜き感が出てしまうかもしれないですね。

でも、無料はありがたいです。 ありがとう、Let’s Encrypt!!

関連記事

marketing-web.hatenablog.com

marketing-web.hatenablog.com

nginx実践入門 (WEB+DB PRESS plus)

nginx実践入門 (WEB+DB PRESS plus)

Railsのmigrationのremove_indexで名前を変更したIndexを削除する方法

f:id:jun9632:20160330145646p:plain

Railsのmigrationでindexを削除しようと思って 最初以下のように書いたら怒られました。

remove_index :items,  [:shop_id,  :item_id]

出力されたエラーメッセージは以下のような感じです。

Index name 'index_items_on_shop_id_and_item_id' on table 'items' does not exist

なぜ、怒られた方と言えば、index名を変更していたんですね。

indexを作成するときに、以下の用に作成していたんです。

 add_index :items, column: [:shop_id,  :item_id], name: 'index_items_on_shop_id_and_item_id_xxxxxxxxxxxxxx'

どうやら、remove_indexするときもカラム名から勝手にindex名を作って その名前のindexを探して削除しに行くのだと思われます。

で、ドキュメントをみたら、ちゃんとオプションがあったので オプション指定してやらないと行けないみたいです。

remove_index - リファレンス - - Railsドキュメント

下記はサンプルです。

unique 条件をを外したいパターンです。

# -*- encoding : utf-8 -*-
class ChangeUniqueIndexToOrderItem < ActiveRecord::Migration
  def up
    remove_index :items, column: [:shop_id,  :item_id], name: 'index_items_on_shop_id_and_item_id_xxxxxxxxxxxxxx'
    add_index :items, column: [:shop_id,  :item_id], name: 'index_items_on_shop_id_and_item_id_xxxxxxxxxxxxxx'
  end

  def down
    remove_index :items, column: [:shop_id,  :item_id], name: 'index_items_on_shop_id_and_item_id_xxxxxxxxxxxxxx'
    add_index :items, column: [:shop_id,  :item_id], unique: true, name: 'index_items_on_shop_id_and_item_id_xxxxxxxxxxxxxx'
  end
end

関連記事

marketing-web.hatenablog.com

simple_formでラベルを非表示する方法

simple_formを使っている時に、ラベルを非表にする方法です。

github.com

下記サンプルは、Titleラベルを非表示にしたい場合です。

before

f:id:jun9632:20160330110647p:plain

after

f:id:jun9632:20160330110703p:plain

書き方

書き方はとても簡単です。

= simple_form_for [@post] do |f|
  = f.input :title, :input_html => {:class => 'form-control'}

label: falseをつければおしまいです。

= simple_form_for [@post] do |f|
  = f.input :title, :input_html => {:class => 'form-control'}, label: false

でも、ラベルを消したくなる時ってそんなに無いと思うから、 いざ使おうと思った時にはきっと忘れてしまうような気がします。

関連記事

marketing-web.hatenablog.com

marketing-web.hatenablog.com

Ruby on Rails 4 アプリケーションプログラミング

Ruby on Rails 4 アプリケーションプログラミング

rails4で画像をS3にアップロードする [carrierwave + imagemagick + fog + S3 + Dropzone]

f:id:jun9632:20160328194228p:plain

carrierwave + imagemagick + fog + S3 + Dropzone の組み合わせで、画像をアップロードして、S3に保存する方法です。

作業方法を下記していきます。

imagemagickのインストール

Macだったら、brewでimagemagick をインストールします。

$ brew install imagemagick

EC2のLinuxマシンでyumが使えれば

$ yum install imagemagick

Gemの追加

続いて、Gemを追加します。 今回追加するGemは、以下です。

  • carrierwave
  • rmagick
  • fog

carrierwave

github.com

carrierwaveがファイルのアップロード担当です。

rmagick

github.com

画像の加工を担当します。 今回は画像のリサイズのために使います。

fog

github.com

クラウド系のストレージ簡単に接続してくれるGemです。 今回はS3接続のために使用します。

Gemfile

上記のGemを追加するため、 Gemfileに以下を追加して、bundle install します。

# https://github.com/carrierwaveuploader/carrierwave
gem 'carrierwave'
# https://github.com/rmagick/rmagick
gem 'rmagick', require: 'RMagick'
# https://github.com/fog/fog
gem 'fog'

Modelの追加

今回は、商品の画像を想定しており、Item というモデルに複数の ItemImage モデルが紐づくテーブル構造です。

まず、下記を実行して、モデルのベースを作成します。

$ rails g model item
$ rails g model item_image

上記を実行すると、migrationファイルも生成されるので、それぞれの migrationファイルを以下のように編集します。 columnについては適宜変更してください。

class CreateItems < ActiveRecord::Migration
  def change
    create_table :items do |t|

      t.string :code
      t.string :name

      t.timestamps null: false
    end

    add_index :items, [:code]

  end
end
# -*- encoding : utf-8 -*-
class CreateItemImages < ActiveRecord::Migration
  def change
    create_table :item_images do |t|

      t.integer :item_id, null: false

      t.text :image, null: false
      t.string :alt
      t.integer :row_order

      t.timestamps null: false
    end
  
    add_index :item_images, [:item_id]
  
  end

end

マイグレーションファイルを編集したら、以下を実行してテーブルを作成します。

$ rake db:migrate

uploaderの作成

続いてuploaderを作成します。 下記を実行してUploaderを作成します。

$ rails g uploader item_image

app/uploaders/item_image_uploader.rbが作成されるので

下記のように編集します。 下記のサンプルでは、アップロードした画像を、必ず正方形にするようなリサイズをRMagickで行っています。

RMagickの挙動については、こちらがとてもわかり易いです。

noodles-mtb.hatenablog.com

class ItemImageUploader < CarrierWave::Uploader::Base

  include CarrierWave::RMagick

  storage :fog

  # s3のディレクトリ
  def store_dir
    "/img/up/item/#{model.id}"
  end

  version :_100x100 do
    process :resize_and_pad => [100, 100, '#fff']
  end

  version :_280x280 do
    process :resize_and_pad => [280, 280, '#fff']
  end

  version :_540x540 do
    process :resize_and_pad => [540, 540, '#fff']
  end

  version :_800x800 do
    process :resize_and_pad => [800, 800, '#fff']
  end

  # 許可する画像の拡張子
  def extension_white_list
    %w(jpg jpeg gif png)
  end


  def filename
    "img.jpg"
  end

end

デフォルトだと、filenameに"Time.now.strftime('%Y%m%d%H%M%S').jpg"みたいになっていますが、 複数versionファイルを作成して、以下のように versionでアクセスしようとすると オリジンを作った時間と_800x800を作った時間がずれることがあるため決まった定数のようなものを指定したほうがよいです。

item_image.image.url(:_800x800)

s3の作成

s3のバケット作成

サクッと作成できると思うので、今回は割愛します。ご了承ください。

s3のユーザー作成

S3用のユーザーを作成します。 S3だけの権限を与えたユーザーを作った方が良いと思います。

作り方は下記をご参考ください。

blog.cloudpack.jp

carrierwave + fogの設定

config/initializers/carrierwave.rb を作成して以下のように編集します。 S3にアップロードするための接続設定を行っていきます。

CarrierWave.configure do |config|
  config.fog_credentials = {
      :provider               => 'AWS',
      :aws_access_key_id      => ENV['S3_ACCESS_KEY_ID']
      :aws_secret_access_key  => ENV['S3_SECRET_ACCESS_KEY']
      :region                 => 'ap-northeast-1', # Tokyo
      :path_style             => true,
  }

  config.fog_public     = true # public-read
  config.fog_attributes = {'Cache-Control' => 'public, max-age=86400'}

  case Rails.env
    when 'production'
      config.fog_directory = 'xxxxx.com'
      config.asset_host = 'https://s3-ap-northeast-1.amazonaws.com/xxxxx.com'
    when 'staging'
      config.fog_directory = 'staging.xxxxx.com'
      config.asset_host = 'https://s3-ap-northeast-1.amazonaws.com/staging.xxxxx.com'
    when 'development'
      config.fog_directory = 'dev.xxxxx.com'
      config.asset_host = 'https://s3-ap-northeast-1.amazonaws.com/dev.xxxxx.com'
    when 'test'
      config.fog_directory = 'test.xxxxx.com'
      config.asset_host = 'https://s3-ap-northeast-1.amazonaws.com/test.xxxxx.com'
  end
end

S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEYは、作成したS3アクセス用のユーザーのACCESS_KEYとSECRET_ACCESS_KEYを設定してください。 また、xxxxx.com には、作成したbacket名に適宜変更してください。

routes.rd

route.rdは以下のように設定しました。 今回は、:edit, :update, :destroy はありませんが、altカラムの編集と画像の削除に設定しています。

    resources :items, only: [:show]
      resources :item_images, only: [:edit, :update, :destroy] do
        collection do
          post :upload
        end
      end
    end

Modelの編集

各モデルに設定を追加していきます。

Item

# -*- encoding : utf-8 -*-
class Item < ActiveRecord::Base

  has_many :item_images

end

ItemImage

# -*- encoding : utf-8 -*-
class ItemImage < ActiveRecord::Base
  mount_uploader :image, ItemUploader

  belongs_to :item

end

mount_uploaderで先ほど作ったItemUploaderとひも付けをします。

controller

コントローラーの設定

item_image_controller.rb

  def upload
    @item = Item.find(params[:id])
    item_image = @item.item_images.build(image: params['file'])
    item_image.save
  end

View

フロント側のViewのソースはこんな感じです。

Dropzoneを使って フロントから サーバへファイルを送ります。 こちらドラッグ&ドロップで画像を追加できるので、気に入っています。

Dropzone.js

item/show.html

    = render 'items/image_upload_form'

items/_image_upload_form.html.slim

= form_tag(upload_item_item_images_path(@item),
        :id => 'item-image-dropzone', :class => 'dropzone', method: :post)

javascript:

  Dropzone.autoDiscover = false;

  Dropzone.options.itemImageDropzone = {
    paramName: "file",         // input fileの名前
    parallelUploads: 10,            // 1度に何ファイルずつアップロードするか
    acceptedFiles: 'image/*',   // 画像だけアップロードしたい場合
    maxFiles: 10,                      // 1度にアップロード出来るファイルの数
    maxFilesize: 100,                // 1つのファイルの最大サイズ(1=1M)
    dictFileTooBig: "ファイルが大きすぎます。 ({{filesize}}MiB). 最大サイズ: {{maxFilesize}}MiB.",
    dictInvalidFileType: "画像ファイル以外です。",
    dictMaxFilesExceeded: "一度にアップロード出来るのは10ファイルまでです。",
    dictDefaultMessage: 'アップロードする画像をここにドラッグ&ドロップしてください。'
  };
  var itemImageDropzone = new Dropzone("form#item-image-dropzone");

まとめ

きっと、以上でファイルアップロードができると思いす。 適当にモデル等々を変更してください。

関連記事

marketing-web.hatenablog.com

実践Ruby on Rails 4 現場のプロから学ぶ本格Webプログラミング

実践Ruby on Rails 4 現場のプロから学ぶ本格Webプログラミング

お名前.com + Route53で独自ドメインのメールをGmailに転送する

お名前.comにメールの転送サービスがあるのことをご存知でしょうか?

こちらの、お名前.com転送Plus です。

ドメイン取るなら お名前.com:メール転送

お名前.comでドメインを購入すると、 独自ドメインのメールを転送できるサービスです。

お名前.com上でも設定の方法のページはあるのですが、

www.onamae.com

基本的には、お名前.comのDNSを使う方法になっています。

すでに、お名前.com => Route53でホストしちゃっている方向けに, 独自ドメインメールをGmailに転送する方法です。
(Gmail以外の他のメールでももちろんOKです。)

設定方法

まず、オプション設定タブのメール転送設定を選択します。

f:id:jun9632:20160328105411p:plain

ドメインの選択

転送したい、ドメインの右にある「設定する」ボタンをクリックします。

f:id:jun9632:20160328105147p:plain

転送元/転送先の設定

転送元メールアドレスと転送先メールアドレスを設定します。

転送元メールアドレスは、独自のドメインで利用したいメールアドレスとです。

example.comを仮に持っているとして、admin@の受信メールアドレスを作りたい場合です。

例) admin@example.com

転送先メールアドレスは、その名の通り転送先にしていしたいメールアドレスとです。

今回仮にgamilにしていますが、ご自身で利用しているGamil以外のメールでも問題ありません。

例) xxxxxxexampe.com+admin@gmail.com

f:id:jun9632:20160328110015p:plain

ネームサーバー設定の確認

上記設定が完了したら、確認画面に進むボタンをクリックします。

また、下記画像の転送ネームサーバー設定の値を後々のRoute53の設定に使うので メモっておくようにしてください。 

f:id:jun9632:20160328110604p:plain

確認

確認画面で設定した内容が問題なければ、[設定する]ボタンをクリックしてください。

f:id:jun9632:20160328110856p:plain

route53の設定

続いてRoute53の設定です。 先ほど、ネームサーバー設定の確認でメモった情報でMXレコードを追加します。

f:id:jun9632:20160328111251p:plain

内容を上記のように入力して、[Save Record Set]ボタンをクリックして設定完了です。

DNSの変更を待つ

上記設定を行っても、速攻で反映されるわけではありません。 設定が変更されるまでに、少し時間があるので気長に待ってください。

転送されないと焦って、設定をいじらないように注意しましょう。

まとめ

お名前.comを使っていれば、無料で転送できるようなので、とってもありがたいサービスです。 特に個人的なサイトの場合、SSLの購入すするときに admin@example.com みたいなドメインが一瞬必要だったり、 問い合わせ用の info@example.comが欲しかったりと、受信専用のメールアドレスを簡単に作れるのはありがたいです。 しかも、メールの制限は無いので、無制限でアドレスを作ることができます。 受信専用でそんなに必要はないかもしれませんが、 何はともあれ、ちゃちゃっと独自ドメインの受信メールアドレスを作れるので 大変たすかります。

関連記事

marketing-web.hatenablog.com

Amazon Web Services パターン別構築・運用ガイド  一番大切な知識と技術が身につく

Amazon Web Services パターン別構築・運用ガイド  一番大切な知識と技術が身につく

  • 作者: NRIネットコム株式会社,佐々木拓郎,林晋一郎,小西秀和,佐藤瞬
  • 出版社/メーカー: SBクリエイティブ
  • 発売日: 2015/03/25
  • メディア: Kindle版
  • この商品を含むブログを見る

レスポンシブデザインの参考に!Responsive Web Design JP がとてもいい!

f:id:jun9632:20160324132651p:plain

新しいサイトを立ち上げようとした時に、サイトのデザインを考えるのがものすごく苦手です。

ほとんど、プログラマ寄りなので、デザインのようなカッコいいとかオシャレとかには、程遠い生活をしています。

本来のちゃんとしたサイトであれば、デザイナーさんとかにお願いすればよいのかもしれませんが、 個人のサイトであったり、あまりお金をかけられない状況であれば、やっぱり自分でやるしかなくなってしまいますよね。

ここ最近の風潮から、モバイルファースでいかないと行けないと思うので、 レスポンシブルデザインが必須になってくると思います。

そんな時に、サイトデザインの参考になるのが、

この、Responsive Web Design JP です。

responsive-jp.com

キャッチコピーの 「 日本国内の秀逸なレスポンシブWebデザインを集めたギャラリーサイト」

まさにその通りです。

秀逸なレスポンシブWebデザインを集めてくれているサイトです。

見て頂ければわかりますが、 Responsive Web Design JPさんのフィルタがかかっているので、どれもカッコいいです!

あれこれ、頑張って探す必要がないです。

しかも、カテゴリや色、タイプ、技術別にも分かれているので 目的に合わせて、サイトを調べられるのでそれもかなりありがたいです。

ECサイトのデザインはこちらです。

responsive-jp.com

本当にありがたいサイトです。 いろんなサイト見て勉強させて頂きます。

関連記事

marketing-web.hatenablog.com

HTML5+CSS3でつくる!  レスポンシブWebデザイン

HTML5+CSS3でつくる! レスポンシブWebデザイン

  • 作者: 大串肇,齋木弘樹,清野奨,嵩本康志,松尾祥子,松尾慎太郎,宮崎優太郎,吉澤富美
  • 出版社/メーカー: ソーテック社
  • 発売日: 2016/03/17
  • メディア: 単行本
  • この商品を含むブログを見る

Rails4のlink_toでconfirmを表示する方法

f:id:jun9632:20160323131752p:plain

Ruby on Rails 4 で link_toで削除ボタンを作った時に、 「削除します。よろしいですか?」というような、confirmを表示させようと思って、 以下の用に書いたのですが、完全にconfirmが無視されて、削除処理が走ってしまいました。

 = link_to item_path(@item),
                method: :delete,
                class: 'btn btn-danger', 
                :confirm => '削除します。よろしいですか?'
        = icon('trash')
        span 削除

対処方法

下記によれば

ActionView::Helpers::UrlHelper

サンプルにある用に、data-confirmに値をもたせれば良いようなので

以下の用に、書くと解決されます。

 = link_to item_path(@item),
                method: :delete,
                class: 'btn btn-danger', 
                data: { :confirm => '削除します。よろしいですか?'} do
        = icon('trash')
        span 削除

関連記事

marketing-web.hatenablog.com

marketing-web.hatenablog.com

パーフェクトRuby on Rails

パーフェクトRuby on Rails