20190503のRubyに関する記事は24件です。

Day002 webエンジニアへの道 - Ruby on Railsの基礎を学ぶ -

こんにちは。
webエンジニアを目指すtomoです。

エンジニアの勉強をしている日々の学びをQiitaに記録しています。

私自身の頭の整理と今後迷った時のリファレンスも兼ねて書き連ねていきますが、同じようにエンジニア転職を考えている方の参考にもなればと考えています。
また、「ここ間違ってる!こっちが正しい!」といったご指摘もあれば頂けると嬉しいです?


前回は仮想マシンの環境設定を行いました。

しかし、実際にRuby on railsを使い始めたところ必要な関連ソフトが足りずエラーが連発しました。

必要な関連ソフトは何かなどちゃんと把握した上で進めたいのですが、それでは時間がいくらあっても足りなさそうなので、関連ソフトの理解は後にしてまずはdot-installのレッスン通りCentOS6を用いた環境で学習を進めることにします。

また、ひとつひとつのコードを全て書き写しているとキリがないので、学習を進める中で調べたことやエラー対応したことに絞って記述していきます。

railsサーバの起動

commandline
rails server -b [ipアドレス] -d

-b オプションでバインドするipアドレスを指定。
-d オプションでデーモンとしてサーバを起動。
デーモンとはメモリ上に常駐し、いつでも処理できるよう待機するプログラムのこと。

[参考]
railsコマンド(rails)
デーモン (daemon)

rails(5.1.4)以降ではリンク先が表示されないため、ブラウザに直接http://[ipアドレス]:3000と入力して動作確認する。

scaffoldで作成したファイルを削除する

commandline
rails destroy scaffold [name]

[name]は削除したいscaffoldの名前。
rails g scaffold [name]で要素を間違えて指定した時に有用

[参考]
scaffoldで作成したファイルを全削除

Modelの作成

rails g model Post title:string body:text

Post → Modelは個々のデータ構造を定義するので単数形で指定する。
string → 1行テキスト
text → 複数行のテキスト

追加した変更が反映されない時の対応

Transmit5を利用して仮想マシンとのファイル転送を行っていたが、エディタで保存したはずの変更がブラウザに反映されなかった。

原因:Transmitとの接続が途中で切れていたため。(Transmit上で「サーバから切断」を行うと、再接続しても既に開いているエディタのファイルとのリンクが切れてしまう)

tips:ブラウザまたはコンソール上で見える簡単な変更(htmlの文章やplaceholderなど)を加えてみると、どこに不具合があるか特定しやすい。目で見て確認しづらい処理の不具合をチェックする場合は処理の内容を簡素化(文字列の出力など)すると特定しやすい。

★メモ

*後日調べること & 気づいたこと
  • kill -9 [プロセス番号] → killコマンドで-9以外の場合どうなる?
  • Convention over Configuration → どのファイルがどこのディレクトリにあるか、指定は単数形か複数形かといった細かいルールがrailsには設定されている。ということらしいので調べる。
  • Active Recordとは何か?
  • MVCのフローを理解するため整理して図に書き出す。
  • 開発環境構築が始めの1歩だと思っていたが、「どのパッケージをどのバージョンで入れるか」というのは難易度が高いので詳しくは後で学んだ方が良い...

さいごに

2日目にして完全に自分用の学習メモと化してきました...

続けていった後で、「うわ、このとき自分はこんな簡単なことで悩んでたのかよ...」ってなれるよう精進します。


twitterもやっているので、宜しければフォローお願いします!
@tomo_tech_

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

37日目。Rubyでクラスの継承など。

クラスの継承

RubyⅢ(8分)
RubyⅣ(18分)

やっばりちょっと難しいですね・・・。
2周目だけどてこずりました。

クラスの継承

class Food < Menu
end

継承されるもの

親クラスのインスタンスとメソッド

子クラスにインスタンスを追加

クラス継承のところでattr_accessoにて追加できる。

class Food < Menu
  attr_accessor :calorie
end

子クラスにメソッドを追加

同様に、クラス継承のところでdefにて追加できる。

class Food < Menu
  attr_accessor :calorie
  def calorie_info
    return "#{self.name}#{self.calorie}kcalです"
  end
end

メソッドのオーバーライド(上書き)

子クラスと親クラスに同じ名前のメソッドがある場合、子クラスが優先される。

親クラスの同名のメソッドを呼び出す(super)

super(name: name, price: price)

日付を扱う(Dateクラス)

Rubyにて事前定義済みなので呼ぶだけでいい

# requireを用いて、Dateクラスを読み込んでください
require "date"

# 変数birthdayに、Dateクラスのインスタンスを代入してください
birthday = Date.new(2019,5,1)

# 変数birthdayをputsしてください
puts birthday

# 変数birthdayにsunday?メソッドを用いた結果をputsしてください
puts birthday.sunday?

# 変数todayに、Date.todayの戻り値を代入してください
today =Date.today

# 変数todayをputsしてください
puts today

# 変数todayに対してsunday?メソッドを用いた結果をputsしてください
puts today.sunday?

クラスメソッド

Date.todayのようちクラス名に対して呼び出すメソッドのこと

class A
  def A.B?
  end
end

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

class A
    def B
        if A.C
        end
    end
end
A.C

完成!

さらに頭がこんがらがっています。
インスタンスメソッド、クラスメソッド。
よく似ていますよね。。。実際どうやって使い分けるんでしょう。
アルゴリズム体操ですかね。。。

(所要時間 52分)
(2周目 21分)

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

【Ruby】配列内の配列のkeyをで重複をなくす方法

やりたかったこと

配列内に配列があり、その配列内の1要素をkeyとして重複を消したい。


環境

ruby 2.5.1p57 (2018-03-29 revision 63029)

sandbox.rb

sandbox.rb
def test()
  array = [["id01",111,"1"],["id01",222,"2"],["id02",111,"1"],["id02",222,"2"],["id01",333,"3"]]
  p array.group_by{ |e| e.first }.select { |k, v| v}.map{|m| m.last.last}
end
test()

内容

Ruby: 配列で重複してるものを探す - 宇宙船サンドボックスを参考に記述を変更した。
業務上1億件近くのデータを扱うため速度は重要だった。
配列で重複した場合は後勝ちとした。


結果

これが
[["id01", 111, "1"], ["id01", 222, "2"], ["id03", 111, "1"], ["id02", 111, "1"], ["id02", 222, "2"], ["id01", 333, "3"]]
これに
[["id01", 333, "3"], ["id02", 222, "2"], ["id03", 111, "1"]]

これで早くなった。
もっといい方法があれば教えてほしい。
コメントいただいてありいがとうございます!

※備忘録に近いが記載する。

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

docker で rspec の system spec を実行するための設定メモ

はじめに

docker 環境で rspec の system spec を実行させるための設定に手間取ったので、メモ代わりに書いておきます。
railsが動くdocker の image に chrome をインストールするのもちょっとなあと思ったので、 selenium/standalone-chrome-debug
を使う方法です。

確認した Rails 環境は、5.2.2 です。

ちなみに Gemfile でバージョン指定はしていませんが、Gemfile.lock を確認したところ

capybara (3.14.0)
rspec-rails (3.8.2)
selenium-webdriver (3.141.0)

となってました。

docker-compose.yml の編集

chrome が動作するように設定を追加します。
Rails は web で動作します。

docker-compose.yml
version: '3'

services:
  web:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - .:/app
      - bundle:/usr/local/bundle
    tty: true
    environment:
      # この環境変数を追加
      - "SELENIUM_DRIVER_URL=http://selenium_chrome:4444/wd/hub"
  # 以下の4行を追加
  selenium_chrome:
    image: selenium/standalone-chrome-debug
    logging:
      driver: none
  db:
    image: postgres:10.7-alpine
    volumes:
      - pgsqldb:/var/lib/postgresql/data
    environment:
      - "POSTGRES_USER=xxxx"
      - "POSTGRES_PASSWORD=xxxx"
volumes:
  pgsqldb:
  bundle:

chrome を動作させるために、selenium_chrome を追加しています。
logging の設定を driver: none にしているのは、不要なログ出力を抑制するためです。

rspec は web で実行しますが、 chrome は、selenium_chrome で動作させるため、web 側からアクセスできるように
環境変数 SELENIUM_DRIVER_URL を web 側に追加しています。

Gemfile を編集する

gem ファイルに rspec-rails を追加します。
chromedriver-helper を削除します。webdrivers gem は追加しません。

Gemfile
group :test do
  # Adds support for Capybara system testing and selenium driver
  gem 'capybara', '>= 2.15'
  gem 'selenium-webdriver'
  gem 'rspec-rails'
end

docker 環境で、bundle install を実行する

docker 環境で追加した gem をインストールします。

$ docker-compose up -d
$ docker-compose exec web bash

# 以下は web コンテナ内で実行します。
$ bundle install

rspec の初期設定をする。

続けて、rspec の初期設定をします。

# 以下は web コンテナ内で実行します。
$ bin/rails g rspec:install

system spec の設定をする。

system spec の設定をするために、 spec/rails_helper.rb を編集し、 spec/support/capybara.rb を追加します。

spec/rails_helper.rb
# 以下の1行を有効にします。
Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }

# Checks for pending migrations and applies them before tests are run.
# If you are not using ActiveRecord, you can remove these lines.
spec/support/capybara.rb
require 'capybara/rspec'

RSpec.configure do |config|
  config.before(:each, type: :system) do |config|
    driven_by :selenium, using: :headless_chrome, options: {
      browser: :remote,
      url: ENV.fetch("SELENIUM_DRIVER_URL"),
      desired_capabilities: :chrome
    }
    Capybara.server_host = 'web'
    Capybara.app_host='http://web'
  end
end

spec/support/capybara.rb 内で selenium_chrome 側の headless chrome を利用するための設定をしています。
web コンテナ側から selenium_chrome コンテナ の chrome を使用するために、 options の中で、 urlSELENIUM_DRIVER_URL 環境変数の値を設定しています。
headless chrome からは、ローカル環境ではなく、 web コンテナ側の rails アプリを表示してテストする必要があるため、
Capybara.server_hostCapybara.app_host を設定しています。

参考情報

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

Docker で RSpec の System Spec を実行するための設定メモ

はじめに

docker 環境で rspec の system spec を実行させるための設定に手間取ったので、メモ代わりに書いておきます。
railsが動くdocker の image に chrome をインストールするのもちょっとなあと思ったので、 selenium/standalone-chrome-debug
を使う方法です。

確認した Rails 環境は、5.2.2 です。

ちなみに Gemfile でバージョン指定はしていませんが、Gemfile.lock を確認したところ

capybara (3.14.0)
rspec-rails (3.8.2)
selenium-webdriver (3.141.0)

となってました。

docker-compose.yml の編集

chrome が動作するように設定を追加します。
Rails は web で動作します。

docker-compose.yml
version: '3'

services:
  web:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - .:/app
      - bundle:/usr/local/bundle
    tty: true
    environment:
      # この環境変数を追加
      - "SELENIUM_DRIVER_URL=http://selenium_chrome:4444/wd/hub"
  # 以下の4行を追加
  selenium_chrome:
    image: selenium/standalone-chrome-debug
    logging:
      driver: none
  db:
    image: postgres:10.7-alpine
    volumes:
      - pgsqldb:/var/lib/postgresql/data
    environment:
      - "POSTGRES_USER=xxxx"
      - "POSTGRES_PASSWORD=xxxx"
volumes:
  pgsqldb:
  bundle:

chrome を動作させるために、selenium_chrome を追加しています。
logging の設定を driver: none にしているのは、不要なログ出力を抑制するためです。

rspec は web で実行しますが、 chrome は、selenium_chrome で動作させるため、web 側からアクセスできるように
環境変数 SELENIUM_DRIVER_URL を web 側に追加しています。

Gemfile を編集する

gem ファイルに rspec-rails を追加します。
chromedriver-helper を削除します。webdrivers gem は追加しません。

Gemfile
group :test do
  # Adds support for Capybara system testing and selenium driver
  gem 'capybara', '>= 2.15'
  gem 'selenium-webdriver'
  gem 'rspec-rails'
end

docker 環境で、bundle install を実行する

docker 環境で追加した gem をインストールします。

$ docker-compose up -d
$ docker-compose exec web bash

# 以下は web コンテナ内で実行します。
$ bundle install

rspec の初期設定をする。

続けて、rspec の初期設定をします。

# 以下は web コンテナ内で実行します。
$ bin/rails g rspec:install

system spec の設定をする。

system spec の設定をするために、 spec/rails_helper.rb を編集し、 spec/support/capybara.rb を追加します。

spec/rails_helper.rb
# 以下の1行を有効にします。
Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }

# Checks for pending migrations and applies them before tests are run.
# If you are not using ActiveRecord, you can remove these lines.
spec/support/capybara.rb
require 'capybara/rspec'

RSpec.configure do |config|
  config.before(:each, type: :system) do |config|
    driven_by :selenium, using: :headless_chrome, options: {
      browser: :remote,
      url: ENV.fetch("SELENIUM_DRIVER_URL"),
      desired_capabilities: :chrome
    }
    Capybara.server_host = 'web'
    Capybara.app_host='http://web'
  end
end

spec/support/capybara.rb 内で selenium_chrome 側の headless chrome を利用するための設定をしています。
web コンテナ側から selenium_chrome コンテナ の chrome を使用するために、 options の中で、 urlSELENIUM_DRIVER_URL 環境変数の値を設定しています。
headless chrome からは、ローカル環境ではなく、 web コンテナ側の rails アプリを表示してテストする必要があるため、
Capybara.server_hostCapybara.app_host を設定しています。

参考情報

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

36日目。Rubyのクラスとメソッドの使い方。

今日は一週間ぶりにProgateを開きました。
まずは前回までの復習です。

RubyⅠ(10分)
RubyⅡ(14分)
RubyⅢ(9分)

若干忘れてましたが、なんとか思い出しました!
ではRubyⅣクラスとメソッドです。

クラスを作る

Class Menu
end

インスタンス変数を作る

Class Menu
  attr_accessor :name
  attr_accessor :price
end

attr_accessorって、なんて読むんだろう? アトリアクセソーかな?
なんでattr_accessorで追加できるんだろう。謎。

インスタンスの生成

menu1=Menu.new

インスタンス変数の使い方

menu1.price = 800

複数のインスタンス

menu1=Menu.new
menu2=Menu.new
menu3=Menu.new
こんな感じでどんどんふやせる。

クラスの中でメソッドを定義

できる。ある程度まとめた方が便利なんだろうな。
インスタンスメソッドという。ややこしい。

インスタンスメソッドの中でreturnする

putsもreturnも使える。

メソッドの中のインスタンス変数

自分自身に設定した値を見るにはselfを使う

self.変数

ややっこしいんだけど、インスタンス変数を呼ぶ、= self.変数
「 Aに対してBメソッドを呼び出して戻り値を出力 」
これは puts A.B 何回も出てくるよ。ややっこしい!

initializeメソッド

インスタンスを生成したとき実行されるメソッド

ファイルを分割する

①区切りのいいところでファイルを分ける
②require <なにがし>で呼び出す

コンソールから入力を受け取る

①gets.chompを実行、入力待機状態になる
②変数 = gets.chomp で入力された内容を変数で受け取れる
※数値の場合は'gets.chomp.to_i'とする
これも読み方がわからない。ゲッツチョップ、ゲッツチョップトォーアイ???

完成!

ファイルを分割してメソッドでインスタンスで頭がこんがらがってますがなんとかゴールできました!
(所要時間 1時間)

追記) Ruby語の読み方

コメントいただきました!

> attr_accessorって、なんて読むんだろう? アトリアクセソーかな?

正式な呼び方もしくは広く通用している読み方があるのかどうか私は知りませんが,attr は attribute(属性)から来ているのでしょう。
「アトリビュート・アクセッサー」とかでいいんじゃないですかね。
> gets.chomp、gets.chomp.to_i。これも読み方がわからない。
 ゲッツチョップ、ゲッツチョップトォーアイ???

gets は「ゲット・ストリング」だと長いので,私は「ゲット・エス」かなあ。
同じく puts は「プット・エス」。
(中略)
いろんな方のご意見が聞いてみたい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsのclass_methodsがやっていること

module ModuleA
  extend ActiveSupport::Concern

  class_methods do
    def method_a
       puts "method_a"
    end
  end

end

class ClassA
  include ModuleA
end

ClassA.method_a #=> "method_a"

Moduleのメソッドをクラスメソッドとして使用できるようにするために、class_methodsを使う。
class_methodsで囲んだ箇所をActiveSupportのConcernがextendしてくれる。

class_methodsがないと‥

module ModuleA

  def method_a
     puts "method_a"
  end

end

class ClassA
  include ModuleA
end

ClassA.method_a #=> "NoMethodError: undefined method `method_a' for ClassA:Class"
instance_a = ClassA.new
instance_a.method_a #=> "method_a"

ModuleAのメソッドはClassAのインスタンスメソッドになる。

ちなみに

module ModuleA
  extend ActiveSupport::Concern

  class_methods do
    def self.method_a
      puts "self_method_a"
    end
  end

end

class ClassA
  include ModuleA
end

当たり前だが、ModuleAのself.method_aはあくまでもModuleAの特異メソッドだからClassAからもインスタンスからも呼び出すことはできない。

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

Hanamiのcircle ci設定例

ほぼほぼcirciel ci公式のrubyの例のコピペですが覚書としておいておきます。

version: 2 # use CircleCI 2.0
jobs: # a collection of steps
  build: # runs not using Workflows must have a `build` job as entry point
    parallelism: 1
    docker: # run the steps with Docker
      - image: circleci/ruby:2.6.1 # ...with this image as the primary container; this is where all `steps` will run
        environment: # environment variables for primary container
          BUNDLE_JOBS: 3
          BUNDLE_RETRY: 3
          BUNDLE_PATH: vendor/bundle
          PGHOST: 127.0.0.1
          PGUSER: postgres
          HANAMI_ENV: test
      - image: circleci/postgres:9.5-alpine # database image
        environment: # environment variables for database
          POSTGRES_USER: postgres
          POSTGRES_DB: bookshelf_test
          POSTGRES_PASSWORD: "postgres"
    steps: # a collection of executable commands
      - checkout # special step to check out source code to working directory

      # Which version of bundler?
      - run:
          name: Which bundler?
          command: bundle -v

      # Restore bundle cache
      # Read about caching dependencies: https://circleci.com/docs/2.0/caching/
      - restore_cache:
          keys:
            - bookshelf-v2-{{ checksum "Gemfile.lock" }}
            - bookshelf-v2-

      - run: # Install Ruby dependencies
          name: Bundle Install
          command: bundle check || bundle install

      - run: sudo apt update
      - run: sudo apt install -y postgresql-client || true

      # Store bundle cache for Ruby dependencies
      - save_cache:
          key: bookshelf-v2-{{ checksum "Gemfile.lock" }}
          paths:
            - vendor/bundle

      - run:
          name: Wait for DB
          command: dockerize -wait tcp://localhost:5432 -timeout 1m

      - run:
          name: Database setup
          command: bundle exec hanami db prepare

      - run:
          name: Run rspec
          command: bundle exec rspec

      # Save test results for timing analysis
      - store_test_results: # Upload test results for display in Test Summary: https://circleci.com/docs/2.0/collect-test-data/
          path: test_results
      # See https://circleci.com/docs/2.0/deployment-integrations/ for example deploy configs

参考 : https://circleci.com/docs/2.0/language-ruby/#sample-configuration

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

windowsでrails + elasticsearch環境を構築する

はじめに

最近、個人的に作っているアプリで全文検索を使ってみたくなり、Elasticsearchの環境を構築しました。

しかし、家のPCのOSはWindowsで、Qiitaや開発ブログなどでは大体Mac(Linux)で書かれていて、Windowsを使っている身としては肩身が狭いです。。。そして、Elasticsearchを取り入れる時にも、エラーが結構出ました。

Windows + Railsでelasticsearchを取り入れた時に躓いた部分などを備忘録的に投稿します。elasticsearch初心者なので、初歩的なエラーが多いです。

アプリにelasticsearchをセットアップ

なお、セットアップにはこちらを参考にさせて頂きました。

gem 'elasticsearch-model', git: 'git://github.com/elasticsearch/elasticsearch-rails.git'
gem 'elasticsearch-rails', git: 'git://github.com/elasticsearch/elasticsearch-rails.git'
class PoolHall < ActiveRecord::Base
  include Elasticsearch::Model
end

以上をGemfileに記載して、bundle installを実行。Elasticsearch::Modelをincludeして、ためしにPoolHall.search 'test'をしてみました。

検索は出来ているように思えましたが、検索結果を出力してみようと思い、resultsのeachやmapを呼び出すと以下のようなエラーが出ました。

Faraday::ConnectionFailed in PoolHallsController#new

Failed to open TCP connection to localhost:9200 (Connection refused - connect(2) for "localhost" port 9200)
Extracted source (around line #6):

調べたところ、elasticsearchをlocalhost:9200で起動しないと駄目だと知りました。

elasticsearchをインストール

elasticsearchはJavaを実行できる環境が必要なので、こちらからJREをダウンロード。

Javaはインストールしていたので、実行できるかと思いきや、elasticsearchを実行するとエラーが出ました。こちらは、Javaのバージョンが古かったので、アップデートしたら実行できました。 Java SE Runtime Environment 8u211のWindows x86 Offlineをダウンロードし、インストールしました。

こちらのDownloadsのWindowsから、elasticsearchをダウンロード。バージョンは7.0.1です。

elasticsearchをダウンロードし、解凍したら、elasticsearch-7.0.1-windows-x86_64.zip\elasticsearch-7.0.1\bin\elasticsearch.batを実行すると、elasticsearchが起動できます。

インデックスを作成

以下のコマンドを実行。

rails c

elasticsearchを起動し、インデックスを作成し、登録。

PoolHall.__elasticsearch__.create_index! force: true
PoolHall.import

いちいち、コンソールを起動してやるのも面倒なので、こちらを参考にタスクを作成。

# rails g task elasticsearchで作成
namespace :elasticsearch do
  desc 'Elasticsearch のindex作成'
  task create_index: :environment do
    PoolHall.__elasticsearch__.create_index! force: true
  end

  desc 'Article を Elasticsearch に登録'
  task import_hall: :environment do
    PoolHall.import
  end
end

これで以下のコマンドでインデックスを作成できる。

rake elasticsearch:create_index
rake elasticsearch:import_hall

検索結果をactiverecordとして扱いたい

これで、検索の準備が整い、検索をしてみました。

@hall = PoolHall.search(params[:word)

viewで、検索結果を表示しようとしたところ、インスタンスメソッドを呼び出すところでメソッドが定義されていないとエラーが発生しました。

thumbnailというメソッドは、PoolHallモデルのインスタンスメソッドで、サムネイルを返すメソッドでした。

<%= @hall.each do |h| %>
  <%= image_tag h.thumbnail %>
<% end %>

調べてみると、検索結果はElasticSearchのオブジェクトとして返ってきていたので、当然の結果でした。activerecordとして扱いたい場合は以下の通り。

@hall = PoolHall.search(params[:word).records

おわりに

elasticsearchは非常に便利ですね。
今後はkibanaなどにも手を出していきたいです。

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

RubyとSlackBotを使って、誰でもIssueを作成できるBotを作ってみた。

はじめに

先日、このTweetを見かけてしまいました。

「これは...圧倒的に便利すぎる!」 と衝撃を受け、有り余る好奇心で開発を試みたのが始まりです。(しかもGWで割と時間もあったので)

今回はRubyとSlack Bot、Github APIを使用して、Slackのリアクションでissueを作成してみました。ちなみにSlack Botほぼ初心者でも2日くらいでだいたい動くものは完成したので、サクッと作れます。

完成イメージ

slack_bot.png

この記事を読んでできること

  • RubyとSlack Botを使って、SlackのリアクションでGithubのissueが作成できる。
  • Slack上の特定のメッセージ、リアクションに対して、決まった返答を返すことができる。

準備

言語:Ruby
API:Github API
Bot:Slack

Slack Botを使ったことのない方推奨の記事:
http://studio-andy.hatenablog.com/entry/ruby-bot
一度手を動かしてみると挙動が把握で切るのでオススメです!

SlackBotを作成しよう

https://amdlaboratory.com/amdblog/slack-bot%E3%81%AE%E4%BD%9C%E3%82%8A%E6%96%B9%E3%80%82/
こちらのSlack Botの作り方を参考にBotを作成してください。

APIの挙動確認、パラメータ、レスポンスなどはこちらからデモ操作できます。
https://api.slack.com/methods/chat.postMessage/test

Github APIを取得

こちらも公式APIドキュメントから細かいパラメータの設定や、レスポンスなどが確認できます。
https://developer.github.com

今回はissueを作成したいので、
https://developer.github.com/v3/issues/#create-an-issue

Rubyファイルを作成

作成するファイル構成
slack_bot
┣ slack_bot.rb
┣ Gemfile
┗ Gemfile.lock

Gemfile
# frozen_string_literal: true
source "https://rubygems.org"
ruby "2.6.3"

gem 'http'
gem 'json'
# websocketを使用する
gem 'faye-websocket'
gem 'eventmachine'

bundle install
しておく。

slack_bot.rb
require 'http'
require 'json'
require 'eventmachine'
require 'faye/websocket'
require 'uri'
require 'net/http'
require 'net/https'


# 環境変数を設定する
# export SLACK_API_TOKEN=xoxo-hogehoge-api
# export GITHUB_USERNAME=hoge
# export GITHUB_PASSWORD=hogepass

SLACK_RTM_URL="https://slack.com/api/rtm.start"
SLACK_REACTION_URL="https://slack.com/api/reactions.get"


response = HTTP.post(SLACK_RTM_URL, params: {
    token: ENV['SLACK_API_TOKEN']
})

rc = JSON.parse(response.body)
url = rc['url']

EM.run do
    # Web Socketインスタンスの立ち上げ
    ws = Faye::WebSocket::Client.new(url)

    #  接続が確立した時の処理
    ws.on :open do
        p [:open]
    end
    # RTM APIから情報を受け取った時の処理
    ws.on :message do |event|
        data = JSON.parse(event.data)
        p [:message, data]
        if data['text'] == '疲れた'
            ws.send({
                type: 'message',
                text: "お疲れ様です! <@#{data['user']}> さん",
                channel: data['channel']
            }.to_json)
        elsif data['text'] == ':github_issue:'
            ws.send({
                type: 'message',
                text: "<@#{data['user']}> さん!メッセージに、:github_issue:リアクションをするとissueが簡単に作成できますよ。",
                channel: data['channel']
            }.to_json)
        elsif data['text'] == 'グッときた'
            ws.send({
                type: 'message',
                text: "<@#{data['user']}> さん、:最高かよ:",
                channel: data['channel']
            }.to_json)
        end

        if data['reaction'] == 'github_issue'

            slack_response = HTTP.post(SLACK_REACTION_URL, params: {
                    token: ENV['SLACK_API_TOKEN'],
                    channel: data['item']['channel'],
                    timestamp: data['item']['ts']
                })
            slack_response = JSON.parse(slack_response)

            https = Net::HTTP.new('api.github.com', '443')
            https.use_ssl = true
            https.start do |https|
                # Issue を作る API: http://developer.github.com/v3/issues/#create-an-issue
                req = Net::HTTP::Post.new('/repos/githubアカウント名/リポジトリ名/issues')
                req.basic_auth ENV['GITHUB_USERNAME'],ENV['GITHUB_PASSWORD']
                issue_info = {
                'title': "#{slack_response['message']["text"]}",
                'body': "ヘルプです",
                "labels": [
                        "help wanted"
                    ]
                }

                # githubAPIを叩いて、issue作成
                req.body = JSON.generate issue_info
                github_response = https.request(req)

                # JSON.parseとは、JSON形式の文字列をRubyのHash形式に変換するためのメソッド
                github_response = JSON.parse(github_response.body)

                ws.send({
                    type: 'message',
                    text: "<@#{data['user']}> さんのために、issueを作成しました!issueボードを確認してください。 #{github_response["html_url"]}",
                    channel: data['item']['channel']
                }.to_json)
            end
        elsif  data['reaction'] == 'fish'
            slack_response = HTTP.post(SLACK_REACTION_URL, params: {
                    token: ENV['SLACK_API_TOKEN'],
                    channel: data['item']['channel'],
                    timestamp: data['item']['ts']
                })
            slack_response = JSON.parse(slack_response)

            ws.send({
                type: 'message',
                text: "魚のリアクションしましたね?",
                # text: "#{slack_response['message']['text']}に、魚のリアクションしましたね?",
                channel: data['item']['channel']
            }.to_json)
        elsif data['reaction'] == 'gyozabu'
            ws.send({
                type: 'message',
                text: "なるほど!<@#{data['user']}> さんは震えるほど餃子が食べたいみたいですよ! <@here> 今日は、餃子活動しないんですか?",
                channel: data['item']['channel']
            }.to_json)
        end
    end
    # 接続が切断した時の処理
    ws.on :close do
        p [:close, event.code]
        ws = nil
        EM.stop
    end

end

rubyスクリプト実行。
bundle exec ruby slack_bot.rb

rubyスクリプト起動.png

作成したSlackBotの入っているチャンネルで、メッセージを送信したり、リアクションしてみるとBotが動いて、挙動が確認できると思われます。

まとめ

これでエンジニアではなくても、困っているメンバーが簡単にIssueを作成できるようになりました。(使われると嬉しい。。)Issue Boadは毎週エンジニアMTGで確認して、社内メンバーの困っているIssueを改修して参ります。

おまけ

折角SlackBotを作ったので、多少ユーモアを入れてみました。
(割と開発の醍醐味だと思っています)
非常に共感したので引用させて頂きました。↓

くだらないBotを運用する一番のメリットは、楽しく技術の勉強ができるという点です。
失敗をおそれず色々な技術を試せる
工夫次第でどんな技術でも自由に取り入れられる
得た知見を業務に役立てられる
車輪の再開発/overkillが気にならない
フロント側のコードを書かなくても目に見える楽しいアウトプットがある
https://tech.dely.jp/entry/slack_bot_philosophy

今回は餃子が震えているリアクションをするとBotが反応してくれます。

gyozabu.png
(@here がメンションになっていない部分の課題感は残りつつ)
SlackBotはアイディア次第でもっと面白くできそうです!

何か間違えている部分など気になる点があれば、優しく教えて頂けると励みになります。

推奨記事

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

VSCode公式の機能で、リモートサーバにSSHして編集する【Insider Preview】

Remote Development with Visual Studio Code という機能のプレビュー版がリリースされました。

※以下、本稿の内容は2019年5月3日現在 Insider Preview (要するにベータ版みたいなの)であり、通常バージョンのVSCodeでは動作しません。

VSCode Remote 機能の概要

  • ローカルマシンでは通常通り、VScode本体とUI関連の機能等を動かしている
  • リモートサーバ上のファイルにアクセスして編集できる
  • リモートサーバ上で言語の拡張機能等を実行する
  • リモートとしては「SSH」のほかに「Container」「WSL」が選べる

今までもいくつかサードパーティのリモート編集の拡張機能は存在しましたが、「リモートサーバ上で言語の拡張機能等を実行する」という点が異なる点だと思います。これにより、リモート上の言語実行環境等を使ってローカルで開発できます。

例えば「リモートサーバ上のRubyファイルをVScodeで編集するために、ローカルにもRubyをインストールする」といった事が必要無くなるようです。リモート上のRubyを使ってローカルのVSCodeで開発できるようになります。

環境

以下、下記環境で実際に試してみます。

  • ローカル環境
    • Windows 10 Pro 1809
    • Visual Studio Code 1.34.0-insider
  • リモート環境
    • CentOS Linux release 7.6.1810
    • ※VSCode無し

手順

ここからは実際に、SSH でリモートサーバ上に接続して、リモート上で Ruby 拡張機能を動かしてみます。
だいたい Developing on Remote Machines using SSH and Visual Studio Code に書いてある通りですが。

まず下記から、InsidersビルドのVScodeをインストールします。
https://code.visualstudio.com/insiders/
Insiders buildは、緑色のアイコンが目印です。青色アイコンの安定バージョンVSCodeとは共存できますので、普段安定板を使っていても特に問題はありません。

次に、Microsoft が提供している Remote Development 拡張機能パックをインストールします。下記コマンドを使うのが早いと思います。

PowerShell
> code-insiders --install-extension ms-vscode-remote.vscode-remote-extensionpack

> code-insiders コマンドを打つか何かして Insiders Build 版の VSCode を起動します。

SSHの設定

SSH 設定は、OpenSSH の ssh_config の記法が利用できます。VSCode のコマンドパレットから >Remote-SSH: Open configuration File を実行すると設定ファイルが開くので、SSH 接続情報を記入しておきます。

たとえば > ssh your.name@your-remote-server.example.com -i C:\Users\your\secret\key\path\.ssh\id_rsa で接続できるサーバの場合、下記のような感じです。

Host your-remote-server
    HostName your-remote-server.example.com
    User your.name
    IdentityFile C:\Users\your\secret\key\path\.ssh\id_rsa

VSCode のコマンドパレットから >Remote-SSH: Connect to Host を実行し、先ほど設定したホスト情報を選択します。

うまくいけば VSCode のウィンドウがもうひとつ開いて、リモート上のファイルが実行可能になります。

リモート上の Ruby Extension を使ってみる

リモートで開いた VSCode Window で、>Extensions: Install Extensions から、Ruby 拡張機能をインストールします。
image.png
インストールボタンが「install on SSH: your-remote-server」となっているのを確認してください。通常は、単にインストールと書かれているだけだと思います。

VSCode を >Developer: Reload Window でリロードして、Open Folder ボタンから適当なフォルダを開き、index.rb みたいな適当なファイルを開いてみましょう。

下記のように、 Ctrl+Space で Ruby の補完がかかれば成功です。
image.png

失敗時は「No Suggestions.」になると思います。実際にリモート接続していない VSCode ウィンドウで試すと下記のようになり、リモート上でしか Ruby 拡張機能が動いていない事が確認できると思います。
image.png

なおインストールされた拡張機能はどこにあるのかというと、リモートサーバのホームディレクトリ以下に .vscode-remote というディレクトリが作成されており、そこに保存されるようでした。

$ ls ~/.vscode-remote/extensions/
rebornix.ruby-0.22.3

まとめ

以上より、ローカルにある VSCode を使ってリモート上の開発をすることができるようになりました。

まだプレビュー版であり、実際どれくらい便利に使えるのか分かりません。あまりにもあっさり動いたので、リモート上で拡張機能がどれくらい機能しているのか、どんな拡張機能でも動くのか、など不明点はありますが、少なくとも VSCode 公式の機能で、リモート上の開発が簡単にできるようになったというのは事実です。

これが実現すれば、ローカルに言語実行環境などをインストールしていない環境下でも、VSCode さえあれば開発ができるようになります。また、初学者や非エンジニアなど開発環境をローカルに構築するのが難しいケースにおいても有効に使えると思います。正式リリースが非常に楽しみです。

参考

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

VSCode公式の機能で、リモートサーバにSSHして編集する【Insiders Preview】

Remote Development with Visual Studio Code という機能のプレビュー版がリリースされました。

※以下、本稿の内容は2019年5月3日現在 Insider Preview (要するにベータ版みたいなの)であり、通常バージョンのVSCodeでは動作しません。

VSCode Remote 機能の概要

  • ローカルマシンでは通常通り、VScode本体とUI関連の機能等を動かしている
  • リモートサーバ上のファイルにアクセスして編集できる
  • リモートサーバ上で言語の拡張機能等を実行する
  • リモートとしては「SSH」のほかに「Container」「WSL」が選べる

今までもいくつかサードパーティのリモート編集の拡張機能は存在しましたが、「リモートサーバ上で言語の拡張機能等を実行する」という点が異なる点だと思います。これにより、リモート上の言語実行環境等を使ってローカルで開発できます。

例えば「リモートサーバ上のRubyファイルをVScodeで編集するために、ローカルにもRubyをインストールする」といった事が必要無くなるようです。リモート上のRubyを使ってローカルのVSCodeで開発できるようになります。

環境

以下、下記環境で実際に試してみます。

  • ローカル環境
    • Windows 10 Pro 1809
    • Visual Studio Code 1.34.0-insider
  • リモート環境
    • CentOS Linux release 7.6.1810
    • ※VSCode無し

手順

ここからは実際に、SSH でリモートサーバ上に接続して、リモート上で Ruby 拡張機能を動かしてみます。
だいたい Developing on Remote Machines using SSH and Visual Studio Code に書いてある通りですが。

まず下記から、InsidersビルドのVScodeをインストールします。
https://code.visualstudio.com/insiders/
Insiders buildは、緑色のアイコンが目印です。青色アイコンの安定バージョンVSCodeとは共存できますので、普段安定板を使っていても特に問題はありません。

image.png

次に、Microsoft が提供している Remote Development 拡張機能パックをインストールします。下記コマンドを使うのが早いと思います。

PowerShell
> code-insiders --install-extension ms-vscode-remote.vscode-remote-extensionpack

> code-insiders コマンドを打つか何かして Insiders Build 版の VSCode を起動します。

SSHの設定

SSH 設定は、OpenSSH の ssh_config の記法が利用できます。VSCode のコマンドパレットから >Remote-SSH: Open configuration File を実行すると設定ファイルが開くので、SSH 接続情報を記入しておきます。

たとえば > ssh your.name@your-remote-server.example.com -i C:\Users\your\secret\key\path\.ssh\id_rsa で接続できるサーバの場合、下記のような感じです。

Host your-remote-server
    HostName your-remote-server.example.com
    User your.name
    IdentityFile C:\Users\your\secret\key\path\.ssh\id_rsa

VSCode のコマンドパレットから >Remote-SSH: Connect to Host を実行し、先ほど設定したホスト情報を選択します。

うまくいけば VSCode のウィンドウがもうひとつ開いて、リモート上のファイルが実行可能になります。

リモート上の Ruby Extension を使ってみる

リモートで開いた VSCode Window で、>Extensions: Install Extensions から、Ruby 拡張機能をインストールします。
image.png
インストールボタンが「install on SSH: your-remote-server」となっているのを確認してください。通常は、単にインストールと書かれているだけだと思います。

VSCode を >Developer: Reload Window でリロードして、Open Folder ボタンから適当なフォルダを開き、index.rb みたいな適当なファイルを開いてみましょう。

下記のように、 Ctrl+Space で Ruby の補完がかかれば成功です。
image.png

失敗時は「No Suggestions.」になると思います。実際にリモート接続していない VSCode ウィンドウで試すと下記のようになり、リモート上でしか Ruby 拡張機能が動いていない事が確認できると思います。
image.png

なおインストールされた拡張機能はどこにあるのかというと、リモートサーバのホームディレクトリ以下に .vscode-remote というディレクトリが作成されており、そこに保存されるようでした。

$ ls ~/.vscode-remote/extensions/
rebornix.ruby-0.22.3

まとめ

以上より、ローカルにある VSCode を使ってリモート上の開発をすることができるようになりました。

まだプレビュー版であり、実際どれくらい便利に使えるのか分かりません。あまりにもあっさり動いたので、リモート上で拡張機能がどれくらい機能しているのか、どんな拡張機能でも動くのか、など不明点はあります。(たとえば Ruby 拡張機能でも Language Server 機能はエラーで動いておらず、まだ動かし方は調べていません)
ですが少なくとも VSCode 公式の機能で、リモート上の開発が簡単にできるようになったというのは事実です。

これが実現すれば、ローカルに言語実行環境などをインストールしていない環境下でも、VSCode さえあれば開発ができるようになります。また、初学者や非エンジニアなど開発環境をローカルに構築するのが難しいケースにおいても有効に使えると思います。正式リリースが非常に楽しみです。

参考

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

ruby オブジェクト設計

単一責任

変更に対応できるように

- wheelの切り出し

単一責任の為に

- initailizeの while=nil

変動に対応する

class Gear 
  attr_reader :chainring, :cog, :wheel

  def initialize(chainring, cog, wheel=nil)
    @chainring = chainring
    @cog = cog 
    @wheel = wheel
  end

  def ratio
    chainring / cog.to_f
  end

  def gear_inches
    ratio * wheel.diameter
  end
end

class Wheel
  attr_reader :rim, :tire

  def initialize(rim, tire)
    @rim = rim
    @tire = tire
  end

  def diameter
    rim + (tire * 2)
  end

  def circumference
    diameter * Math::PI
  end
end

@wheel = Wheel.new(26, 1.5)

p @wheel.circumference

gear2 = Gear.new(11,11,@wheel)


p gear2.gear_inches

 引数のコントロール

  • ハッシュで格納

arges = ハッシュ

  • 引数の格納順番への依存はなくなる
class Gear

  attr_reader :chinring, :cog, :wheel

  def initialize(arges)
    @chinring = arges[:chinring]
    @cog = arges[:cog]
    @wheel = arges[:wheel]
  end

  def fog
    chinring + cog + wheel
  end
end

class Wheel
  attr_reader :ring

  def initialize(ring)
    @ring = ring
  end

  def ee
   ring
  end

end

p Gear.new(chinring: 52, cog: 11, wheel: Wheel.new(11).ee ).fog


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

Ruby on railsで引っかかったところ

railsでつまづいたところをメモしていこうかなと思います。

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

Rails6 のちょい足しな新機能を試す10(year_format(令和ネタ)編)

はじめに

Rails 6 に追加されそうな新機能を試す第10段。 今回のちょい足し機能は、 date_select の year_format 編です。
datetime_select, date_select で year_format オプションを指定することで、年の書式を指定できるようになりました。

記載時点では、Rails は 6.0.0.rc1 です。 gem install rails --prerelease でインストールできます。

今回は、せっかくなので、「令和」に対応した Ruby 2.6.3 を使ってます。

$  rails --version
Rails 6.0.0.rc1

プロジェクトを作成する

rails プロジェクトを作成します。

$ rails new sandbox6_0_0rc1

scaffold で CRUD を作成する

$ cd sandbox6_0_0rc1
$ bin/rails g scaffold User name birth_at:datetime
$ bin/rails db:create db:migrate

app/views/users/_form.html.erb を編集する

:year_format オプションを追加して年の書式を変更してみます。

app/views/users/_form.html.erb
  ...
  <%= form.datetime_select :barth_at, year_format: ->year { "#{Date.new(year, 12, 31).jisx0301.split('.').first}(#{year})" } %>

登録画面を表示

User の登録画面を表示してみると、年の書式が変わっていることがわかります。
year_fmt.png

参考情報

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

Rails6 のちょい足しな新機能を試す9(エラーメッセージのフォーマット編)

はじめに

Rails 6 に追加されそうな新機能を試す第9段。 今回のちょい足し機能は、 full message 編です。
ymlファイルで指定できるエラーフォーマットが拡張されてます。

記載時点では、Rails は 6.0.0.rc1 です。 gem install rails --prerelease でインストールできます。

$  rails --version
Rails 6.0.0.rc1

プロジェクトを作成する

rails プロジェクトを作成します。

$ rails new sandbox6_0_0rc1

model を作る

$ cd sandbox6_0_0rc1
$ bin/rails g model User name email
$ bin/rails db:create db:migrate

model に validation を追加する

app/models/user.rb
class User < ApplicationRecord
  validates :name, presence: true
  validates :email, presence: true
end

何もymlファイルに指定がないとき

何もymlファイルに指定がないときには、今までと同じ Name can't be blankEmail can't be blank になります。

$ bin/rails c
Running via Spring preloader in process 653
Loading development environment (Rails 6.0.0.rc1)
irb(main):001:0> user = User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>
irb(main):002:0> user.validate
=> false
irb(main):003:0> user.errors.full_messages
=> ["Name can't be blank", "Email can't be blank"]
irb(mail):004:0> exit

yml ファイルでエラーフォーマットを指定

エラーメッセージのフォーマット全体を指定してみます。

config/locales/en.yml
en:
  errors:
    format: '%{message}'

指定されたフォーマット (can't be blank だけ) にエラーメッセージが変わります。

$ bin/rails c
Running via Spring preloader in process 696
Loading development environment (Rails 6.0.0.rc1)
irb(main):001:0> user = User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>
irb(main):002:0> user.validate
=> false
irb(main):003:0> user.errors.full_messages
=> ["can't be blank", "can't be blank"]
irb(main):004:0> exit

実はここまでは、Rails 5.2.3でも同じです。

yml ファイルで model レベルでエラーフォーマットを指定

ここからが、Rails6のちょい足しです。
エラーメッセージのフォーマットを model レベルで指定してみます。

config/locales/en.yml
en:
  errors:
    format: '%{message}'
  activerecord:
    errors:
      models:
        user:
          format: '%{attribute} : %{message}'

では、エラーメッセージを確認してみましょう。
このとき、 ActiveModel::Errors.i18n_customize_full_message = true にします。

$ bin/rails c
Running via Spring preloader in process 1338
Loading development environment (Rails 6.0.0.rc1)
irb(main):001:0> ActiveModel::Errors.i18n_customize_full_message = true
=> true
irb(main):002:0> user = User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>
irb(main):003:0> user.validate
=> false
irb(main):004:0> user.errors.full_messages
=> ["Name : can't be blank", "Email : can't be blank"]
irb(main):007:0> exit

なお ActiveModel::Errors.i18n_customize_full_message はデフォルトでは、false です。
ActiveModel::Errors.i18n_customize_full_message = false のときは、modelレベルの設定は無視されます。

$ bin/rails c
Running via Spring preloader in process 1343
Loading development environment (Rails 6.0.0.rc1)
irb(main):001:0> ActiveModel::Errors.i18n_customize_full_message
=> false
irb(main):002:0> user = User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>
irb(main):003:0> user.validate
=> false
irb(main):004:0> user.errors.full_messages
=> ["can't be blank", "can't be blank"]
irb(main):005:0> exit

yml ファイルで attribute レベルでエラーフォーマットを指定

今度は、attribute レベルでエラーフォーマットを指定してみます。

config/locales/en.yml
en:
  errors:
    format: '%{message}'
  activerecord:
    errors:
      models:
        user:
          format: '%{attribute} : %{message}'
          attributes:
            name:
              format: '%{message} - %{attribute}'

では、試してみましょう。
name は、 '%{message} - %{attribute}' のフォーマットが適用され
email は、 '%{attribute} : %{message}' のフォーマットが適用されます。

$ bin/rails c
Running via Spring preloader in process 1383
Loading development environment (Rails 6.0.0.rc1)
irb(main):001:0> ActiveModel::Errors.i18n_customize_full_message = true
=> true
irb(main):002:0> user = User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>
irb(main):003:0> user.validate
=> false
irb(main):004:0> user.errors.full_messages
=> ["can't be blank - Name", "Email : can't be blank"]
irb(main):005:0>

i18n_customize_full_message の設定

今回、irb の中で動的に設定しましたが、 i18n_customize_full_message は、 config ファイルで設定できます。

config/application.rb
module Sandbox600rc1
  class Application < Rails::Application
     ...
    config.active_model.i18n_customize_full_message = true
  end
end

参考情報

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

【Rails5.2.1】credentials.ymlを使用してアプリをHerokuにデプロイできなかった話

credentials.ymlを使用してアプリをHerokuにデプロイしようとしたら下記エラーが発生した。

NoMethodError: undefined method `[]' for nil:NilClass

解決策

下記コマンドを実行したらデプロイできた。

heroku config:set RAILS_MASTER_KEY=`cat config/master.key`
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【10日間でポートフォリオ作成に挑戦】6日目:テストコードの実装

概要

今回は、2019年のGW期間(10日間)を全て費やして取り組むポートフォリオの製作過程を取りまとめた内容を投稿させて頂きます。(投稿は毎日行う予定)

全体通した取り組みの詳細については、前回までの記事をご参照ください。

【10日間でポートフォリオ作成に挑戦】1日目:要件定義〜記事投稿のCRUD
【10日間でポートフォリオ作成に挑戦】2日目:アクセス制限〜コメントのCRUD機能
【10日間でポートフォリオ作成に挑戦】3日目:ページネーション~CKEditorの導入
【10日間でポートフォリオ作成に挑戦】4日目:テーブル分割〜CKEditorのフォームへの反映
【10日間でポートフォリオ作成に挑戦】5日目:CKEditorへ画像アップロード機能を追加

今日一日の作業内容

ここからは、今日1日で取り組んだ作業内容をご説明します。

テストコードの記述

これまで実装した各機能のテストコードを記述して行きました。

  • アカウント認証機能(devise)
  • 記事のCURD機能(一覧、新規作成、編集、削除)
  • 記事へのコメントのCRUD機能(新規作成、編集、削除)
  • 記事一覧のページネーション(kaminari)
  • 画像のアップロード機能(Shrine)
  • CKEditorによる記事の編集機能

テストで利用したgemは下記の通りです。

group :development, :test do
(中略)
  gem 'rspec-rails'
  gem 'factory_bot_rails'
  gem 'rails-controller-testing'
end

group :test do
  gem 'capybara', '>= 2.15'
  gem 'selenium-webdriver'
  gem 'chromedriver-helper'
end

なお、テストコードの実装にあたっては、Everyday Railsをかなり参考にさせて貰いました。
Everyday Rails - RSpecによるRailsテスト入門

今日の失敗

ここからは、今日の失敗をまとめます。

テストコードを通す為に無理矢理な処理を記述する

※追記(5/3):こちらのコードは、伊藤さん(@jnchito)がコメントにて、適切な修正案を提示して頂いているので、そちらもご参考にしてください。

まずは、下記のコードをご覧ください。

spec/system/post_spec.rb
require 'rails_helper'

RSpec.describe 'post_comment', type: :system do
  let!(:user) { create(:user) }
  let(:post) { create(:post, id: 1, user_id: 1) }
  let(:post_description) { create(:post_description, post_id: 1) }
  before { login_user(user) }

  it 'creates post' do
    visit posts_path
    click_on(I18n.t('common.button.new'))

    fill_in 'post[title]', with: 'テストタイトル1'
    fill_in 'post[post_description_attributes][description]', with: 'テスト詳細1'
    click_button(I18n.t('common.button.submit'))

    expect(page).to have_content('テストタイトル1', 'テスト詳細1')
  end

  it 'edits post' do
    post
    create(:post_description, post_id: 1)
    visit edit_post_path(post)

    fill_in 'post[title]', with: 'テストタイトル2'
    fill_in 'post[post_description_attributes][description]', with: 'テスト詳細2'
    click_button(I18n.t('common.button.submit'))

    expect(page).to have_content('テストタイトル2', 'テスト詳細2')
  end
end

上記のコードは二つの欠点があります。

I18n

辞書ファイルの名称と一致するかチェックしてsubmitボタンを押す処理ですが、下記の様にI18nを外すと、エラーになってしまいます。

click_button(t('common.button.submit'))
NoMethodError:
undefined method `t' for #<RSpec::ExampleGroups::Post:0x00007fb64b90cac8>

インターネットで調べると、stackoverflowに下記の様な回答があったので、それに沿って、I18nを付与すると、エラーは解消されました。

Use I18n.t instead of just t.
undefined method `t' for Admin::FaqsController:Class

しかし、I18n無しでも本来は実装出来るはずです。
その方法が見当たらなかった為、一旦この方法で実装しています。

editでのpostの定義

editアクションのテストにおいて、post_descriptionを新たに作成していますが、本来であれば、letで事前に作成しているはずなので、不要です。

しかし、このコードを外すとエラーになってしまいます。

Failures:

  1) post edits post
     Failure/Error: fill_in 'post[post_description_attributes][description]', with: 'テスト詳細2'

     Capybara::ElementNotFound:
       Unable to find field "post[post_description_attributes][description]" that is not disabled

こちらも原因が特定できませんでした、テストは正常にできている様なので、一旦はこの状態で実装し、後々対策を調査しようと考えています。

流石に疲れて来た

開発を進めながら、個人ブログとQiitaを両方毎日投稿するのは結構疲れます・・・
(特にQiitaは多く目に触れるので、かなり神経を擦り減らす・・・)

その影響で集中力が持続せず、作業効率がかなり落ちてしまっている様に感じます。
適宜息抜きも必要だと、改めて実感しました。

明日の予定

  • 記事の検索機能(ransack)
  • 記事へのいいね機能(別ユーザーのみ)
  • 記事のストック機能(自身の記事も可)

明日までにここを完成させないと、いよいよ後が無い・・・
デスマーチで乗り切るほかあるまい・・・

おまけ

最後になりますが、現在、私は下記の目標を立てて学習に取り組んでいます。

  • 3年間で「10,000時間」をプログラミングに費やす
  • その間、毎日ブログの投稿を行う

Twitterでは、その過程で学んだ事などを発信しています。
もし宜しければフォローしてみてください。

Twitter:@ryoutaku_jo
ブログ:りょうたくのWEBエンジニア日記

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

railsのbxsliderのaタグが効かないときの対処法

はじめに

スライドショーのgem「bxslider」を使用していた際に、スライドショー内のリンクをクリックしても遷移しなかったので、直接コードをいじってなんとか解決しました。
そのフローを残しておきたいと思います。

開発環境

  • windows7
  • ruby 2.4.5p335 (2018-10-18 revision 65137) [x64-mingw32]
  • Rails 5.2.3
  • bxslider-rails 4.2.5.1
  • jquery-rails 4.3.3

事象・検証状況

  • スライドショー内のリンクをクリックしても遷移しない
  • aタグをマウスオンすると遷移先URLが表示される
    • aタグを覆う要素は存在しない
  • レンダリング時のエラーなし
    • 自身のコーディングに不備はない可能性が高い
  • デベロッパーツールのコンソール上でもエラーなし
    • JQueryでもエラーはない
  • めっちゃ調べた。
    • これっぽい。

カミナリ|bxSliderのスライド内のリンクが効かなくなった際の対処方
https://kaminarimagazine.com/web/2019/03/29/bxslider%E3%81%AE%E3%82%B9%E3%83%A9%E3%82%A4%E3%83%89%E5%86%85%E3%81%AE%E3%83%AA%E3%83%B3%E3%82%AF%E3%81%8C%E5%8A%B9%E3%81%8B%E3%81%AA%E3%81%8F%E3%81%AA%E3%81%A3%E3%81%9F%E9%9A%9B%E3%81%AE%E5%AF%BE/

解説

原因

スマホのタッチ・スワイプに対応させるためのオプションで、デフォルトが trueである、「touchEnabled」オプションにより、マウスオン(タッチ)時にドラッグ(スワイプ)操作判定となる。つまり、clickイベントとmouseleaveイベントが発火しない。
もう少し詳しく話すと、ページをスライドさせない(ドラッグ(スワイプ)距離がないor短い)とき、クリック処理がされないような記述になっている。

参考サイトの対応策

  1. 「mousedown」で対策する
  2. bxSliderのオプションの「touchEnabled」を’false’に設定する

感想

どっちもやだ。

理由

  1. 操作感が変わる→気持ち悪い
  2. スマホ上でフリックによるスライドはさせたい

解決するにいたった手順

  1. gemのソースからtouchEnabledを探す
  2. initTouch呼び出してた。探す
  3. onTouchStartをbindしてた。探す
  4. いっぱい何か書いてる...。でも、 onTouchStartというよりは onTouchEndみたいな関数が関係してそう。それ、bindされてた。探す
  5. distance計算してるし、ここだ!
  6. 無理矢理クリックイベントを発火させる方法調べる → 発見!(参考サイト参照) → 完成!

onTouchStartあたりから逐次console.log(hoge);で呼び出されている関数を確認していました。

該当コード

app_name\vendor\bundle\ruby\2.4.0\gems\bxslider-rails-4.2.5.1\vendor\assets\javascripts\jquery.bxslider.js
var onTouchEnd = function(e) {
  if (slider.settings.mode === 'fade') {
    #
    # スライドショーのスタイルがfadeの時の処理
    #
  } else {
    #
    # 横スライド(horizontal)か縦スライド(vertical)の判定処理
    #
    if (!slider.settings.infiniteLoop && ((slider.active.index === 0 && distance > 0)|| (slider.active.last && distance < 0))) {
      setPositionProperty(value, 'reset', 200);
    } else {
      if (Math.abs(distance) >= slider.settings.swipeThreshold) {
        #
        # ドラッグ距離 or スワイプ距離がスライド有効距離以上の場合のスライド処理
        #
      } else {
+       document.elementFromPoint(e.clientX, e.clientY).click();
        setPositionProperty(value, 'reset', 200);
      }
    }
  }
};

まとめ

初心者によるChromeのアップデートに対する不具合解消ですので、かなり付け焼刃的な解決法だったのかな、と思っています...。ちなみに、今回はfade形式のスライドショーはを使う予定がなかったので、コードを変更していませんが、# スライドショーのスタイルがfadeの時の処理部分の類似部分を変更すればfadeでも正しく動作するはずです。

参考サイト

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

Ruby 範囲と配列についてもっと〜チェリー本4章〜

Ruby 範囲と配列についてもっと〜チェリー本4章〜

※この記事は初心者の私がチェリー本で学習するにあたって新しく知ったことや考えたことアウトプットするための場として書いています。
この記事は以下の情報を参考にして記述し、一部引用しています。
プロを目指す人のためのRuby入門
配列の便利メソッドあれこれ

範囲から値が連続する配列を作成する

(1...4).to_a
=> [1, 2, 3]

 [*'x'..'z']
=> ["x", "y", "z"]

.to_aメソッドで配列化することができます。
2つ目は[]の中に*を範囲の頭につけても配列化することができます。


配列の要素を取得するメソッド

a = [1, 2, 3, 4, 5]

a.values_at(0,1,3)
=> [1, 2, 4]

a[-1]
=> 5

a.last(3)
=> [3, 4, 5]

a.first(2)
=> [1, 2]

配列の添字から要素を複数指定して取得するときはvalue_atメソッドを使用します。
要素を1つ指定したい場合は配列[]で指定します。
配列の前後から複数指定する場合は.last.firstメソッドを使用します。

配列のメソッドについては配列の便利メソッドあれこれがとても参考になります!


配列に初期値を設定する場合の注意点-4.7.13より引用

 a = Array.new(5, 'default')
=> ["default", "default", "default", "default", "default"]
str = a[0].upcase!
=> "DEFAULT"
a => ["DEFAULT", "DEFAULT", "DEFAULT", "DEFAULT", "DEFAULT"]

a = Array.new(5){'default'}
=> ["default", "default", "default", "default", "default"]
str = a[0].upcase!
a => ["DEFAULT", "default", "default", "default", "default"]

上のコードは同じ参照値の要素が同時に作られるため、1つ変更すると5つとも変更されてしまいます。
下のコードはブロックによって一つずつ別のオブジェクトの要素が作成されるため1つ変更してもその他の要素には影響を与えません。


あとがき

今日は@jnchitoさんのQiitaで記事を公開するときに気を付けるべきマナーについて 〜無断でネットや書籍の内容を丸写しするのはやめよう〜という記事を読んで少なからず当てはまる点があるのでは無いかと思い、記事の書き方を改めるのと過去の記事に注釈を追記しました。

先程の記事内でもリンクが貼ってあった”ブログに技術書の内容を丸写しする問題点と、オリジナルなコンテンツを書くためのアイデア”がとても参考になりました!

最近コードを写経とは別に実際に書く機会が無いとQiitaなどに記事を上げるだけではアウトプットの質が低いなと思えて来ました...(何かコードの問題集みたいなのないかな)

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

イテレータでインデックスを取ってみる

前提

Rubyのバージョン: 2.5.0p0 (2017-12-25 revision 61468) [x86_64-darwin18]

インデックス取れないの? -> 取れた!

「たのしいRuby」でお勉強中。
26ページの繰り返しにtimesメソッドの記載がありました。

iterator_sample.rb
5.times do
    print '(☝︎ ՞ਊ ՞)☝︎'
end

#=> (☝︎ ՞ਊ ՞)☝︎(☝︎ ՞ਊ ՞)☝︎(☝︎ ՞ਊ ՞)☝︎(☝︎ ՞ਊ ՞)☝︎(☝︎ ՞ਊ ՞)☝︎

これを書きながらふと「これのインデックス取れんの?」と疑問に思ったわけですな。

doの後ろで取ろう

doの後ろに変数を置いてあげると勝手にブチこんでくれます。
(下記例では「i」)

index_hosii.rb
5.times do |i|
    print i
end

#=> 01234

おまけ

2つ目を指定してもなんにもでません。

j_missing.rb
5.times do |i, j|
    print j
end

#=> #何も出ない。ひたすらに虚無。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby】caseでFizzBuzzを解いてみよう!

FizzBuzzとは

1から100の数字に対して、以下の制御を行うアルゴリズムです。

  1. 3で割り切れる場合は、「Fizz」と表示
  2. 5で割り切れる場合は、「Buzz」と表示
  3. 15で割り切れる場合は、「FizzBuzz」と表示
  4. それ以外の場合は、その数値を表示

シンプルにifを使って書くのがオーソドックスかと思います。
今回は、別解としてcaseを使って表現してみます。

まずは、結果から。

(1..100).each do |num|
  puts case 0
    when num % 15 then :FizzBuzz
    when num % 3  then :Fizz
    when num % 5  then :Buzz
    else num
  end
end

ポイント1:「case 式」「when 式」という構造

caseでは、「case 式」と「when 式」という形を取り、それぞれの式の結果が等しいかどうかを判定して制御を行っています。
ここで、「式」とは「num % 15」のような計算式以外にも「0」や「"AAA"」のようなオブジェクトも含まれます。
したがって、caseの隣には必ずしも「num % 15」のような計算式を記載するとは限りません。

case "AAA"
  # whenに計算式を書くのもアリ
  when "AA" + "A" then puts "same"
  else puts "different"
end

# => same

上記の例では、
「"AAA" === "AA" + "A"」という比較を行った結果、等しいと判定して
「puts "same"」の処理が実行されています。

ポイント2:caseは該当のwhenに対するthen以降の値を返す

判定の結果、該当のwhen(else)のthen以降の結果をcaseの返り値として扱います。
試しに、putsをpメソッドに変えてみると、
それぞれ「:Fizz」、「:Buzz」、「:FizzBuzz」が表示されていることが確認できます。
以下の記述でも同様の結果が得られますが、少し冗長ですよね。

(1..100).each do |num|
  case 0
    # 判定ごとにputsを記載
    when num % 15 then puts :FizzBuzz
    when num % 3  then puts :Fizz
    when num % 5  then puts :Buzz
    else puts num
  end
end

ポイント3:whenの記載順に注意!(上から順に判定)

if同様に、判定は上から順に行い、該当したところで値を返します。
したがって、仮に以下の順にした場合は、「FizzBuzz」は表示されません。
(15の倍数は先に「num % 3」に該当してしまうため「Fizz」が表示される)

(1..100).each do |num|
  puts case 0
    # 15の倍数はここに該当してしまう
    when num % 3  then :Fizz
    when num % 5  then :Buzz
    when num % 15 then :FizzBuzz
    else num
  end
end

本記事があなたのcaseの理解に少しでも貢献できれば幸いです。

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

Ruby Gold 勉強1(メソッド探索)

Ruby Silverを合格できたので

早速、Ruby Goldの勉強を進めております。
Silverまでは「メソッドを暗記できるか」というイメージだったのですが、
Goldとなると、Rubyの言語仕様というか、特徴というか、そのようなことを理解しておく必要があると感じました。

ということで、何個か模擬問題を解いているうちに、メソッド探索について理解しておく必要があると思いましたので、自分のサイトでまとめてみました!

自分のサイト

次は、文法などもまとめていけたらと思います。

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

Rails ~会員登録までの道のり~

これまでのあらすじ

前回の記事よりActionMailerの機能でメールを飛ばせることを検証しました。
今回は、メールアドレスを認証させるために仮ユーザ登録させてからユーザ登録させるための仮ユーザ登録機能を作ってみます。
ユーザ認証に関しては、deviseなどのGemの利用がRailsでは、一般的(というか便利)らしいですが、Ruby・Railsの勉強をするために純粋にRuby・Railsで構築してきたいと思います。

Top画面の作成

まずトップ画面を作成します。
top_cotroller.rbを作成します。

terminal
$ rails g cotroller top
top_controller.rb
class TopController < ApplicationController
    def index
    end
end

indexアクションを作成し、/views/top/index.html.erbを作成します。
config/routes.rbにルートを追加する。

rootパスにtop#indexアクションを指定します。

routes.rb
Rails.application.routes.draw do
    root 'top#index'

    # top_controller
    get 'top', to: 'top#index'
end

TOP画面はこんな感じにBootstrapを駆使して作りました。

ezgif.com-video-to-gif.gif

(管理者とかあるのはご愛嬌で...)

背景に動画を埋め込んで「イマドキ」っぽいおしゃれなWebサイトっぽくしました。

仮ユーザのModel作成

仮ユーザ(temp_user)のModel(DBのテーブル)を作成します。

schema.rb
  create_table "temp_users", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
    t.string "mail_address", null: false
    t.string "last_name", null: false
    t.string "first_name", null: false
    t.string "token", limit: 64, null: false
    t.datetime "expired_at", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["mail_address"], name: "index_temp_users_on_mail_address", unique: true
    t.index ["token"], name: "index_temp_users_on_token", unique: true
  end

仮ユーザ情報として、ログインに使用するmail_addressとユーザの氏名、仮ユーザを認証するためのtoken、仮ユーザ情報の有効期限のためのexpired_atで構成されています。

仮ユーザ登録までの流れ

  1. TOP画面のsign onボタンを押下し、仮登録画面を表示。
  2. 仮登録画面で氏名・メールアドレスを入力。
  3. メールアドレス登録完了と同時に、本登録画面のurlを記載したメール送信。
  4. 仮登録が完了した旨のメッセージ表示。

1. TOP画面のsign onボタンを押下し、仮登録画面を表示。

以前の記事で紹介したモーダルウィンドウを起動し、仮登録画面を表示しました。

ezgif.com-video-to-gif.gif

top_controller#sign_onで表示しています。
form_with用の@temp_userをnewしている以外は特別なことはしていません。

top_controller.rb
class TopController < ApplicationController
    def sign_on
        @temp_user= TempUser.new
    end
end

2. 仮登録画面で氏名・メールアドレスを入力。

temp_user.rbにバリデーションを追加しました。

temp_user.rb
class TempUser < ApplicationRecord
    VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
    validates :last_name, presence: true
    validates :first_name, presence: true
    validates :mail_address, presence: true
    validates :mail_address, format: { with: VALID_EMAIL_REGEX }
end

氏名は必須。
メールアドレスは必須とメールアドレス正規表現によるフォーマットチェックをしています。

3. メールアドレス登録完了と同時に、本登録画面のurlを記載したメール送信。

top#sign_onでformを表示し、formのsubmitでtop#createを読んでいます。
本処理は

  • mail_addressがすでに使用されている(temp_userに登録済み)の場合は、UPDATE、未使用の場合は、INSERTをする。(UPSERT処理)
  • UPDATE時、入力された姓・名・有効期限は後勝ちとする。
  • tokenはランダム文字列で生成。

find_or_initialize_byで条件にformで入力されたmail_addressを指定しています。
find_or_initialize_byは取得できた場合、取得結果をModelに詰め、できない場合は、ただ単純にnewします。

その後、姓・名をセットします。

tokenですが、SecureRandom.urlsafe_base64でランダム文字列を作成しました。なお、重複登録を防ぐために、token = 生成文字列でtemp_userを検索し、存在した場合、再度生成しています。

有効期限はとりあえず現在日時をセットし、saveしています。

find_or_initialize_byで生成しているため、すでに存在している場合は、UPDATE・新規の場合はINSERTをrailsで判断しています。(便利ですね!!)

top_controller.rb
    def create
        # 入力されたメールアドレスは登録済みか
        @temp_user = TempUser.find_or_initialize_by(mail_address: temp_users_params[:mail_address])
        # 入力された姓・名をセット
        @temp_user.last_name = temp_users_params[:last_name]
        @temp_user.first_name = temp_users_params[:first_name]
        # トークンを生成
        @temp_user.token = create_token
        # 有効期限に現在日時をセットする
        @temp_user.expired_at = DateTime.now

        respond_to do |format|
            # UPSERT実行
            if @temp_user.save
                format.js { @status = "success" }
            else
                format.js { @status = "fail" }
            end
        end
    end

    private
        def create_token
            token = nil
            loop do
                token = SecureRandom.urlsafe_base64
                    break if TempUser.find_by(token: token).nil?
            end
            return token
        end

4. 仮登録が完了した旨のメッセージ表示。

成功した場合は、完了した旨のメッセージを表示しました。
これもModalで表示していますが、formのモーダルを消してから、完了のモーダルをshowするため
遅延実行しています。

create.js.erb
<% if @status == 'success' %>
    $('#top_modal').modal('hide');
    setTimeout(function(){
        $('#top_modal').html('<%= j(render 'completed_sign_on') %>');
        $('#top_modal').modal('show');
    }, 500);
<% elsif @status == 'fail' %>
    $('#sign_on_errors').html('<%= j(render 'layouts/error_messages', model: @temp_user) %>');
<% end %>

こんな感じ...

ezgif.com-video-to-gif.gif

今後は…

これで仮登録までできました。
とりあえず、前回の記事の検証と合わせて、メールを飛ばすところまではつくりたいですね。
(こうしたほうがいいとか、ご意見あったらぜひぜひいただけると嬉しいです。)

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