20210314のRubyに関する記事は22件です。

Action Cableでできることと実装方法

Action Cableは、Railsのアプリケーションと同様の記述で、WebSocket通信という双方向の通信によるリアルタイム更新機能を実装できるフレームワークで、Rails5から実装されました。

Action Cableを利用することで、たとえばリアルタイムで更新されるチャット機能を実装することができます。

チャネルの作成

channel

リアルタイム更新機能を実現するサーバー側の仕組みでデータの経路を設定したり、送られてきたデータを画面上に表示させたりします。

チャネルを作成するコマンド

ターミナル

rails g channel message #チャネル名
# ↓実行結果↓
Running via Spring preloader in process *****
      invoke  test_unit
      create  test/channels/message_channel_test.rb
      create  app/channels/message_channel.rb
   identical  app/javascript/channels/index.js
   identical  app/javascript/channels/consumer.js
      create  app/javascript/channels/message_channel.js

生成されるファイルのうち以下の2つが重要です。
app/controller/message_controller.rb → クライアントとサーバーを結びつけるためのファイル
app/javascript/channels/message_channel.js → サーバーから送られてきたデータをクライアントに描画するためのファイル

クライアントとサーバーを結びつける

stream_fromメソッド

サーバーとクライアントを関連づけるメソッドでAction Cableにあらかじめ用意されています。

app/controller/messages_controller.rb
class MessageChannel < ApplicationCable::Channel
  def subscribed
    stream_from "message_channel" #記述箇所
  end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end
end

broadcast

stream_fromメソッドで関連付けられるデータの経路で、サーバーから送られるデータの経路です。broadcastを介してデータをクライアントに送信します。

broadcastを使ってコントローラーへ以下のように記述します。

app/controller/messages_controller.rb
class MessagesController < ApplicationController
  def new
    @messages = Message.all
    @message = Message.new
  end

  def create
    @message = Message.new(text: params[:message][:text])
    if @message.save
      ActionCable.server.broadcast 'message_channel', content: @message
    end
  end
end

broadcastという経路を通して、message_channelに向けて@messagecontentに入れてデータを送信しています。

JavaScriptへの記述

サーバーから送信されたデータはdataの中にcontentという名称で入っているので、data.content.textで使用できます。

app/javascript/channels/message_channel.js
import consumer from "./consumer"

consumer.subscriptions.create("MessageChannel", {
  received(data) {
    // 以下で使用
    const html = `<p>${data.content.text}</p>`;
    const messages = document.getElementById('messages');
    const newMessage = document.getElementById('message_text');
    messages.insertAdjacentHTML('afterbegin', html);
    newMessage.value='';
  }
});

ちなみにビューはこんな感じです。

app/views/messages/new.html.erb
<h3>mini-talk-app</h3>
<%= form_with model: @message do |f| %>
  <%= f.text_field :text %>
  <%= f.submit '送信' %>
<% end %>
<div id="messages">
  <% @messages.reverse_each.each do |message| %>
    <p><%= message.id %></p>
    <p><%= message.text %></p>
  <% end %>
</div>

content以外もクライアントに送信できる

app/controller/messages_controller.rbでは保存したメッセージの値をcontentに入れて送信していましたが、設定すれば他の値も送ることができます。

ぼくが今開発しているアプリでは、こんな感じで情報を追加してクライアント側に送信しています。

app/controllers/chats_controller.rb
class ChatsController < ApplicationController  def create
    @chat = Chat.new(chat_params)
    if @chat.save
      # userとtimeにそれぞれ@userと@timeを入れている
      @user = current_user
      @time = @chat.created_at.strftime("%Y-%m-%-d %-H:%-M")
      ActionCable.server.broadcast 'chat_channel', content: @chat, user: @user, time: @time
    end
  end

  private
  def chat_params
    params.require(:chat).permit(:text).merge(
      user_id: current_user.id,
      group_id: params[:group_id]
    )
  end
end

クライアント(JavaScript)側ではこのようにデータを利用しています。JavaScript側で投稿日時の表示形式を変更する方法が分からなかったので、事前にサーバー(Ruby)側で表示形式を変更してから送信しています。

app/javascript/channels/chat_channel.js
import consumer from "./consumer"

consumer.subscriptions.create("ChatChannel", {
  received(data) {
    // 送信されたデータを以下で使用
    const html = `<div class="each-chat">
                    <p class="chat-time">${data.time}</p>
                    <p class="chat-text">${data.content.text}</p>
                    <p class="chat-user">${data.user.name}</p>
                  </div>`;
    const chats = document.getElementById("chats");
    chats.insertAdjacentHTML("afterbegin", html);
    const newChat = document.getElementById("chat_text");
    newChat.value="";
  }
});

参考資料

【Rails6.0】ActionCableを使用したライブチャットアプリを実装する手順を解説

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

docker内でGem(特にbundler)のdefault設定を解除したい!

概要

ローカル環境でも厄介なGemのdefault設定。
「このバージョンのGemで運用していきたいのに!」といった気持ちに反して、bundle installすると、default設定されたバージョンでインストールされてしまう。。。

ローカル環境ではその解除はできるが、Dockerのコンテナ内では少しだけ訳が違ったので、まとめたいと思い本記事を制作しました。

不足点、誤りがあれば遠慮なくご指摘いただけますと幸いです。

経緯

これまでローカル環境にて開発を行い、いざ本番環境へデプロイ!となるとエラーが返され、苦い思いをしてきた私。
今回はdocker-compose upで立ち上げたコンテナ内でgem listを見ると、ローカルでのgem listとバージョンの違いが生じていました。
そこで、コンテナ内でも指定したバージョンのGemで運用したく奮闘したのが経緯です。

方法

①コンテナ内に入る

ローカルにて自身のアプリのディレクトリにて

% docker-compose exec web bash 
root@コンテナID:/アプリ名#        以降プロンプト以外は省略します
②Gemを確認(省略可能)
# gem list bundler ※ちなみに全てのGemをリスト化するには"bundler"を省略

*** LOCAL GEMS ***

bundler (2.1.4, default: 1.17.2)

**この忌々しいdefaultを解除していきます。

③default設定されているGemのありかを探る。
# gem environment

RubyGems Environment:

...略

  - INSTALLATION DIRECTORY: /usr/local/bundle
  - USER INSTALLATION DIRECTORY: /root/.gem/ruby/2.6.0

...略

  - GEM PATHS:
     - /usr/local/bundle
     - /root/.gem/ruby/2.6.0
     - /usr/local/lib/ruby/gems/2.6.0

...略

ここでちなみにローカルで同様にgem environmentコマンドを実施した時の結果を下記に記します。

% gem environment 

RubyGems Environment:

...略

  - INSTALLATION DIRECTORY: /Users/ユーザー/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0
  - USER INSTALLATION DIRECTORY: /Users/ユーザー/.gem/ruby/2.6.0

...略

  - GEM PATHS:
     - /Users/ユーザー/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0
     - /Users/ユーザー/.gem/ruby/2.6.0

...略

INSTALLATION DIRECTORYに注目してください。
指定されているディレクトリがわずかに違います。
⑤の項目にてさらに解説します。

④ホームに戻る
# cd

root@コンテナID:~#
⑤default設定のあるディレクトリまでたどり着く

ここで、ローカル環境でのたどり着き方の場合は、INSTALLATION DIRECTORYに記載されているパスまでcdコマンドで行けばいいのですが・・・

ローカルの場合!

% cd /Users/ユーザー/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0
ユーザー@PC 2.6.0 %

コンテナ内の場合!!

# cd /usr/local/lib/ruby/gems/2.6.0
root@コンテナID:/usr/local/lib/ruby/gems/2.6.0#

INSTALLATION DIRECTORYに記載されているパスでは「そんなディレクトリは無い!!」と怒られたので、別のディレクトリを探したところ,
GEM PATHSに記載のあったディレクトリが正解だったようです。

⑥specificationsの中のdefaultを削除する

lsコマンドを使ってみると、このディレクトリの中にspecificationsというディレクトリがあるはずです。
この中にdefault設定があります。
※気になる方はここで、cd specificationsして、lsコマンドをすると、defaultと記載のあるGemがいくつかあると思います。

# rm -rf specifications/default/

これでspecifications内のdefaultディレクトリが全て削除されます。

⑦念の為、確認。
# gem list bundler

*** LOCAL GEMS ***

bundler (2.1.4)

bundlerのdefaultが解除されています。
ここでbundle installし、Gemfile.lockを確認すると最後の行に・・・

BUNDLED WITH
   2.1.4

と、自分の好きなバージョンでバンドルされていることが確認できました!

考察

ここで学んだことが2つあります。

まず1つ
当然ではありますが、ローカル環境とコンテナ環境は別物。Gemのバージョンの相違に気づけたこと。

もう1つ
同じgem environmentに返される結果は、似てこそあれども微妙な違いがあること。
自分の求める情報はどこに記載があるのか注視しなければならない。

これまでデプロイの時に何かしらのエラーに叩きのめされていたので、docker使用時に未然にGemのバージョンを合わせれて良かったと思います。

誰かの参考になれば幸いです。
ありがとうございました。

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

ゲッター・セッター・アクセサメソッド

前提

インスタンス変数はクラスの外から呼び出すことができない

class User
  def initialize(name)
    @name = name
  end
end
user = User.new('Alice')
puts @name

=> #クラスの外からインスタンス変数を呼び出した為出力されず

ゲッターメソッドとは

クラス内からインスタンス変数を参照するメソッド

セッターメソッドとは

クラス内からインスタンス変数を更新するメソッド

@nameをクラスの外から参照するためのメソッドを定義してみる(ゲッターメソッド)

class User
  def initialize (name)
    @name = name
  end
#ゲッターメソッド
  def name
    @name
  end
end

user = User.new('Alice')
puts user.name 

=> 'Alice'

@nameの内容をクラスの外から変更したい場合も変更用のメソッドを定義する(セッターメソッド)

class User
  def initialize(name)
    @name = name
  end
  # ゲッターメソッド
  def name
    @name
  end
  # セッターメソッド(rubyでは=で終わるメソッド名を定義すると変数に代入する形式でそのメソッドを呼び出す)
  def name=(value)
    @name = value
  end
end

user = User.new('Alice')

# ↓ここは変数に代入してる訳ではなく、name=というメソッド(セッター)を呼び出してる。また=の前にスペースがあっても使える
user.name = 'Bob'
puts user.name
=>'Bob'

アクセサメソッド

上記でやったセッターやゲッターを定義せずに省略することができる
メソッド名 内容
attr_reader: 変数名 参照を可能にする(ゲッター)
attr_writer: 変数名 更新を可能にする(セッター)
attr_accessor: 変数名 参照と更新両方可能にする(ゲッター・セッター)

attr_accessorメソッド

参照と更新両方可能にする(ゲッター・セッター)

class User
  attr_accessor :name

  def initialize (name)
    @name = name
  end
end

user = User.new('Alice')
#変更
user.name = 'Bob'
#参照
puts user.name 
=> 'Bob'

attr_readerメソッド

参照できるが変更はできない(ゲッター)

class User
  attr_reader :name

  def initialize (name)
    @name = name
  end

end

user = User.new('Alice')
#参照は可能
puts user.name
=> 'Alice'

user.name = 'Bob'
=> 'undefined method `name=' for #<User:0x00007fca8b11eb28 @name="Alice"> (NoMethodError)
Did you mean?  name'

attr_writerメソッド

変更できるが参照できない(セッター)

class User
  attr_writer :name

  def initialize (name)
    @name = name
  end

end

user = User.new('Alice')
#変更は可能
user.name = 'bob'
puts user.name 
=> undefined method `name' for #<User:0x00007fc7c48d87c0 @name="bob"> (NoMethodError)
Did you mean?  name=

まとめ

インスタンス変数はクラスの外から参照することができない
セッターゲッターがあることでクラスの外からインスタンス変数を参照したり更新することができる
アクセサメソッド使うとセッターとゲッターの定義を省略することができる

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

Rubyにおけるオブジェクト、クラス、インスタンス、メソッド、インスタンス変数とは

※この文章はアウトプットに挑戦した初学者の単なる走り書きです。

タイトル通り、Rubyにおけるオブジェクト、クラス、インスタンス、メソッド、インスタンス変数という言葉がどのような意味を持つのかを記述していきます。プログラミングにおいては何かが何かを包含してピラミッド構造のようなものを構成している点が特徴的だと感じたので、それに着目し、より高次の概念を表す用語が上に来るように書きました。

オブジェクト

Rubyではあらゆる「データ」がオブジェクトです。Rubyのなかでオブジェクトではないものはないと思い込んでいたのですがそれは誤りで、「データ」でないメソッドなどはオブジェクトではありません。

クラス

クラスはオブジェクトを生み出し、オブジェクトの振る舞いを規定するものです。

インスタンス

全てのオブジェクトは何らかのクラスに属します。その際に「所属先」のクラスに対し「所属するオブジェクト」がインスタンスと呼ばれます。

メソッド

メソッドとはオブジェクトについての一連の処理を定義したものです。例として文字列オブジェクトの文字数を返すlengthメソッドなどがあります。メソッドはデータではないため、必然的にオブジェクトではないということになります。
また、メソッドが呼び出されたオブジェクトをそのメソッドのレシーバといいます。

インスタンス変数

メソッド内で定義された変数は通常メソッドの外では機能しませんが、@から始まるインスタンス変数なら、クラス内のインスタンスの中でならどこでも使えます。

では結局オブジェクトとは何なのか?

オブジェクトとは何かを理解するには、理屈だけでなく感覚的にオブジェクトを理解することが必要だそうです。自分がその域に達するのはまだ先のように思えますが、今のところオブジェクトとは、自然言語における主語と述語のような相互補完関係をメソッドと築いているものなのかなと思います。(メソッドは繋げられるが自然言語で述語が連続したら文法的におかしいという違いはありますが)

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

【Rails6】flatpickrのカレンダーを使ってDBにパラメータを保存する方法

flatpickrのカレンダーを使ってDBにパラメータを保存する方法を解説します。

flatpickrのカレンダーを使ってみたけど、DBにパラメータを保存できない。
視覚的にわかりやすいカレンダータイプの日付選択を採用したい。
カレンダーからポチポチして日付を選べるようにしたい。

など、ポートフォリオ作成などにも役立つと思い記事にしてみました。

完成後のアプリ

Image from Gyazo

開発環境

・macOS Catalina 10.15.7
・Ruby 2.7.2
・Rails 6.0.3.4
・flatpickr 4.6.9

YouTube

・近々YouTubeに動画投稿予定

1. 環境構築

  • 以下を前提に進行
    • トップページはcalendars#indexの前提で進行
    • データベースはPosgreSQL

1.1 アプリの作成

ターミナル
rails new flatpickr_sample -d postgresql
cd flatpickr_sample
  • 今回アプリ名はflatpickr_sampleとします。
  • アプリ作成後はcdコマンドで該当アプリのディレクトリに移動することを忘れないようにしましょう。

1.2 gemのインストール

Gemfile
# 中略
gem 'bootsnap', '>= 1.1.0', require: false

# ***** 以下を追加 *****
# 日本語化
gem 'rails-i18n', '~> 6.0'
# ***** 以上を追加 *****

# 中略

group :development do
  # 中略

  # ***** 以下を追加 *****
  # デバッグで利用
  gem 'pry-byebug'
  # ***** 以上を追加 *****

end
ターミナル
bundle install
rails db:create
  • gemの追加後は忘れずbundle installを実行
  • その後rails db:createでデータベースを作成

【要確認】ターミナルにてrails sでサーバーを起動して、http://localhost:3000/ に接続して「Yay! You’re on Rails!」が出るか確認しましょう。


1.3 モデル、テーブル、コントローラの作成

ターミナル
rails g model Calendar date:date
rails db:migrate
  • 今回モデル名はCalendar、カラムはdateカラムを使用します。
  • rails db:migrateによりテーブルが作成されます。

【要確認】上記コマンドによりapp/models/calendar.rbファイル、db/migrate/..._create_calendars.rbファイル、db/schema.rbファイルが作成されています。


ターミナル
rails g controller calendars index new create
  • コントローラ名はcalendarsとします。コントローラ名は原則複数形なので注意しましょう。
  • rails g controller calendars(コントローラ名) index(コントローラアクション名)で、アクション名のビューファイルも一緒に作成されます。

ターミナル
rm -f app/views/posts/create.html.erb
  • 今回使用しない不要なファイルをrm -fコマンドで削除しましょう。

1.4 ルーティング設定

config/routes.rb
Rails.application.routes.draw do
  root "calendars#index"
  resource :calendars, only: [:index, :new, :create]
end

1.5 flatpickrの導入

ターミナル
yarn add flatpickr
  • yarnを使用してflatpickrを導入します。
  • yarnが使用できない場合、先にyarnのインストールをしましょう。

app/assets/stylesheets/application.scss
/* ...
 *= require_tree .
 *= require_self

# ***** 以下を追加 *****
 *= require flatpickr
# ***** 以上を追加 *****

 */

【注意】app/assets/stylesheets/application.cssの拡張子cssをscssに変更するのを忘れずに


app/javascript/packs/application.js
require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
// ***** 以下を追加 *****
require('flatpickr')
require('flatpickr/dist/l10n/ja')
// ***** 以上を追加 *****
app/javascript/packs/calendar.js
// ***** 以下を追加 *****
 flatpickr.localize(flatpickr.l10ns.ja)
  const config = {
    inline: true,
  }
  flatpickr('#calendar_form', config);
});
// ***** 以上を追加 *****
  • app/javascript/packsディレクトリ内にcalendar.jsファイルを作成する。
  • 今回id名はcalendar_formとします。上記の5行目の#以降がid名となります。

app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>FlatpickrSample</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>
    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
<!-- ***** 以下を追加 ***** -->
    <%= javascript_pack_tag 'calendar', 'data-turbolinks-track': 'reload' if controller_name == 'calendars' %>
<!-- ***** 以上を追加 ***** -->

  </head>
  <body>
    <%= yield %>
  </body>
</html>
  • 上記の追加は、コントローラ名がcalendarsと時のみ、packs内のcalendar.jsに対してターボリンクスをするという記述になります。

2 パラメータをDBに保存する

2.1ビューファイルの編集

app/views/calendars/new.html.erb
<!-- ***** 以下を追加 ***** -->
<%= form_with model: @calendar do |form| %>
  <input id ="calendar_form" type="text" name="calendar[date]" readonly="readonly" >
  <%= form.submit "送信" %>
<% end %>
<!-- ***** 以上を追加 ***** -->
  • dateカラムのパラメータを保存するためにヘルパーメソッドのform_withを使用します。
  • app/javascript/packs/calendar.js内で設定したid名と、上記2行目のinput id ="id名"を一致させることでflatpickrのカレンダーを呼び出します。

app/views/calendars/index.html.erb
<!-- ***** 以下を追加 ***** -->
<%= link_to "日付投稿ページへ", new_calendars_path %>
<h1>日付一覧</h1>
<% @calendars.each do |calendar| %>
  <%= calendar.date %>
<% end %>
<!-- ***** 以上を追加 ***** -->

・上記はカレンダーで送信されたパラメータを一覧表示するための記述です。


2.2 コントローラの編集(index,new,create)

app/controllers/calendars_controller.rb
class CalendarsController < ApplicationController
  def index
  # ***** 以下を追加 *****
    @calendars = Calendar.order(id: :asc)
  # ***** 以上を追加 *****
  end

  def new
  # ***** 以下を追加 *****
    @calendar = Calendar.new
  # ***** 以上を追加 *****
  end

  def create
  # ***** 以下を追加 *****
    save_params = params[:calendar][:date].to_date
    Calendar.create!(date: save_params)
    redirect_to root_path
  # ***** 以上を追加 *****
  end

end
  • 結論から言うと、上記のコードを追加したらパラメータがDBへ保存されます。なぜ保存できたかを次に詳しく説明します。

2.3 なぜそのままではパラメータが保存できないのか

app/controllers/calendars_controller.rb
class CalendarsController < ApplicationController
  def index
    @calendars = Calendar.order(id: :asc)
  end

  def new
    @calendar = Calendar.new
  end

  def create
  # ***** 以下を追加 *****
    binding.pry
  # ***** 以上を追加 *****
    save_params = params[:calendar][:date].to_date
    Calendar.create!(date: save_params)
    redirect_to root_path
  end

end

【確認手順】
1. binding.pryを上記の位置に追加する。
2. ターミナルにてrails sでサーバーを起動して、http://localhost:3000/ に接続する。
3. カレンダーから適当な日付を選択して送信する。
4. 下記のように処理が止まったのを確認し、paramsと入力する。送信されたパラメータの中身を確認できます。


ターミナル
    10: def create
    11:   binding.pry
 => 12:   save_params = params[:calendar][:date].to_date
    13:   Calendar.create!(date: save_params)
    14:   redirect_to root_path
    15: end

[1] pry(#<CalendarsController>)> params
=> <ActionController::Parameters {"authenticity_token"=>"0E0nP7UnRkkg+4ZL2h7vZ160OkJP0NAQEkNaYF4nAC5+KH6JKSOKkN6rMB0y4eMthFRZIYGtBHHOLIOTCf5ekg==", "calendar"=>{"date"=>"2021-03-09"}, "commit"=>"送信", "controller"=>"calendars", "action"=>"create"} permitted: false>
[2] pry(#<CalendarsController>)>
  • 上記の青字のコードがカレンダーから送られたパラメータになります。不要な部分が多いことがわかります。必要な部分は"calendar"=>{"date"=>"2021-03-09"}部分のみになります。

ターミナル
[2] pry(#<CalendarsController>)> params[:calendar]
=> <ActionController::Parameters {"date"=>"2021-03-09"} permitted: false>
  • params[:calendar]と入力することで青字の"date"=>"2021-03-09"が抽出できました。ですが、まだ保存できる形ではありません。

ターミナル
[4] pry(#<CalendarsController>)> params[:calendar][:date]
=> "2021-03-09"
  • params[:calendar][:date]と入力することで青字の"2021-03-09"が抽出できました。これで保存されそうな気がしますが、これでは文字列扱いなので保存ができません。

ターミナル
[6] pry(#<CalendarsController>)> params[:calendar][:date].to_date
=> Wed, 03 Mar 2021
  • params[:calendar][:date].to_dateとすることで=> Wed, 03 Mar 2021という結果となりました。to_dateメソッドを使うことでdateカラムに保存されるようにうまいこと変換してくれます。

save_params = params[:calendar][:date].to_date
    Calendar.create!(date: save_params)
  • 最後にparams[:calendar][:date].to_dateを変数save_paramsにして、Calendar.create!(date: save_params)でdateカラムに該当する日付を保存します。

3 まとめ

flatpickrのカレンダーを利用した場合、コントローラのcreateアクションでパラメータを以下のようにする必要があります。これでDBに保存できるようになりました。

save_params = params[:calendar][:date].to_date
    Calendar.create!(date: save_params)

flatpickrにBootstrapを適応させてキレイに作成したり、日付を複数選択できるようにする方法を追い追い追記していきます。ここまでお疲れさまでした。

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

サーバーの構築からソースコードのデプロイまで(Amazon Linux 2, Nginx + Puma, Rails 6.1, Ruby2.7.2)

この記事での注意

この作業はローカル環境からの作業とサーバーでの作業が複雑に入り交じっています。その為、ターミナルで実行するコマンドの説明に「ローカル」「サーバー」と記載しています。お間違えないように気をつけてください。

アプリケーション名を「sample611」 としています。ご自身のアプリケーション名に読み替えてご参考ください。

サーバーにインストールする環境

  • Amazon Linux 2
  • Ruby 2.7.2 (rbenv)
  • Rails 6.1
  • MySQL or PostgreSQL
  • Nginx + Puma 5
  • Node.js 12
  • Yarn

本番サーバーの構築

本番サーバーのEC2のOSがAmazon Linux2の場合の設定手順を紹介します。

1. システムライブラリアップデート

まずはAmazon Linux 2のシステムライブラリを更新します。

サーバー
$ sudo yum update -y

2. タイムゾーン、ロケール

日本での運用する場合はタイムゾーンと言語の設定を日本にした方がわかりやすいと思います。

サーバー
$ sudo timedatectl set-timezone Asia/Tokyo
$ sudo localectl set-locale LANG=ja_JP.UTF-8

3. NTP

サーバーの時刻のずれを自動で補正するタイムサーバーの設定を行います。

サーバー
$ yum info chrony
$ chronyc sources -v

4. 標準パッケージインストール

Rubyのインストールなどで必要になるライブラリをインストールします。

サーバー
$ sudo yum -y install gcc-c++ glibc-headers openssl-devel readline libyaml-devel readline-devel zlib zlib-devel libffi-devel libxml2 libxslt libxml2-devel libxslt-devel git

5. データベースモジュールのインストール

Railsからデータベースにアクセスするためのアダプタをインストールする必要がありますが、そのインストールに必要なライブラリをデータベース別にインストールします。

  • PostgreSQLの場合
サーバー
$ sudo yum -y install postgresql-devel
  • MySQLの場合
サーバー
$ sudo yum -y install mysql-devel

6. Rubyのインストール

Railsを動かすためにはRubyのインストールが必要です。いろんなインストール方法がありますが、Rubyのバージョンを切り替えることができるrbenvを使った方法を説明します。

6.1 rbenvのインストール

サーバー
$ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
$ source ~/.bash_profile
$ rbenv -v

6.2 ruby-buildのインストール

サーバー
$ git clone git://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
$ cd ~/.rbenv/plugins/ruby-build
$ sudo ./install.sh
$ rbenv install -l

6.3 Rubyのインストール

rbenvとruby-buildをインストールが終わったので、Rubyのインストールを行います。

サーバー
$ rbenv install 2.7.2
$ rbenv global 2.7.2

rubyのインストールにはすこし時間がかかります。
rbenv globalでシステム全体で使用するRubyのバージョンを指定しています。

7. Rails 6で必要なモジュールのインストール

7.1 Node.jsのインストール

Rails 6からは Node.jsYarn が必要になりました。Node.jsはサーバーサイドでのJavaScript実行環境で、Yarnはfacebook製のJavaScriptのパッケージマネージャーです。

サーバー
$ curl -sL https://rpm.nodesource.com/setup_14.x | sudo bash -
$ sudo yum install -y nodejs
$ sudo npm install -g n
$ sudo n stable
$ sudo ln -snf /usr/local/bin/node /usr/bin/node

7.2 Yarnのインストール

サーバー
$ curl --silent --location https://dl.yarnpkg.com/rpm/yarn.repo | sudo tee /etc/yum.repos.d/yarn.repo
$ sudo yum install yarn -y

8. Railsのインストール

Rails 6のインストールを行います。gem install railsでもインストールできますが、ここではパッケージ管理ソフトbundlerからインストールする方法を紹介します。

サーバー
$ cd ~
$ mkdir rails
$ cd rails
$ bundle init
$ vim Gemfile
(gem "rails"をコメントアウト解除)

$ bundle install
$ bundle exec rails -v
(インストールされたことを確認)

$ cd ..
$ rm -rf rails

データベースの設定

9. 本番データベースの作成

ここではRDSの設定については詳しく説明しません。すでにRDSにデータベースが設定されていることを前提に進めていきますが、下記をご確認ください。

  1. RDSにて作成したデータベースのユーザー名、パスワード、データベースエンドポイントをご確認ください。
  2. RDSのセキュリティグループでウェブサーバーとなるEC2のセキュリティグループのアクセス許可を設定してください。

10. Railsアプリと本番データベースの接続設定

Railsとデータベースの接続設定を行います。データベースの設定にはデータベースにアクセスするためのパスワードが含まれますが、これを直接database.ymlに記載すると、github上でパスワードを知ることができてしまい危険です。

こういった機密情報は config/credentials.yml.enc にセットします。(Rails5.2以降で可能)

このファイルはエディタで表示すると暗号化された文字列になっていますが、 master.key を使うことで閲覧、編集することができます。この credentials.yml.encmaster.key の関係を理解しておいてください。

次のコマンドでcredentials.yml.encを編集します。ここではエディタをvimに指定しています。

ローカル
$ EDITOR=vim bin/rails credentials:edit

下記のようにdbの項目を追記し、RDS作成時に設定したユーザー名、パスワード、データベースエンドポイントを記述します。

credential.yml.enc(ローカル)
# aws:
#   access_key_id: 123
#   secret_access_key: 345

# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
secret_key_base: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

db:
  username: (username)
  password: (password)
  host: (database endpoint)

終わったら、:wqでエディタを終えます。

次に本番環境において、データベースの設定が適用されるようconfig/database.ymlを編集します。database.ymlの最後の方にproductionの項があります。そこを下記のように修正します。

database.yml(ローカル)
production:
  <<: *default
  database: sample611_production
  username: <%= Rails.application.credentials.db[:username] %>
  password: <%= Rails.application.credentials.db[:password] %>
  host:     <%= Rails.application.credentials.db[:host] %>
  port:     5432

Rails.application.credentials.dbとすることでcredentials.yml.encのdbの項を読みにいきます。
portはデータベースで使用するポートを指定します。一般的にはPostgreSQLの場合は 5432、MySQLの場合は 3306 になります。

ここで、改修したコードをいったんコミットし、githubへプッシュしましょう。

ローカル
$ git add .
$ git commit -m "Setup database"
$ git push origin master

デプロイ設定

デプロイツールCapistranoを使ったデプロイの設定を行います。

11. Capistranoのインストール

Capistranoをインストールします。下記のようにGemfileのdevelopmentグループにcapistrano, capistrano-rails, capistrano-rbenvを追加し、bundle installします。

Gemfile(ローカル)
group :development do
  gem "capistrano", "~> 3.14", require: false
  gem "capistrano-rails", "~> 1.6", require: false
  gem 'capistrano-rbenv', '~> 2.2'
end
ローカル
$ bundle install
$ bundle exec cap install

12. Capfileの設定

下記のようにCapfileに追記します。

Capfile(ローカル)
  :
# require "capistrano/rails/migrations"
# require "capistrano/passenger"
require 'capistrano/rails'
require 'capistrano/rbenv'
  :

13. デプロイの設定

Capistranoのデプロイ設定を行います。全体的なデプロイの設定を「config/deploy.rb」、本番環境用の設定を「config/deploy/production.rb」に記述します。

config/deploy.rb(ローカル)
lock "~> 3.15.0"

set :application, "sample611"

# 自分のリポジトリを設定
set :repo_url, "git@github.com:yukeippi/sample611.git"

# rbenvの設定
set :rbenv_type, :user
set :rbenv_ruby, '2.7.2'

# Railsアプリの設置先
set :deploy_to, "/var/rails/sample611"

# 共有ファイルの設定
append :linked_files, "config/master.key"

# 共有ディレクトリの設定
append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "public/system"

# リリース保存数
set :keep_releases, 5

linked_filesはデフォルトではconfig/database.ymlとなっていますが、config/master.keyに変更してください。
次にconfig/deploy/production.rbを編集します。

下記の (server ip) にはデプロイ先EC2のIPをセットしてください。
githubへのキー登録については下記をご参照ください。

config/deploy/production.rb(ローカル)
set :rails_env, 'production'
set :branch, 'master'

# githubへの設定
set :ssh_options, {
  auth_methods: [ 'publickey' ],
  keys:         [ '~/.ssh/aws.pem' ],
}

server '(server IP)', user: 'ec2-user', roles: %w{app db web}

区切りが良いので、ここでいったんコミット、プッシュしましょう。(プッシュは任意です)

ローカル
$ git add .
$ git commit -m "Setup capistrano"
$ git push oririn master

14. Githubへのキー登録

デプロイ時にデプロイ先サーバーがgithubからソースコードを取得するための設定を行います。
この設定は下記の記事をご参照ください。

15. デプロイ先ディレクトリの作成

Railsアプリのプログラムの設置(デプロイ)を行います。アプリケーションの設置場所を「/var/rails」、実行者を「ec2-user」とした場合、下記のコマンドを実行し、ディレクトリを作ります。

サーバー
$ cd /var/
$ sudo mkdir rails
$ sudo chown ec2-user:ec2-user rails

ここで「cap deploy:check」を実行しますが、下記のように失敗します。この処理の目的は、Capistranoにデプロイに必要なディレクトリを作らせることです。

ローカル
$ bundle exec cap production deploy:check

00:02 deploy:check:make_linked_dirs
      01 mkdir -p /var/rails/sample611/shared/config
    ✔ 01 ec2-user@(server IP) 0.078s
00:02 deploy:check:linked_files
      ERROR linked file /var/rails/sample611/shared/config/master.key does not exist on (server IP)

エラーメッセージにあるように「master.key」がないことが原因です。サーバーに入って下記のようにmaster.keyを作成してください。

サーバー
$ cd /var/rails/sample611/shared/config/
$ vim master.key
(ローカルにあるmaster.keyを貼り付け)

これで問題は解決しましたので、再度、デプロイチェックを行います。

ローカル
$ bundle exec cap production deploy:check

成功するはずです。これでデプロイ先ディレクトリができました。

16. Databaseのcreate

しかし、まだこの時点でもデプロイ途中で失敗します。それはデータベースがcreateされていないからです。deployでデータベースのmigrateは実行しますが、createしていないため、migrateできずに失敗してしまいます。

そこで、失敗前提でデプロイを実行し、そのときにデプロイされたソースコードを使ってcreateします。

ローカル
$ bundle exec cap production deploy

最初はgemをインストールするため、時間がかかります。
しばらく待つとデプロイは終わり、途中で失敗していることが確認できます。その後、本番サーバーに入り、下記のようにデプロイされたソースコードのルートに移動し、データベースを作成します。

サーバー
$ cd /var/rails/sample611/releases/(最新リリース)/
$ bundle exec rails db:create RAILS_ENV=production

(最新リリース)は「20210216104333」といったディレクトリ作成日時を意味するディレクトリを指します。ソースコードがデプロイされると、Capistranoがこのようなディレクトリを作成し、そこにプログラムを保存します。

これでデータベースが作成されたら、デプロイが可能になります。
再度デプロイします。

ローカル
$ bundle exec cap production deploy

これで成功するはずです。

WebサーバーとRailsアプリの連携

17. Pumaの設定

本番環境のPumaの設定を行います。

config/puma/production.rb(ローカル)
# Puma can serve each request in a thread from an internal thread pool.
# The `threads` method setting takes two numbers: a minimum and maximum.
# Any libraries that use thread pools should be configured to match
# the maximum value specified for Puma. Default is set to 5 threads for minimum
# and maximum; this matches the default thread size of Active Record.
#
max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
threads min_threads_count, max_threads_count

# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
#
port        ENV.fetch("PORT") { 3000 }

# Specifies the `environment` that Puma will run in.
#
environment ENV.fetch("RAILS_ENV") { "production" }

# Specifies the `pidfile` that Puma will use.
# pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }

# Specifies the number of `workers` to boot in clustered mode.
# Workers are forked web server processes. If using threads and workers together
# the concurrency of the application would be max `threads` * `workers`.
# Workers do not work on JRuby or Windows (both of which do not support
# processes).
#
# workers ENV.fetch("WEB_CONCURRENCY") { 2 }

# Use the `preload_app!` method when specifying a `workers` number.
# This directive tells Puma to first boot the application and load code
# before forking the application. This takes advantage of Copy On Write
# process behavior so workers use less memory.
#
# preload_app!

# Allow puma to be restarted by `rails restart` command.
plugin :tmp_restart

bind "unix:///var/rails/sample611/shared/tmp/sockets/puma.sock"

RAILS_ENVをproductionに指定しているところと、最下行でsocketを指定しているのがポイントです。

ここでいったんコミット、プッシュしてください。

ローカル
$ git add .
$ git commit -m "Setup puma for production"
$ git push origin master

18. Nginxのインストール

ここからはサーバーでの作業になります。
サーバーにNginxをインストールします。

サーバー
$ sudo amazon-linux-extras install nginx1 -y

19. NginxとPumaの紐付け

次にNginxとPumaを紐付けるための設定ファイルを作成します。ポイントはuptreamの設定、rootの指定、proxy_passの指定です。

サーバー
$ sudo vim /etc/nginx/conf.d/sample611.conf
/etc/nginx/conf.d/sample611.conf(サーバー)
upstream puma {
    server unix:///var/rails/sample611/shared/tmp/sockets/puma.sock fail_timeout=0;
}

server {
    listen 80;
    server_name example.com;

    root /var/rails/sample611/current/public;

    try_files $uri/index.html $uri @railsapp;

    location @railsapp {
        proxy_pass http://puma;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
    }

    error_page 500 502 503 504 /500.html;
    client_max_body_size 4G;
    keepalive_timeout 10;
}

upstream puma内にあるpuma.sockのパスに気をつけてください。これは15のbindで設定したパスにあわせなければなりません。

20. Pumaのサービス設定

次にpumaをsystemctlで起動や再起動ができるように設定を行います。

サーバー
$ sudo vim /etc/systemd/system/puma.service
/etc/systemd/system/puma.service(サーバー)
[Unit]
Description=Puma HTTP Server
After=network.target

[Service]
Type=simple
Environment="RAILS_ENV=production"
WorkingDirectory=/var/rails/sample611/current
ExecStart=/home/ec2-user/.rbenv/shims/bundle exec /var/rails/sample611/shared/bundle/ruby/2.7.0/bin/puma -C /var/rails/sample611/current/config/puma/production.rb
Restart=always

[Install]
WantedBy=multi-user.target

ここでpumaの実行パスや実行時の設定ファイルを指定しています。
あと、このままでは実行権限がないため、下記のコマンドでpuma.serviceに実行権限を付与してください。

サーバー
$ sudo chmod +x /etc/systemd/system/puma.service

これでサーバー側の設定は終わりです。

21. Puma, Nginxの起動

いよいよウェブサーバー、アプリケーションサーバーの起動です。まず、Pumaを起動します。

サーバー
$ sudo systemctl daemon-reload
$ sudo systemctl start puma

下記のコマンドで起動を確認します。

サーバー
$ sudo systemctl status puma

● puma.service - Puma HTTP Server for sample611 (production_mysql)
   Loaded: loaded (/etc/systemd/system/puma.service; enabled; vendor preset: disabled)
   Active: active (running) since 土 2021-02-20 15:47:30 JST; 27min ago
 Main PID: 2876 (bundle)
   CGroup: /system.slice/puma.service
           └─2876 puma 5.1.1 (tcp://0.0.0.0:3000,unix:///var/rails/sample611/shared/tmp/sockets/puma.sock) [20210216072308]

上記のようになければ「Active: active」になっていれば、起動成功です。
もし「Active: inactive」になっていれば、何かしらの設定に間違いがある可能性があります。
journalctl -xe等を使って原因を調べてください。

次にnginxの起動します。

サーバー
$ sudo systemctl start nginx.service

これでブラウザを使って下記のようにサイトにアクセスすれば画面に表示されるはずです。

http://(サイトのIP)/

21. デプロイ時のリスタート設定

プログラムをデプロイしたときはアプリケーションサーバーをリスタートする必要があります。capistranoのrestartタスクを追加しておくと、それも自動で実行してくれます。

config/deploy.rb(ローカル)
namespace :deploy do
  task :restart do
    on roles(:web), in: :sequence, wait: 5 do
      within release_path do
        execute "sudo systemctl daemon-reload"
        execute "sudo systemctl restart puma"
      end
    end
  end

  after :finishing, :restart
end

これでローカル側のコード改修が終わりです。
最後にここまでをコミットしましょう。

ローカル
$ git add .
$ git commit -m "Setup restart puma"

再起動することを確認するため、デプロイします。

ローカル
$ bundle exec cap production deploy

 :
00:34 deploy:cleanup
      Keeping 5 of 6 deployed releases on (server IP)
      01 rm -rf /var/rails/sample611/releases/20210314010936
    ✔ 01 ec2-user@(server IP) 0.797s
00:35 deploy:restart
      01 sudo systemctl daemon-reload
    ✔ 01 ec2-user@(server IP) 0.199s
      02 sudo systemctl restart puma
    ✔ 02 ec2-user@(server IP) 0.094s
00:35 deploy:log_revision
      01 echo "Branch master (at 0e3b185044e3a56f20a3bf37d7c6ce31f5a92b54) deployed as release 20210314015609 b…
    ✔ 01 ec2-user@(server IP) 0.119s

デプロイログからrestartが実行されていることがわかると思います。
これでデプロイは成功です!お疲れ様でした。

puma, nginxの自動起動設定

最後にpuma,nginxの自動起動設定を行っておくとよいでしょう。これを行うことでサーバーを再起動しても自動でWebアプリケーションを起動してくれます。

サーバー
$ sudo systemctl enable puma.service
$ sudo systemctl enable nginx.service

以上です。

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

エスケープシーケンスでターミナルにカラー表示するチートシート

image.png

こんなふうにテキストに色や下線を付けてターミナルで表示させるには、

Rubyなら \e[...m

puts("ABC\e[4;31;44mDEF\e[0mGHI")
puts("ABC\e[38;5;45mDEF\e[0mGHI")

Perlなら \e[...m

print "ABC\e[4;31;44mDEF\e[0mGHI\n";
print "ABC\e[38;5;45mDEF\e[0mGHI\n";

PHPなら \e[...m

print("ABC\e[4;31;44mDEF\e[0mGHI\n");
print("ABC\e[38;5;45mDEF\e[0mGHI\n");

Bashなら \e[...m

echo -e "ABC\e[4;31;44mDEF\e[0mGHI"
echo -e "ABC\e[38;5;45mDEF\e[0mGHI"

Pythonなら \x1b[...m

print("ABC\x1b[4;31;44mDEF\x1b[0mGHI");
print("ABC\x1b[38;5;45mDEF\x1b[0mGHI")

Node.jsなら \x1b[...m

console.log("ABC\x1b[4;31;44mDEF\x1b[0mGHI");
console.log("ABC\x1b[38;5;45mDEF\x1b[0mGHI");

Scalaなら \u001b[...m

println("ABC\u001b[4;31;44mDEF\u001b[0mGHI");
println("ABC\u001b[38;5;45mDEF\u001b[0mGHI");

Javaなら \033[...m または \u001b[...m

System.out.println("ABC\033[4;31;44mDEF\033[0mGHI");
System.out.println("ABC\033[38;5;45mDEF\033[0mGHI");

[m の間の数字が色や装飾を表すパラメータです。パラメータが複数の場合はセミコロンで区切ります。上記サンプルであれば4は下線、31は赤文字、44は青背景、0はリセットです。

\e[4;31;44m\e[4m\e[31m\e[44m としても同じです。

38と48は、セミコロン区切りで続くパラメータが必須で、詳細な色を指定できます。

装飾

パラメータ 効果 Windows Terminal Windows Tera Term Mac Terminal
1 太字(Bold)
2 細字(Faint, light font weight) ×
3 斜体(Italic)
4 下線(Underline)
5 点滅(Blink)
6 速い点滅(Rapid blink) × × ×
7 背景色と文字色を入れ替える
8 文字を隠す(Conceal) ×
9 取り消し線(Strike) × ×

○:対応していそう
△:エスケープシーケンスに反応して変化はしているが、思ってたのとちがう
×:非対応

8の文字を隠す効果は、対応している端末では、文字は見えませんが、マウスで選択すると文字が現れ、テキストをコピーできます。tmuxでもテキストをコピーできます。

Windows Terminal

image.png

WindowsのTera Term

image.png

MacのTerminal

image.png

標準の8色 明るい8色 256色 RGB
文字色 30~37 90~97 38;5;n 38;2;r;g;b
背景色 40~47 100~107 48;5;n 48;2;r;g;b

8色は下1桁で次の色を表します。

0 1 2 3 4 5 6 7

256色のnは0~255の範囲で指定します。nを0~7にすると標準の8色と同じ色で、8~15にすると明るい8色と同じ色になります。

RGBのr, g, bの箇所も0~255の範囲で指定します。

WindowsのTera Termでは8色までしか対応していないようです。Windows TerminalとMacのTerminalは、256色もRGB指定も機能するようです。

Windows Terminal

8色指定と256色指定
image.png

RGB指定
image.png

WindowsのTera Term

8色指定と256色指定
image.png

RGB指定
image.png

MacのTerminal

8色指定と256色指定
image.png

RGB指定
image.png

リセット

パラメータ 効果
0 リセット

色や装飾の指定をすべて解除します。

この0は省略できます。Rubyなら \e[m でリセットになります。

サンプルコード

この記事の装飾と色のサンプル出力に使ったコードは以下です。Rubyです。

puts()

def showcase1(str, text)
  "\e[#{str}m\\e[#{str}m#{text}\e[0m "
end

def showcase2(str)
  "\e[#{str}m\\e[#{str}m" + (" " * (8 - str.length)) + "\e[0m "
end

def showcase3(str, text)
  "\e[#{str}m#{text}\e[0m"
end

line = " "
(1..9).each do |i|
  line += showcase1("#{i}", "いろは")
end
puts(line)
puts()

line1 = " "
line2 = " "
(0..7).each do |i|
  line1 += showcase2("#{30+i}")
  line2 += showcase2("#{40+i}")
end
puts(line1)
puts(line2)
line1 = " "
line2 = " "
(0..7).each do |i|
  line1 += showcase2("#{90+i}")
  line2 += showcase2("#{100+i}")
end
puts(line1)
puts(line2)
puts()

(0..256).each do |i|
  if i % 8 == 0
    line1 = " "
    line2 = " "
  end
  line1 += showcase2("38;5;#{i}")
  line2 += showcase2("48;5;#{i}")
  if i % 8 == 7
    puts(line1)
    puts(line2)
  end
end
puts()

(0..256).each do |i|
  if i % 64 == 0
    line1 = " "
  end
  line1 += showcase3("48;2;0;255;#{i}", " ")
  if i % 64 == 63
    puts(line1)
  end
end
puts()

※エスケープシーケンスは、ほかにもカーソル移動や画面のクリアなどもできますが、本稿では色やテキストの装飾にとどめておきます。

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

プログラミング初学者がLINE-botを作ってみた

概要

私事ですが、全く天気予報を見ないです。
そのため朝に雨が降っていなければ傘を持っていかず、毎回コンビニで傘を買ったり、雨に打たれながら帰宅することが多々あります。
そこで!傘代の節約、これ以上家に傘を増やさないためにも、自分用に朝7時に傘が必要か通知してくれて、こちらが「明日・明後日・今日・などなど」と入力すればその日に雨が降るかを返してくれるLINE Botを実装してみました。

IMB_wjbsdm.gif

手順

・事前にRailsの環境を整える
新規Railsアプリケーションの作成「rails new」コマンドを実行

$ rails new アプリケーション名

作成されたディレクトリ(フォルダ)に移動

$ cd 作成されたディレクトリ名

Gemfileの一番下に以下のコードを追加

gem ' dotenv-rails ' 
gem 'line-bot-api' #dotenv-railsは環境変数を扱うために使用するgem

以下のコマンドでGemをインストール

$bundle install

Userモデルを作成

$ rails generate model モデル名 カラム名:データ型

上記のコマンドで新しくマイグレーションファイルも作成される。このマイグレーションファイルを以下のように編集

class CreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      t.string :line_id, null: false # この行を修正

      t.timestamps
    end
  end
end

以下のコマンドでデータベース・テーブルを作成

$ bundle exec rails db:create
$ bundle exec rails db:migrate

新規作成されたファイルを編集

lib/tasks/scheduler.rake
task :update_feed => :environment do
  require 'line/bot'  # gem 'line-bot-api'
  require 'open-uri'
  require 'kconv'
  require 'rexml/document'
#line-bot側の設定
  client ||= Line::Bot::Client.new { |config|
    config.channel_secret = ENV["LINE_CHANNEL_SECRET"]
    config.channel_token = ENV["LINE_CHANNEL_TOKEN"]
  }
  # 使用したいxmlデータURLを入力
  # xmlデータをパース
  # パスの共通部分を変数化
  # 6時〜12時の降水確率
  # メッセージを発信する降水確率の下限値の設定
  min_per = 20
  if per06to12.to_i >= min_per || per12to18.to_i >= min_per || per18to24.to_i >= min_per
    word1 =
      ["テキスト入力",].sample
    word2 =
      ["テキスト入力"].sample
    # 降水確率によってメッセージを変更する閾値の設定。
#ifの条件式の中で降水確率によって送信されるメッセージを変更
    mid_per = 50
    if per06to12.to_i >= mid_per || per12to18.to_i >= mid_per || per18to24.to_i >= mid_per
      word3 = "テキスト入力"
    else
      word3 = "テキスト入力!"
    end
    # 発信するメッセージの設定
    push =
      "#{word1}\n#{word3}\n降水確率\n   6〜12時 #{per06to12}\n 12〜18時  #{per12to18}\n 18〜24時 #{per18to24}\n#{word2}"
    # メッセージの発信先idを配列で渡す必要があるため、userテーブルよりpluck関数を使ってidを配列で取得。
#multicastsメソッドは、今回利用しているgem「line-bot-api」で定義されており、このメソッドを呼び出している
    user_ids = User.all.pluck(:line_id)
    message = {
      type: 'text',
      text: push
    }
    response = client.multicast(user_ids, message)
  end
  "OK"
end

以下のコマンドでコントローラの作成

$ bundle exec rails g controller linebot

作成されたコントローラーの編集

app/controller/linebot_controller.rb
class LinebotController < ApplicationController
  require 'line/bot'  # gem 'line-bot-api'
  require 'open-uri'
  require 'kconv'
  require 'rexml/document'

  def callback
    body = request.body.read
    signature = request.env['HTTP_X_LINE_SIGNATURE']
    unless client.validate_signature(body, signature)
      return head :bad_request
    end
    events = client.parse_events_from(body)
    events.each { |event|
      case event
        # メッセージが送信された場合の対応を入力

        # ユーザーからテキスト形式のメッセージが送られて来た場合の対応を入力

          when /.*(明日|あした).*/
            # info[2]:明日の天気
            per06to12 = doc.elements[xpath + 'info[2]/rainfallchance/period[2]'].text
            per12to18 = doc.elements[xpath + 'info[2]/rainfallchance/period[3]'].text
            per18to24 = doc.elements[xpath + 'info[2]/rainfallchance/period[4]'].text
            if per06to12.to_i >= min_per || per12to18.to_i >= min_per || per18to24.to_i >= min_per
              push =
                "テキスト\nテキスト\n降水確率\n   6〜12時 #{per06to12}\n 12〜18時  #{per12to18}\n 18〜24時 #{per18to24}\nテキスト"
            else
              push =
                "テキスト"            end
          # テキスト以外(画像等)のメッセージが送られた場合
        else
          push = "テキスト"
        end
        message = {
          type: 'text',
          text: push
        }
        client.reply_message(event['replyToken'], message)
        # LINEお友達追された場合
      when Line::Bot::Event::Follow
        # 登録したユーザーのidをユーザーテーブルに格納
        line_id = event['source']['userId']
        User.create(line_id: line_id)
        # LINEお友達解除された場合
      when Line::Bot::Event::Unfollow
        # お友達解除したユーザーのデータをユーザーテーブルから削除
        line_id = event['source']['userId']
        User.find_by(line_id: line_id).destroy
      end
    }
    head :ok
  end

  private

  def client
    @client ||= Line::Bot::Client.new { |config|
      config.channel_secret = ENV["LINE_CHANNEL_SECRET"]
      config.channel_token = ENV["LINE_CHANNEL_TOKEN"]
    }
  end
end



記号の意味
・「.」:何か1文字(「あ」や「a」など1文字なら何でもヒットします)
・「*」:直前の文字が0回以上繰り返す場合にマッチ


ルーティングの設定
メッセージが来た時、友達追加された時、解除がされた時に、linebotコントローラのcallbackアクションが呼ばれるようにしています。

config/routes.rb
Rails.application.routes.draw do
  post '/callback' => 'linebot#callback'
end

この後、お好きなレンタルサーバー、共有サーバーでデプロイ
(私はHerokuでデプロイしました)


こちらの
LINE Botアカウント作成・設定
をして完成!!

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

オプション引数の使い方と機能について

はじめに

オプション引数の使い方と機能について説明します。

オプション引数とは

使い方:(** 引数)   ←引数の前に" ** "をつけます。

オプション引数は、引数としてハッシュしか受け取れないようにする働きがあります。
使用する目的としては、間違ってハッシュ以外の引数を渡したときに、エラーが出るようにするためです。
(データの整合性を保つためにオプション引数で記述する)

処理の流れ

newメソッドよってinitializeメソッドが呼び出され、ハッシュの引数(name: "山田太郎")が(**params)に渡されます。
そして @name = params[:name]によって、@nameに"山田太郎"が代入されます。
同時にこのとき、Personクラスがインスタンス化されます。

次にperson.introductionによってintroductionメソッドが呼び出され
puts "私の名前は#{name}です。"の#{name}に"山田太郎"が代入されてputsが出力されます。

class Person
  attr_reader :name

 # (**params)と書いてオプション引数を使用し、ハッシュしか受け取れないようにしている
  def initialize(**params)
    @name = params[:name]
  end

  def introduction
     puts "私の名前は#{name}です。"
  end
end
 
 #ハッシュ(name: "山田太郎" )をinitializeメソッドに渡している
 person = Person.new(name: "山田太郎" )

 person.introduction


#出力結果
私の名前は山田太郎です。

ちなみに

オプション引数を使用した状態で引数をハッシュ以外にした場合、下記エラーが発生します。

#変更前
person = Person.new(name: "山田太郎" )

#変更後
person = Person.new("山田太郎" )

#変更後の出力結果
`initialize': wrong number of arguments (given 1, expected 0) (ArgumentError)

最後に

今回は、オプション引数について説明させていただきました。
データの整合性を保つことを意識して、今後使用していきたいと思います。
ありがとうございまいした。

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

postとgetの違いについて

はじめに

sinatraを使ってformから得た値を出力する、という処理について、getだけを使った処理とpostを使った処理を両方行ったので、両者の違いを記事にまとめます。

getとpostって何?

getとpostはどちらも代表的なHTTPリクエストのメソッドです。
HTTPとは、Webサイトを表示する際に使用するプロトコルで、クライアントとサーバー間でのWebページの送受信で使用されます。
クライアントがgetやpostなどのリクエストをサーバーへ投げることで、Webサーバーがクライアントにレスポンスを返す、という仕組みになっており、私たちがブラウザで単語を検索する時にも、無意識のうちにgetやpostを使ってリクエストを投げています。

行った処理

・formに名前を入力して送信
・~~/hello というurlに「こんにちは、 <名前>さん。」と表示

getについて

getでは、urlで指定したファイルを要求するメソッドです。この際、urlの末尾に情報を付与するのが大きな特徴です。
以下にコードを示します。
form.erbの中で、form<method = "get">として、getメソッドを指定しています。

main.rb
require 'sinatra'
require 'sinatra/reloader'

get '/form' do
  erb :form
end

get "/hello" do  
  "こんにちは、#{params[:name]}さん。"

end
form.erb
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8"/>
  <title>名前</title>
</head>

<body>
  <form action="/hello" method="get"> 
    <input type="text" name="name">
    <input type="submit">
  </form>

</body>
</html>

以下のように、名前を入力し...
スクリーンショット 2021-03-14 12.55.12.png
送信を押すと、挨拶してもらえる!
スクリーンショット 2021-03-14 12.59.46.png

注目して欲しいのは、2枚目の画像のurlです。helloの後ろに?name=hogehogeと書かれているのがわかるかと思います。
このように、getメソッドでは?<キー>=<バリュー>という形でリクエストを指定し、送信します。

postについて

getはurlの末尾に情報を付与しサーバーへ送っていました。
一方、postはメッセージボディに入力した情報を記述してリクエストします。
以下にコードを示します。
form.erbの中で、form<method = "post">として、postメソッドを指定しています。

main.rb
require 'sinatra'
require 'sinatra/reloader'

get '/form' do
  erb :form
end

post '/hello' do
  "こんにちは、#{@params[:name]}"さん。
end
form.rb
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8"/>
  <title>名前</title>
</head>

<body>
  <form action="/hello" method="post">
    <input type="text" name="name">
    <input type="submit">
  </form>


</body>
</html>
hello.erb
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8"/>
  <title>hello</title>
</head>

<body> 
    こんにちは、<%=@name%>さん。
</body>
</html>

コード自体はgetを使った場合とほぼ同じです。
こちらについても、実行結果を画像で示しておきます。
スクリーンショット 2021-03-14 13.41.22.png
送信ボタンをクリックし、
スクリーンショット 2021-03-14 13.41.28.png
挨拶してもらう。

先ほどと違い、urlの末尾に情報が付与されていません。
これは、メッセージボディの箇所に入力した内容が記入されているからなのです。

developerツールで確認

実際のメッセージボディの内容は、chromeのdeveloperツールから確認することができます。
chrome上でoption + command + Iコマンドからdeveloperツールを開き、networkタブをクリックしてください。
すると以下のような画面が出てくると思います。スクリーンショット 2021-03-14 16.12.28.png
ファイルをクリックし、Form Dataと書かれているところを開きます。
スクリーンショット 2021-03-14 16.16.09.png

フォームに入力したデータがメッセージボディ(正確にはリクエストボディ)に格納されていることが実際に確認できました。やったね!
ちなみに、getを使った場合、下の画像ようになります。
スクリーンショット 2021-03-14 16.21.51.png
メッセージボディではなく、クエリストリング(つまり、urlの後ろに付与している情報)として読み込まれていることが分かります。

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

VRoid Studioデータのヘアマテリアルを削除する

この記事の趣旨

下記のソフトウェアを作った本人ではあるのですが、この辺りのソフトウェアで既に実装済みであって、これとは別の実装を作りたい人とか、pixivの中の人とか、原理がわからないとツールを使うのが不安の人とか、いろんな立場の人に読んでもらいたいなと思っています。

VRoid運営がなんと言おうと.vroid内の不要マテリアルは削除していい

複製ボタンで一発で増殖したマテリアルは削除ボタン一発で削除できるのがUI/UXとして好ましいし削除機能を実装しないのは(某天才プログラマーさんの言葉を借りるなら)「けしからん」と思うわけです。
間違えてマテリアル増やしちゃうことだってあるわけじゃないですか。そういううっかりって取り消せること国は権利として認められているのですね。民法95条にも書いてある。

任天堂さんごめんなさい

削除処理の実装の実際

それでは処理を順を追って説明します、

.vroidファイルの構造

VRoidファイルは、ZIPで固めた複数のファイルで構成されますが、説明に必要な部分に絞って表示しております。

Network Diagram.png

Hairishesから参照されている/いないHair-Materialをグループ分けする

髪が参照しているマテリアルというのはhairs_defs.jsonのHairishesの各ノードのParam > _MaterialValueGUIDの値がこれに該当します。
なお、Hairishesは髪グループと髪との2階層構造になっていますが、両方の階層を見る必要があります。

ポイント:material_defs.jsonは髪以外のマテリアルも含む

新しめのバージョンで新規作成したモデルは、便宜上、3つの分類が存在することになります。今回は2のみが削除対象となります。

  1. Hairから参照されているヘアマテリアル
  2. Hairから参照されていないヘアマテリアル
  3. 髪以外のマテリアル

髪のマテリアルであることの識別方法は3つ

また髪かそうでないかを見分けるには以下のいずれかのKey-Value値を見れば良いかと思います。

_PrototypeId => "Flora/F00_000_Hair_00_HAIR"
_SphereAddTextureId => "/Matcaps/Matcap_RimHair"
_Tags => ["HairEditor"] 

私は_SphereAddTextureIdを使っています。
というのも"Flora/F00_000_Hair_00_HAIR"の部分は条件によって異なる場合があるからです。

削除するテクスチャの選定

削除対象のマテリアルノードの _MainTextureId キーに格納されているIDが削除対象のテクスチャとなります。
一応用心のため、削除しないマテリアルから参照されているテクスチャIDが削除対象のテクスチャIDに含まれていないことを確認し、もし重なる部分があれば削除対象から除外しましょう。

SQLiteのエントリー削除

テクスチャの管理テーブルはcanvasとlayerの2つのテーブルで十分じゃないかという気もしないでもないのですが、4つのテーブルから構成されています。

ER図

正攻法としてはcanvas_idからこれに属するlayer_idを求め、layer_order, layer_info, raster_layer_contentの各テーブルのレコードを削除し、canvas_infoのレコードを削除する流れとなるかと思います。
髪のテクスチャIDはcanvas_infoのIDではなくnameの方に紐づいているのでその点だけ注意。

各レイヤーのテクスチャ本体はraster_layer_contentのBLOBに上下反転した状態で入っています。

レンダリング済みテクスチャの削除

rendered_texturesの下にPNGファイルが複数あるかと思いますが、削除対象は先のテクスチャIDに.pngを付けたものが対応しています。

Rubyの実装例

そんなわけで以前実装したものを紹介しておきます。

実はヘアプリセットの出力でもマテリアルは削減している。

実はVRoid Studioのヘアプリセットを出力する処理においても、Hairishesから参照されていないヘアマテリアルのノードとこれに紐づくテクスチャのみしか出力しない処理がなされています。
(出力内容を見る限りでは)等価の処理を再現したのが下記のスクリプトです。

こちらも meta.json + hair_defs.json + material_defs.jsonと関連するテクスチャ(PNGファイル)から生成するので、原理としてはかなり近いです。

これは、VRoid Studioにおいてもヘアプリセットのエクスポートの処理を転用すればヘアマテリアルの削除が容易に実装できることを意味しています。

まとめ

VRoidのサポートページを見る限り、マテリアルを削除できないこと、これを削除できるよう要望があることは十分認識していて、それでもなお実装を後回しにしているのは何かしらの事情がありそうだということ。
困っているユーザーがいるのに「何もしない」という答えは最善ではないと思うので、私はマテリアルを削除する手段を作りました。

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

【Ruby on Rails6】gem "devise" のviewsのpasswordsだけが編集できない。

【Rails6】gem "devise" のviewsのpasswordsだけが編集できない。

↑記事で解決できました。
しかし、私の場合passwordsのviewsだけhtml.erb編集が反映されなかったので事象を記録します。

環境 Ruby 2.6.6p146/Rails 6.0.3

通常通りdeviseを導入後、viewを編集。なぜかpasswordsだけ編集できずググって解決。

結論

config/initializers/devise.rb

devise.rb
  # ==> Scopes configuration
  # Turn scoped views on. Before rendering "sessions/new", it will first check for
  # "users/sessions/new". It's turned off by default because it's slower if you
  # are using only default views.
  # config.scoped_views = false

のコメントアウト箇所…

devise.rb
 # ==> Scopes configuration
  # Turn scoped views on. Before rendering "sessions/new", it will first check for
  # "users/sessions/new". It's turned off by default because it's slower if you
  # are using only default views.
  config.scoped_views = true 

config.scoped_views = true にしてください。
そのままのコメントアウトだとconfig.scoped_views = falseなので注意です。

その後ターミナルで

rails s でサーバー再起動も忘れずに。
※僕は再起動しないと反映されませんでした。

これにて解決。

ここからが??な事象です。もし、謎を知っている方がいたら教えていただきたいです。

反映されない該当箇所をブラウザリロードをすると、ターミナル表示が

 Started GET "/users/password/new" for ::1 at 2021-03-14 11:49:26 +0900
Processing by Devise::PasswordsController#new as HTML
  Rendering /Users/sxkx/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/devise-i18n-1.9.2/app/views/devise/passwords/new.html.erb within layouts/application
  Rendered /Users/sxkx/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/devise-i18n-1.9.2/app/views/devise/shared/_error_messages.html.erb (Duration: 0.1ms | Allocations: 15)
  Rendered /Users/sxkx/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/devise-i18n-1.9.2/app/views/devise/shared/_links.html.erb (Duration: 0.4ms | Allocations: 246)
  Rendered /Users/sxkx/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/devise-i18n-1.9.2/app/views/devise/passwords/new.html.erb within layouts/application (Duration: 2.5ms | Allocations: 863)
[Webpacker] Everything's up-to-date. Nothing to do
  Rendered layouts/_header.html.erb (Duration: 0.3ms | Allocations: 119)
  Rendered layouts/_sidemenu.html.erb (Duration: 0.2ms | Allocations: 135)
[Webpacker] Everything's up-to-date. Nothing to do
Completed 200 OK in 28ms (Views: 26.4ms | ActiveRecord: 0.0ms | Allocations: 11089)

Rendering /Users/sxkx/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/devise-i18n-1.9.2/app/views/devise/passwords/new.html.erb within layouts/application
??なんだか長い…。 devise-i18n…. 日本語化のために入れたgemが悪さをしている??

devise.rb
  config.scoped_views = true 

をすると

Started GET "/users/password/new" for ::1 at 2021-03-14 11:54:00 +0900
Processing by Devise::PasswordsController#new as HTML
  Rendering users/passwords/new.html.erb within layouts/application
  Rendered users/shared/_error_messages.html.erb (Duration: 0.1ms | Allocations: 15)
  Rendered users/shared/_links.html.erb (Duration: 0.9ms | Allocations: 225)
  Rendered users/passwords/new.html.erb within layouts/application (Duration: 4.7ms | Allocations: 1058)
[Webpacker] Everything's up-to-date. Nothing to do
  Rendered layouts/_header.html.erb (Duration: 0.5ms | Allocations: 119)
  Rendered layouts/_sidemenu.html.erb (Duration: 0.3ms | Allocations: 135)
[Webpacker] Everything's up-to-date. Nothing to do
Completed 200 OK in 41ms (Views: 37.7ms | ActiveRecord: 0.0ms | Allocations: 12092)

Rendering users/passwords/new.html.erb within layouts/application
うん。なんかちゃんと狙いのファイルを拾ってくれていそう。

スコープを指定したから、しっかりできた感じなんですね。
ただなぜpasswordsだけがそうなったのかは謎です。

以上。

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

複数タグ投稿機能の実装(formオブジェクト使用)

投稿に複数のタグを紐付ける

現在作成中のポートフォリオの中で複数のタグをつけることができる機能をつけたかったのでそれを実践した際のコードの記述についてまとめておこうと思います。またポートフォリオ完成した際には別記事で解説したいと思います。
それでは実際の実装を書いていきます。。

conntrollerの記述

  def new
    @room = RoomsTag.new
  end

  def create
    @room = RoomsTag.new(room_params)
    tag_list = params[:room][:name].split(",")
    if @room.valid?
      @room.save(tag_list)
      redirect_to root_path
    else
      render "new"
    end
  end

  def edit
    @form = RoomsTag.new(room: @room)
  end

  def update
    @form = RoomsTag.new(room_params, room: @room)
    tag_list = params[:room][:name].split(",")
    if @form.valid?
      @form.save(tag_list)
      redirect_to room_path(@room)
    else
      render :edit
    end
  end

  private
  def room_params
    params.require(:room).permit(:image, :title, :content, :place_id, :floor_id, :style_id, :name).merge(user_id: current_user.id)
  end

formオブジェクトの記述

class RoomsTag
  include ActiveModel::Model
  attr_accessor :image, :title, :content, :place_id, :floor_id, :style_id, :name, :user_id

# バリデーションをカットしています。

  delegate :persisted?, to: :room

  def initialize(attributes = nil, room: Room.new)
    @room = room
    attributes ||= default_attributes
    super(attributes)
  end

  def save(tag_list)

    ActiveRecord::Base.transaction do
      @room.update(image: image, title: title, content: content, place_id: place_id, floor_id: floor_id, style_id: style_id, user_id: user_id)
      @room.room_tag_relations.each do |tag|
        tag.delete
      end

      tag_list.each do |tag_name|
        tag = Tag.where(name: tag_name).first_or_initialize
        tag.save

        room_tag = RoomTagRelation.where(room_id: @room.id, tag_id: tag.id).first_or_initialize
        room_tag.update(room_id: @room.id, tag_id: tag.id)
      end
    end

  end

  def to_model
    room
  end

  private
  attr_reader :room

  def default_attributes
    {
      image: room.image,
      title: room.title,
      content: room.content,
      place_id: room.place_id,
      floor_id: room.floor_id,
      style_id: room.style_id,
      name: room.tags.pluck(:name).join(',')
    }
  end

end

まずデータベースに保存できるところまでを記述していきます。

controller内の記述について

tag_list = params[:room][:name].split(",")

上記の記述はsplitメソッドを用いて , で区切って、配列に変換しています。
その後formオブジェクトへ送ります。

formオブジェクト内の記述について

delegate :persisted?, to: :room

こちらの記述から新規作成か更新かを判別して、formのメソッドをPUTとPATCHで分けている。

def initialize(attributes = nil, room: Room.new)
    @room = room
    attributes ||= default_attributes
    super(attributes)
end

formオブジェクトパターンで編集機能を実装する際は、編集画面でそれぞれのデータを表示させておくには、今回私の実装で行くところのRoomsTagのインスタンスを生成して、それをformに持っていかなくてはいけません。上記のinitializeメソッドの定義によってnewアクションの時は中身は空で、editアクションの時は中身がある状態でformに持っていくことができます。
super(attributes)の記述がないとinitializeメソッドが機能しないようです。

続いてtag_listを引数にとっているsaveメソッド内について。

ActiveRecord::Base.transaction do
end

この間に囲まれた部分で何か処理の失敗が起きた時、do~end間の処理を全てなかったことにするものです。

@room.update(image: image, title: title, content: content, place_id: place_id, floor_id: floor_id, style_id: style_id, user_id: user_id)
@room.room_tag_relations.each do |tag|
  tag.delete
end

@room.updateにてデータの更新をします。
その下の記述で、このroomに紐付く全てのタグを一旦削除しています。

tag_list.each do |tags|
    tag = Tag.where(name: tags).first_or_initialize
    tag.save

    room_tag = RoomTagRelation.where(room_id: @room.id, tag_id: tag.id).first_or_initialize
    room_tag.update(room_id: @room.id, tag_id: tag.id)
end

配列で運ばれてきたtag_listをeachメソッドを使って、一つずつ取り出し、whereメソッドとfirst_or_initializeメソッドで今までに保存したことがないタグだけ保存しています。その後中間テーブルのものを更新しています。
ここまででsave(tag_list)内の記述は終了です。

def to_model
    room
end

createかupdateか適切なコントローラーへのパスをしてあげる記述みたいです、、
最後、privateメソッド内の記述でこれで読み取り専用のメソッドが定義されるようです。

以上で複数タグの投稿機能の実装を行いました。かなり手探りで色々な記事を参考にしながらのものだったので拙い表現となっているかと思います。ご了承ください。。

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

【クソサイト】世界一情報量が多いサイト【個人開発】

はじめに

情報量が多い画像、情報量が多い動画などはたくさんあるのに「情報量が多いサイト」というのは見つからなかったので作ってみました。

概要

為替
ニュース
名言
じゃんけん
投稿
情報量の多い画像
日付け
イカれた鳥
などの情報が載ってます。

あと隠しページがありますので暇な人は探してみてください。そこそこむずいです。

工夫した点

APIを大量に使う

大量と言っても3つですが
NewsAPI...ニュース
exchangeratesapi...為替
名言教えるよ...名言

を使いました。

色使い

原色、反対色、相性の悪い色
をあえて大量に使いまとまりをなくしました。

画像

あえてリサイズをせずサイズをバラバラにしてみました。

動き

スライドショーやイカれた鳥を使い動きを持たせさらに混乱するようにしました。

まとめ

深夜テンションで決めたネタを続投しないよう法が良い

ツイッターやってます
https://twitter.com/yamada1531

追記

皆さん、隠しページ見つけるの早いですね...。公開して3日くらいは絶対見つからないと思ってたのにもう1、2人...。
もうちょっと難しくしようかなーなんて思ってます。

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

[Rails]Fakerで日本語名を生成する方法

日本語名を生成する方法

config/application.rb
module "アプリ名"
  class Application < Rails::Application

    ~~

    config.i18n.default_locale = :ja

    ~~

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

[WIP]Rails6.1ハンズオン(4)~6.0の機能を触る

はじめに

※この記事は執筆途中です。5-1までで力尽きたので5-2以降はまた今度書きます...

Rails6.1ハンズオン(3)の続きです。

6.1ハンズオンと言いながら、実は6.0をまともに触っていない筆者。。。
ですので今回は6.0で追加された機能を触ってみます。
コードはGithubにあげています。章ごとにコミットしてますので、参考にしていただければ幸いです。

やること

個人的に気になった(面白そうor仕事で使いそう)機能をピックアップして、現在のRails_6.1_hands_onプロジェクトに追加してみます。

  • Action Text
  • Action Mailbox
  • ActiveRecord::Relation#pick

参考:Ruby on Rails 6の主要な新機能・機能追加・変更点 - Qiita

5-1. Action Text

5-1-1. Action Text, Active Storageの導入

参考:Action Text の概要 - Railsガイド

まずはActiveStorageが必要なので、

rails active_storage:install

をしたが、

rails aborted!
Don't know how to build task 'active_storage:install' (See the list of available tasks with `rails --tasks`)

と出た。ActiveStorageのREADMEをみると、

rails/rails

Run bin/rails active_storage:install to copy over active_storage migrations.
NOTE: If the task cannot be found, verify that require "active_storage/engine" is present in config/application.rb.

らしいので、config/application.rbをみると、

require "rails"
# Pick the frameworks you want:
require "active_model/railtie"
require "active_job/railtie"
require "active_record/railtie"
# require "active_storage/engine"
require "action_controller/railtie"
# require "action_mailer/railtie"
# require "action_mailbox/engine"
# require "action_text/engine"
require "action_view/railtie"
require "action_cable/engine"
require "sprockets/railtie"

となっていた。rails new で色々無効にしていたのでコメントアウトされていたらしい。

action_text/engineも無効化されているのでこちらも有効にしておく。

そしてやっと、

rails active_storage:install
rails action_text:install

migrationファイルができているので、忘れずにrails db:migrateする。

config/storage.ymlを作る(最初からあるイメージだが、今回は無かった)

local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

test:
  service: Disk
  root: <%= Rails.root.join("tmp/storage") %>

config/environments/development.rb(test.rbも任意で、このハンズオンでテスト書くのかな...)に追加:

config.active_storage.service = :local

5-1-2. ActionTextをモデルに適用する

_2021-03-13_16.52.16.png

Commentのcontentがtextになっていたのを、Action Textに置き換える。

rails g migration RemoveContentInComment
class RemoveContentInComment < ActiveRecord::Migration[6.1]
  def change
    remove_column :comments, :content, :text
  end
end

rails db:migrateして、

app/models/comment.rb

class Comment < ApplicationRecord
  belongs_to :community
  has_rich_text :content
end

has_rich_textでcontentカラムを作ったかのように振る舞ってくれるようになる。

app/views/comments/new.html.haml(一部)

= form.label :content, class: 'form-label'
= form.rich_text_area :content, class: 'form-control mb-4'

app/views/communities/show.html.haml

= comment.content

simple_formatを外した。

すると以下のようになる。

_2021-03-14_9.56.53.png

思ってたのと違う。cssが読み込まれていない。action_text:installしたときに新しく生成されたcssは、app/assets/stylesheets/actiontext.scss。内容がこちら

//
// Provides a drop-in pointer for the default Trix stylesheet that will format the toolbar and
// the trix-editor content (whether displayed or under editing). Feel free to incorporate this
// inclusion directly in any other asset bundle and remove this file.
//
//= require trix/dist/trix

// We need to override trix.css’s image gallery styles to accommodate the
// <action-text-attachment> element we wrap around attachments. Otherwise,
// images in galleries will be squished by the max-width: 33%; rule.
.trix-content {
  .attachment-gallery {
    > action-text-attachment,
    > .attachment {
      flex: 1 0 33%;
      padding: 0 0.5em;
      max-width: 33%;
    }
// (以下省略)

trix/dist/trixをrequireしていて、更に添付ファイルの表示に関するスタイルを上書きしているらしい。

app/views/layouts/application.html.hamlでは、

= stylesheet_link_tag 'application'を消して、= stylesheet_pack_tag 'application'に変えているので、上記ファイルは読み込まれない。

= stylesheet_link_tag 'application'を復活させれば動く(動作確認済み)。

だが、せっかくなので、webpackerに寄せる。

app/javascript/stylesheets/application.scss

@import "~@fortawesome/fontawesome-free/scss/fontawesome";

$primary: #9400d9;
@import "bootstrap";
@import "trix/dist/trix";
@import "actiontext.scss";

app/javascript/stylesheets/actiontext.scssではsprocket用のコードを外して記述。

// We need to override trix.css’s image gallery styles to accommodate the
// <action-text-attachment> element we wrap around attachments. Otherwise,
// images in galleries will be squished by the max-width: 33%; rule.
.trix-content {
  .attachment-gallery {
    > action-text-attachment,
    > .attachment {
      flex: 1 0 33%;
      padding: 0 0.5em;
      max-width: 33%;
    }

    &.attachment-gallery--2,
    &.attachment-gallery--4 {
      > action-text-attachment,
      > .attachment {
        flex-basis: 50%;
        max-width: 50%;
      }
    }
  }

  action-text-attachment {
    .attachment {
      padding: 0 !important;
      max-width: 100% !important;
    }
  }
}

すると、

_2021-03-14_10.25.12.png

できた。

_2021-03-14_10.27.29.png

こんな感じで入力できる。

それで投稿すると

_2021-03-14_10.27.47.png

画像が表示されない。

chromeのconsoleを見ると、

GET http://localhost:3000/rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBCZz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--dce7eada98f7015981b311d8495658640b895ad9/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJY0c1bkJqb0dSVlE2RkhKbGMybDZaVjkwYjE5c2FXMXBkRnNIYVFJQUJHa0NBQU09IiwiZXhwIjpudWxsLCJwdXIiOiJ2YXJpYXRpb24ifX0=--9a6414b9afa153c394bf23eb32d0e368370e8b60/Screenshot_20210304-220735.png 500 (Internal Server Error)

読み込めてない。500なので内部的なエラー。

したがってrailsのコンソールを見ると、

LoadError (Generating image variants require the image_processing gem. Please add `gem 'image_processing', '~> 1.2'` to your Gemfile.)

ちゃんと書いてあった。Gemfileにgem 'image_processing', '~> 1.2'を追加。bundle install。

_2021-03-14_10.37.36.png

表示されました。

5-2. Action Mailbox

(執筆中です)

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

マイグレーションファイルを変更する方法(Rails)

マイグレーションファイル変更する方法

参考にしたサイト Railsチュートリアル(5.1)

$ rails db:rollback
$ rails db:migrate VERSION=0
$ rails db:rollback STEP=○
  • 1つ前の状態に戻します。
  • 最初の状態に戻したいときはVERSION=0つける
  • 最後のマイグレーションファイルから数えて戻したい数だけ戻せる
$ rails db:rollback
$ rails db:migrate:status

Status   Migration ID    Migration Name
--------------------------------------------------
   up     20200830060821  Devise create users
  down    20200830062141  Create books

$ rails db:migrate:statusを使うことで変更できるファイルの一覧を確認できます。
downが変更可能でupが変更できないファイルです。
$ rails db:rollbackを使うとdownに変更されていると思います。

$ rails g migration add_index_to_users_email

usersテーブルの中にemailカラムをを追加するコマンド

おまけ

$ rails db:migrate 
$ bundle exec rake db:migrate 
  • Rails 5以降のコマンド
  • Rails 4以前のコマンド
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

HerokuでMySQLサーバーを使う方法

Heroku CLIをインストールしてから、ClearDBの導入から、Heroku apps:infoまでの手順を紹介します!(学習記録)

Heorkuのインストールからアプリ作成:shinto_shrine:

# heroku cliのインストール
% brew tap heroku/brew && brew install heroku 

# herokuのversionを確認
 % heroku --version 

# herokuにログインする
% heroku login --interactive

#パスワードとメールアドレスを入力してログイン

# heroku createで本番環境用のアプリを作成
cd ~/(アプリまでの相対パス)
heroku create (アプリの名前)

# アプリが正常に作られていることを確認
% git config --list | grep heroku

HerokuにMySQLを導入

# ClearDBを導入
% heroku addons:add cleardb

# Railsに対応させる
heroku_cleardb=`heroku config:get CLEARDB_DATABASE_URL`

# データベースのURLの再設定
 heroku config:set DATABASE_URL=mysql2${heroku_cleardb:5}

Heroku上で非公開の値を管理

# masterkeyを確認
% cd ~/(アプリまでの相対パス)
% EDITOR="code --wait" bin/rails credentials:edit

# heroku上に環境変数を設定
% heroku config:set RAILS_MASTER_KEY=`cat config/master.key`

# 正しく反映されているか確認
% heroku config

Herokuにpush

# herokuに自分のアプリを追加
% git push heroku master

# Rubyのバージョンによっては必要なコマンド
heroku stack:set heroku-18 -a (アプリの名前)

# migrateを実行
heroku run rails db:migrate

# heroku apps:infoで確認
heroku apps:info

以上がHerokuでMySQLサーバーを使う流れになります。参考までにどうぞ!!

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

HerokuでMySQLサーバーを使う方法(Rails)

Heroku CLIをインストールしてから、ClearDBの導入、Heroku apps:infoまでの手順を紹介します!(学習記録)

Heorkuのインストールからアプリ作成:shinto_shrine:

# heroku cliのインストール
% brew tap heroku/brew && brew install heroku 

# herokuのversionを確認
 % heroku --version 

# herokuにログインする
% heroku login --interactive

#パスワードとメールアドレスを入力してログイン

# heroku createで本番環境用のアプリを作成
cd ~/(アプリまでの相対パス)
heroku create (アプリの名前)

# アプリが正常に作られていることを確認
% git config --list | grep heroku

HerokuにMySQLを導入

# ClearDBを導入
% heroku addons:add cleardb

# Railsに対応させる
heroku_cleardb=`heroku config:get CLEARDB_DATABASE_URL`

# データベースのURLの再設定
 heroku config:set DATABASE_URL=mysql2${heroku_cleardb:5}

Heroku上で非公開の値を管理

# masterkeyを確認
% cd ~/(アプリまでの相対パス)
% EDITOR="code --wait" bin/rails credentials:edit

# heroku上に環境変数を設定
% heroku config:set RAILS_MASTER_KEY=`cat config/master.key`

# 正しく反映されているか確認
% heroku config

Herokuにpush

# herokuに自分のアプリを追加
% git push heroku master

# Rubyのバージョンによっては必要なコマンド
heroku stack:set heroku-18 -a (アプリの名前)

# migrateを実行
heroku run rails db:migrate

# heroku apps:infoで確認
heroku apps:info

#最後にHerokuのMySQLサーバーをリセットするコマンド
% heroku run DISABLE_DATABASE_ENVIRONMENT_CHECK=1 rails db:drop db:create db:migrate

以上がHerokuでMySQLサーバーを使う流れになります。参考までにどうぞ!!

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

attr_readerメソッド


attr_readerメソッド

インスタンス変数を呼び出すメソッドを定義するメソッド

class #クラス名
  attr_reader :name

  def initialize(name)
    @name = name
  end
end

attr_readerを使用しない場合

class #クラス名
  def initialize(name)
    @name = name
  end
#この部分の記述をattr_reader :nameに省略できる
  def name
    @name
  end

記述を省略できる

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

【Ruby】クラス、メソッドの定義とインスタンス変数について

はじめに

この記事は、Ruby/Railsの学習期間約1ヶ月の著者が書いたものです。
初投稿となりますので、誤った解釈があるかもしれません。その場合は、コメント等頂けますと幸いですm(_ _)m

今回は、Ruby学習中によく分からなくなりがちな、クラス、メソッドの定義、インスタンス変数について説明します。

独自のクラスを定義する

まず、Rubyではクラスを定義します。
クラスとは、設計図のようなもので、classから始まり、endで終わります。

クラス名は、ClassNameというように、単語の先頭を大文字で定義します。
ラクダのコブのようであることから、CamelCaseと表現します。

class.rb
#クラスの定義
class ClassName
end

クラスにメソッドを定義する

クラスの設計図の中にメソッドを定義することで、そのクラス独自が持っているメソッドとして認識されます。

メソッドとは、何らかの処理を示しており、処理内容をdefendで囲みます。
メソッド名は、method_nameのように2単語目以降は、アンダーバーで示しますので注意しましょう。
(このことを蛇に例えて、snake_caseと呼びます。)

method.rb
#メソッドの定義
class ClassName
  def method_name
  # メソッドの処理を書く
  end
end

#クラスのインスタンスを作成し、メソッドで呼び出す
instance = ClassName.new
instance.method_name

Rubyのオブジェクトは何らかのメソッドを持っていて、そのメソッドを定義することで、処理を返してくれるようになります。

ローカル変数とインスタンス変数について

次に、ローカル変数とインスタンス変数の違いを見てみましょう。

ローカル変数

ローカル変数は、使い回しできる範囲が他の変数よりも限られています。
例えば、メソッド内で定義した場合は、そのメソッド内でしか使えず、同じクラス内であっても使い回しができません。

試しに、以下のローカル変数messageを、say_morningのメソッドの処理として定義し、
say_good_morningのメソッド内で使い回してみましょう。

local_variable.rb
class Greeting
  def say_morning
    message = "morning!"
    puts message
  end

  def say_good_morning
    puts "Good #{message}"
  end
end

instance = Greeting.new
instance.say_morning
instance.say_good_morning

以下のようなエラーが出てしまいました。

`say_good_morning': undefined local variable or method `message' for #<Greeting:0x00007fe5b1984038> (NameError)

つまり、say_good_morningのメソッドの中で、messageというローカル変数やメソッドは定義されてませんよ、と言っているわけです。
このように、ローカル変数は、あるメソッド内で定義した変数は、他のメソッド内で使い回すことができません。

インスタンス変数

一方、インスタンス変数とは、同じオブジェクト内(同クラス内)で使い回せる変数のことをいいます。
変数の先頭に@をつけて定義します。

先程のローカル変数messageを、インスタンス変数@messageに置き換えて見てみましょう。

instance_variable.rb
class Greeting
  def say_morning
    @message = "morning!"
    puts @message
  end

  def say_good_morning
    puts "Good #{@message}"
  end
end

instance = Greeting.new
instance.say_morning
instance.say_good_morning

結果は以下の通り。

morning!
Good morning!

欲しい結果が出ましたね!
つまり、インスタンス変数とは、同じクラス内であれば、異なるメソッド内であっても何度でも使い回せる変数である、ということが証明されました。

おわりに

今回はRubyの基本であるクラス、メソッドの定義とローカル変数とインスタンス変数の違いについて、説明しました。
今後も、Ruby/Railsを学習する上で、皆様に有益な情報を発信できればと思いますので、良ければLGTMお願いします。

参考記事

ローカル変数・インスタンス変数・クラス変数の違い(Ruby)

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

【Rails】Cloud9で送信メールのプレビュー時の自分のホスト名と指定のURLの指定方法【Railsチュートリアル11章】

自分がつまづいた時の状況

Railsチュートリアル第11章アカウントの有効化でHTMLメールとテキストメールのプレビューをする際に、アプリケーションのdevelopment環境の設定ファイルの設定と、Railsサーバー起動時のURLが分からず、送信メールのプレビューが表示できませんでした。

・クラウドIDE:AWS Cloud9
・Rails:v6.0.3
・OS:Win

config/environments/development.rb
Rails.application.configure do
  .
  .
  .
  config.action_mailer.raise_delivery_errors = false

  host = 'example.com' # ここの指定方法が分からなかった(自分のホスト名を指定する)
  config.action_mailer.default_url_options = { host: host, protocol: 'https' }
  .
  .
  .
end

自分のホスト名の調べ方

まず、自分のホスト名を調べる方法ですが、
Cloud9のPreview PreviewRunning Applicationを押下し、アドレスバーがついているpreviewタブを表示させます。
その後、そのアドレスバーに表示されているlocalhost以下の文字列が自分のアドレスになります。

その自分のホスト名の中でもhttpsから.vsfの間の文字列を先ほどのdevelop環境の設定ファイルに反映します。

image.png

config/environments/development.rb
config.action_mailer.raise_delivery_errors = false

  host = 'hoge.vfs.cloud9.us-east-2.amazonaws.com'     # クラウドIDE
  config.action_mailer.default_url_options = { host: host, protocol: 'https' }

設定ファイルを変更したら、Railsサーバーは再起動してください。

送信メールのプレビューのURL

URLですが、下記でプレビューが表示されました。
ホストの後ろに「/rails/mailers/user_mailer」とつけるだけでした。

https://hogehoge.vfs.cloud9.ap-northeast-1.amazonaws.com/rails/mailers/user_mailer

image.png

参考

Railsチュートリアル第11章アカウントの有効化
rails tutorial にて 送信メールのプレビューができない
rails チュートリアルの11章の2・2にてクラウドIDEのホスト名がわからない

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