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

[Ruby] 複数の方法がある場合はより特化した方を選ぼう (初心者向け)

はじめに

Ruby は非常に多彩な文法やクラス、メソッドを持っており、1 つの処理を実装するのに複数の方法が存在することが多いです1。そのため、どの方法を採用すればよいか迷ったり、あるいはよりよい方法がないか気になったりする機会も多いです。そこで、そういうケースで役立つささやかな指針を提案します。それが

ある処理を実装するために複数の方法がある場合は、より特化した方を選ぼう

です。

具体例

ケース 1: 整数の配列の合計値を求める

配列などの Enumerable (列挙可能) なクラスを操作する場合、Array#each や Enumerable#map に続いて Enumerable#inject の使い方を学ぶ方も多いと思います。リファレンスには inject メソッドを用いて整数の配列の合計値を求めるコードが記載されています。

[2, 3, 4, 5].inject { |result, item| result + item } #=> 14

さらに inject の使い方に詳しくなれば、次のように書くかもしれません。

[2, 3, 4, 5].inject(:+) #=> 14

しかし、ここで立ち止まってやりたいのは、より特化した方法があるのではないかと模索することです。Ruby にはあたかもプログラマの思考を先回りしたかのように、おあつらえ向きのメソッドを用意してくれていることがよくあります2

「合計を求めるのだから sum という名前のメソッドはないか」と Array のリファレンスで sum をページ内検索してみましょう。やはりありました :clap_tone1:

[2, 3, 4, 5].sum #=> 14

ケース 2: 配列の先頭から n 個の要素を取得する

次の配列から先頭の要素を 2 つだけ抜き出したいです。

girls = ['ゆの', '宮子', '沙英', 'ヒロ']

先頭の要素を 1 つだけ抜き出すなら

girls[0] #=> "ゆの"

と書いたり、あるいは Array#first を使用して

girls.first #=> "ゆの"

と書けます。わざわざ先頭の要素を取得することに特化したメソッドが first という名前で用意されている。まさに Ruby らしいですね!

では先頭から複数の値を取得するためにはどうしたらよいでしょうか。

例えば Ruby と同じスクリプト言語の Python なら、スライスと呼ばれる機能を使用して次のように書けます。

# Python の世界
>>> girls = ['ゆの', '宮子', '沙英', 'ヒロ']
>>> girls[0:2]  # スライスという機能を使って、0 番目 (先頭) から 2 つの要素を取得する。
['ゆの', '宮子']

しかし Ruby の配列にはスライスという機能はなく、同じ書き方をするとシンタックスエラーとなります。

girls[0:2] # SyntaxError

でも Ruby のことなので、必ず Python のスライスと同じような機能が存在するはずです!Ruby を信じるのです :pray:

今度は Array のリファレンスで slice をページ内検索してみましょう。Array#slice というメソッドが見つけるはずです。

girls.slice(0, 2) #=> ["ゆの", "宮子"]

お見事!さすが Ruby :clap_tone1:

ちなみに Array#[] というメソッド (Ruby では配列の [] もメソッドなのです) では実現できないのでしょうか。リファレンスを確認すると、引数を 2 つ渡す、あるいは Range オブジェクトを渡すことで実現できることがわかります。

girls[0, 2] #=> ["ゆの", "宮子"]
girls[0..1] #=> ["ゆの", "宮子"]

喜ぶのはまだ早いです。さらに別の方法も!先ほど登場した Array#first のリファレンスをよく読んでみてください。引数を指定することで挙動が変わるのです。

girls.first(2) #=> ["ゆの", "宮子"]

なんと先頭から 2 つの要素を取得できました。Ruby は単にメソッドが多いだけではなく、メソッドの呼び方も豊富に用意されているところに多彩さを感じますね。

特化した方を選ぶメリット

特化した方を選ぶメリットですが、

  • 引数やブロックを省略できることで、コードがシンプルになる (コード量が少し減る) 。
  • メソッド名がより直感的で読みやすくなる。

と言えると思います。

例えば inject(:+) より sum の方が、slice(0, 2) より first(2) の方が引数の数が減り、なおかつ直感的に読めると思います。

また

  • 他の方法よりパフォーマンスが向上していたり不具合が解消されていたりする。

というメリットが存在する場合もあります3

まとめ

Ruby では 1 つの処理を実装するのに複数の方法が存在することが多いです。多くのメソッドが提供され、また同じメソッドでもさらに複数の機能を備えていることが多いです。そこで 慣れない処理を実装した際は、より特化した方法が存在しないかリファレンスを調べてみる ことをおすすめします。Ruby ならきっとドラえもんのひみつ道具のように、うってつけな方法を提供してくれることでしょう4 :muscle_tone1:


  1. Ruby と同じスクリプト言語の Perl には "There's more than one way to do it (それをする方法はひとつじゃない)" という思想があり、Ruby と似ています。一方で、同じくスクリプト言語の Python には "There should be one-- and preferably only one --obvious way to do it (それをする明白な方法が一つあるはず。そして望ましいのは一つだけであること)" という正反対の思想があります。 

  2. かつて Ruby はよく 驚き最小の原則 (あるいは「Matz の驚き最小の原則」)とともに語られていたということですが、このように Ruby にはプログラマがほしいと思う機能が自然な形で存在するというのも理由のひとつなのでしょう。 

  3. Array#sum もこの例に該当します。:point_right_tone1: Ruby 2.4で追加されるArray#sumで配列の要素の総和が正確かつ高速になる 

  4. もし Ruby にほしい機能が存在しない場合は、外部のライブラリである Active Support コア拡張機能 を調べてみるのも良いでしょう。Active Support とは Web フレームワークである Ruby on Rails の提供する一つの機能で、Array や Hash など Ruby の既存のクラスを拡張する形で便利なメソッドを提供しています。 

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

Lチカセブン

7つの言語でLチカ。

  1. C
  2. Go
  3. Node.js
  4. Python
  5. Ruby
  6. Rust
  7. Swift

タイトル先行なの、なんの目的もございません。
言語の選びは適当です。

環境

Model : Raspberry Pi 3 Model B Rev 1.2p
OS : Raspbian GNU/Linux 9 (stretch)

Dockerで実行環境構築

以前、ラズパイでの開発にDockerを導入する記事を投稿しました。
Raspberry Pi で TensorFlow する Docker 環境構築

  • Raspbianにプリインストール & GPIO制御可能なもの
    C Python
  • Raspbianに言語 or GPIO制御するライブラリのインストールが必要なもの
    Go Node.js Ruby Rust Swift

せっかくなので後者についてはDockerで実行環境を手に入れます。
もちろんラズパイのローカルにインストールしても問題ありません。

docker run -t -i \
  --name [container_id] \
  --rm \
  --privileged \
  -v $PWD:/usr/src:rw \ 
  -d [language_image] \
  /bin/bash

Swift以外は上記だけで動作可能です。

Lチカ

1秒間隔で7回チカらせます。
wiring.png


C

# wiringPiライブラリをリンク
gcc -o led-blinker led-blinker.c -lwiringPi
#include <wiringPi.h>

#define GPIO2 2

int main(void)
{
    wiringPiSetupGpio();

    pinMode(GPIO2, OUTPUT);

    for (int i = 0; i < 7; i++)
    {
        digitalWrite(GPIO2, HIGH);
        delay(1000);
        digitalWrite(GPIO2, LOW);
        delay(1000);
    }

    return 0;
}

Go

https://github.com/stianeikeland/go-rpio

go version # 本記事では 1.12.5

go get github.com/stianeikeland/go-rpio
package main

import (
    "os"
    "fmt"
    "time"
    "github.com/stianeikeland/go-rpio"
)

var (
    pin = rpio.Pin(2)
)

func main()  {

    err := rpio.Open()

    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    pin.Output()

    for i := 0; i < 7; i++ {
        pin.High()
        time.Sleep(1 * time.Second)
        pin.Low()
        time.Sleep(1 * time.Second)
    } 

    rpio.Close()
}

Node.js

node --version # 本記事では 12.2.0

# https://github.com/fivdi/onoff
npm install onoff
'use strict';

const Gpio = require('onoff').Gpio;
const pin = new Gpio(2, 'out');

const sleep = (ms) => {
  return new Promise(resolve => setTimeout(resolve, ms))
}

const main = async () => {
  try {
    for (let i = 0; i < 7; i++) {
      pin.writeSync(1);
      await sleep(1000);
      pin.writeSync(0);
      await sleep(1000);
    }
  } catch (err) {
    console.log(err);
  } finally {
    pin.unexport();
  }
}

main();

Python

python3 --version # 本記事では 3.5.3
import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BCM)

pin_out = 2
GPIO.setup(pin_out, GPIO.OUT)

for i in range(7):
    GPIO.output(pin_out, GPIO.HIGH)
    time.sleep(1)
    GPIO.output(pin_out, GPIO.LOW)
    time.sleep(1)

GPIO.cleanup()

Ruby

ruby --version # 本記事では2.6.3

# https://github.com/jwhitehorn/pi_piper
gem install pi_piper
require 'pi_piper'

pin = PiPiper::Pin.new(:pin => 2, :direction => :out)

7.times do
  pin.on
  sleep 1
  pin.off
  sleep 1
end

Rust

rustup -V # 本記事では 1.18.2

cargo init led_blinker

# edit Cargo.toml
# edit src/main.rs

cargo run
# Cargo.toml

[dependencies]
# https://github.com/jwhitehorn/pi_piper
rppal = "0.11.2"
// src/main.rs

use std::error::Error;
use std::thread;
use std::time::Duration;

use rppal::gpio::Gpio;

const GPIO_LED: u8 = 2;

fn main() -> Result<(), Box<dyn Error>> {

    let mut pin = Gpio::new()?.get(GPIO_LED)?.into_output();

    for _ in 0..7 {
        pin.set_high();
        thread::sleep(Duration::from_secs(1));
        pin.set_low();
        thread::sleep(Duration::from_secs(1));
    };

    Ok(())
}

Swift

Swiftは自前でimageをbuildします。

docker build -t [name] .

# Dockerfile

FROM resin/rpi-raspbian:stretch

ENV APP_ROOT /usr/src
WORKDIR $APP_ROOT
ADD . $APP_ROOT

RUN apt-get update
RUN apt-get install libxml2-dev git

# install swift4
# https://packagecloud.io/swift-arm/release/packages/raspbian/stretch/swift4_4.2.3_armhf.deb
RUN curl -s https://packagecloud.io/install/repositories/swift-arm/release/script.deb.sh | sudo bash
RUN apt-get install swift4=4.2.3

buildが完了したら他の言語と同様 docker run します。

swift --version # 本記事では 4.2.3

mkdir led-blinker && cd led-blinker
swift package init --type executable

# edit Package.swift
# edit Sources/led-blinker/main.swift 

swift build
// Package.swift

import PackageDescription

let package = Package(
    name: "led-blinker",
    dependencies: [
        // https://github.com/uraimo/SwiftyGPIO  
        .package(url: "https://github.com/uraimo/SwiftyGPIO.git", from: "1.0.0")
    ]
)
// Sources/led-blinker/main.swift 

import Glibc
import SwiftyGPIO

let gpios = SwiftyGPIO.GPIOs(for:.RaspberryPi3)
var gp = gpios[.P2]!

gp.direction = .OUT

for _ in 1...7 {
    gp.value = 1
    sleep(1)
    gp.value = 0
    sleep(1)
}

参考

ラズパイとサーバーサイドSwiftでLチカ! ( Raspberry Pi 2 + SwiftyGPIO ) by n0bisukeさん


単純に /sys/class/gpio を操作する方法もありますが、今回は各言語のライブラリを使用するようにしてみました。
RaspberryPiではPythonが選ばれることが多いですが、たまには他の言語でやってみるのも楽しそうですね。

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

Giphy APIを使ってみる

Twitterでもお馴染みの色々なgif画像が検索できるGiphy
そのAPIを使ってみた話です。コードはここ

RubyとSinatraを使って以下のような簡単な検索画面を作りました。楽しい。

試し方

ここからは大まかな流れを書きます。見た目とか詳細気になる!な方はこちら

フォームの準備

とりあえず下地として検索フォームと受け皿を作ります。

index.erb
  <form action="/search" method="POST">
    <input type="text" name="keyword" placeholder="Search">
  </form>
app.rb
post '/search' do
  puts "Search keyword >> #{params[:keyword]}"
  erb :index
end

これで検索する文字列がコントローラー内に渡されたので、apiをたたく準備をしましょう。

Giphy アカウント

Giphy Developersへ行きます。
初めての場合はアカウント作成を、そうでない場合はログインしてください。

アプリ作成

「Create an App」を押してこの画面に来てください。

アプリ名、何をするかについては各自しっかり書いて「Create New App」をしてください。

APIキー取得

ここまで来たら、先ほどの画面にアプリが登録されてAPIキーが赤字で表示されています。控えておきましょう。

APIをたたく

今回は検証のために、DBに保存して取り出すということをしていないので注意です。また、公式のドキュメントが物凄く優しいので読みましょう。

以下のコントローラーではビューからキーワードを受け取った後に
- そのキーワードでGiphyの検索APIを叩く
- 返り値の各画像に割り当てられているURLを取り出す
- 配列としてビュー部分に返す
という動作をしています。

app.rb
require 'net/http'
require 'json'

post '/search' do
  puts "Search keyword >> #{params[:keyword]}"
  @keyword = params[:keyword]

  # テキスト情報をGiphyAPI検索 (上限20個の指定) >> https://developers.giphy.com/docs/
  def Giphy(text,limit=20)
    option = 'api_key='+ENV['GIPHY_API']+'&q='+URI.encode(text)+'&limit='+limit.to_s
    url = 'http://api.giphy.com/v1/gifs/search?'+option

    # 以下は公式のdocument通り
    resp = Net::HTTP.get_response(URI.parse(url))
    buffer = resp.body
    return JSON.parse(buffer)
  end

  # 以下でAPIをたたく関数を呼び出して返り値のURL部位のみ取り出している
  @gif_urls = []
  Giphy(params[:keyword])["data"].each do |item|
  @gif_urls.concat([item["images"]["fixed_height"]["url"]])
  end

  erb :index
end

かなり強引ですが、現在のパスが/searchの時=コントローラーから値が来た時に共有されているURLの配列を参考に画像を読み込む動作をしています。

index.erb
  <form action="/search" method="POST">
    <input type="text" name="keyword" placeholder="Search">
  </form>

  <% if request.path_info == '/search' %>
    <h2><%= @keyword %></h2>
    <div>
      <% @gif_urls.each do |url| %>
        <img src="<%= url %>">
      <% end %>
    </div>
  <%end%>

以上が実装部分です。あとはcssに少し手を加えれば完了です。

最後に

試しにsearchAPIを使いましたが、他にもGiphyにはいくつかのAPIが整備されているので、気になれば公式ドキュメントから覗いてみましょう。
また、今回は複数キーワード処理をしていなかったり、APIの詳しいパラメータについて言及していないので、もっと知りたい場合は☦️公式ドキュメント☦️を見てください。全てがそこにあります。

至らぬ点が随所にある今回の記事でしたが、温かい目で見守っていただければ幸いです。

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

RubyonRailsでtwitter風webアプリケーションの作成 STEP1:ユーザーの登録

こんにちはジャムです。

Railsを一通り学んだので、自分自身のポートフォリオ用にTwitter風のwebアプリケーションを作成して行きます。

今回はSTEP1として、プロジェクトフォルダの立ち上げからユーザーの新規登録までやっていきます。

でわ、早速

プロジェクトフォルダの作成

今回はそのまんまでtwitterという名にします。

コマンドライン
$ rails new twitter #twitterという名のプロジェクトフォルダ作成

ユーザーを登録するモデル、テーブルの構築

Userモデルの作成

今回のWEBアプリのユーザー情報はメールアドレスとパスワードを設定

コマンドライン
$ rails g model User email:string password_digest:string #userモデルの作成
$ rails db:migrate #データベースへの反映、usersテーブルの作成

Gemfileにbcryptを追記

bycryptはusersテーブルにパスワードを保存する際に暗号化し、セキュリティを高めることができるgemです。

Gemfile
gem 'bcrypt'

インストール

コマンドライン
$ bundle install

userモデルにパスワード暗号化用のおまじないとバリデーションを追記

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

presence:Eメールを登録する際に空白だった場合エラーを返す

ルーティングの設定

config/routes.rb
Rails.application.routes.draw do
  resources :users
  root "users#index"
end

ユーザーコントローラーの作成

index new createコントローラーとビュー作成

コマンドライン
rails g controller Users index new create

コントローラーの修正

app/controller/users_controller.rb
class UsersController < ApplicationController

  def index
    @user = User.all 
  end

  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    @user.save
    redirect_to "/"
  end

  private
  def user_params
    params.require(:user).permit(:email,:password,:password_confirmation)
  end
end

View関連を作成

 全体のhtmlを構成するviewを若干修正

app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>Twitter</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
    <%= render "shared/header" %> #ここを追記
    <div class="container">
    <%= yield %>
    </div>
  </body>
</html>

ヘッダーを作成

先ほど記載した<%= render "shared/header" %>で読み込まれるファイル

app/views/shared/_header.html.erb
<nav class="navbar navbar-inverse">
  <div class="navbar-header">
    <%= link_to "Twitter style Web application","/", class: "navbar-brand" %>
    <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#gnavi">

      <span class="sr-only">メニュー</span><!--音声ブラウザにメニューがあることを伝えています-->
      <span class="icon-bar"></span><!--ハンバーガーメニューの三本線を設定-->
      <span class="icon-bar"></span><!--ハンバーガーメニューの三本線を設定-->
      <span class="icon-bar"></span><!--ハンバーガーメニューの三本線を設定-->

    </button>

  </div>

  <div id="gnavi" class="collapse navbar-collapse"><!--ハンバーガーメニュー内に折り畳まれる部分の設定-->
    <ul class="nav navbar-nav navbar-right">
      <li><%= link_to "新規登録",new_user_path %></li>
    </ul>
  </div>
</nav>

ユーザーの一覧を表示するビューを作成

app/views/users/index.html.erb
<h1>twitter</h1>
<% @user.each do |user| %>
<p><%= user.email %></p>
<% end %>

新規登録用のビューを作成

app/views/users/new.html.erb
  <div class="row">
  <div class="col-md-6 col-md-offset-2">
    <h1>Sign up</h1>
    <%= form_for(@user) do |f| %>
    <p><%= f.label :email , "メールアドレス"%></p>
    <p><%= f.email_field :email %></p>
    <p><%= f.label :password,"パスワード" %></p>
    <p><%= f.password_field :password %></p>
    <p><%= f.label :password_confirmation, "パスワード(再入力)" %></p>
    <p><%= f.password_field :password_confirmation %></p>
    <p><%= f.submit "登録する", class: "btn btn-primary" %></p>
    <% end %>
  </div>
</div>

bootstrapの導入

Gemfaiに追記

Gemfile
gem 'bootstrap-sass'
gem 'jquery-rails'

インストール

コマンドライン
$ bundle install

custom.scssを作成し、以下の内容を追記

app/assets/stylesheets/custom.scss
@import "bootstrap-sprockets";
@import "bootstrap";

input {
  width:600px;
}
 .btn {
   margin-top:20px;
 }

jqueryの設定追記

app/assets/javascripts/application.js
//= require jquery3
//= require bootstrap-sprockets

一旦、ブラウザで確認

コマンドライン
$ rails s -b 192.168.33.11 -d #192.168.33.11はipアドレスを確認してください

ブラウザを開いて192.168.33.11:3000にアクセス

スクリーンショット 2019-05-21 21.32.39.png

新規登録をクリック

スクリーンショット 2019-05-21 21.33.14.png

登録したユーザーが表示される

スクリーンショット 2019-05-21 21.34.07.png

今日はここまで次回はコチラ

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

[Rails]文字数バリデーション

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

[Ruby] Excel の行番号と列番号を番地 (A1 など) に変換する

仕様

例えば (1, 1) を A1 に、(2, 26) を Z2 に変換する。

require 'minitest/autorun'

class CellAddressTest < Minitest::Test
  def test_to_s
    assert_equal('A1', CellAddress.new(row: 1, column: 1).to_s)
    assert_equal('Z2', CellAddress.new(row: 2, column: 26).to_s)
    assert_equal('AA3', CellAddress.new(row: 3, column: 27).to_s)
    assert_equal('AAA100', CellAddress.new(row: 100, column: 703).to_s)
  end
end

実装

class CellAddress
  ALPHABETS = [*'A'..'Z'].freeze

  attr_reader :row_number, :column_number

  def initialize(row:, column:)
    @row_number = row
    @column_number = column
  end

  def to_s
    "#{column_letter}#{row_number}"
  end

  private

  def column_letter
    return nil unless column_number > 0

    q = column_number
    letters = []

    until q.zero?
      # 商は英語で quotient、余りは remainder というらしい。
      q, r = (q - 1).divmod(ALPHABETS.length)
      letters.push(ALPHABETS[r])
    end

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

Twitter認証を二つのモデルでログインする際の注意(OmniAuth+devise)

deviseを複数のモデルでのTwitter認証を実装しようとすると、うまくいかない。
自分は開発中のuserモデルとartistモデルがあるケースでつまったのでまとめていきます。

twitterapiの設定

Twitterのdeveloperアカウントは2018年の秋頃からしようが変更になり、英語でサービスがどのように使われるかを明示しなければならなくなりました。
https://qiita.com/tdkn/items/521686c240b0c5bc6207
承認されない時は、twitter社からメールが届き、いくつかの質問がされます。自分は2回ほどのメールのやり取りを経て、やっとアカウントが承認されました。(サービスをもっと具体的に教えろなど...)

APIKEYを設定してdeviseとomniauthの設定

gem 'devise'
gem 'omniauth-twitter'

を入れて、

下の記事のようにdeviseの設定とomniauthの設定をします
https://qiita.com/daigou26/items/d84e64af775a3054e950

複数モデルに適用させる

問題はここからです。omniauth+deviseだと複数モデルの時に適用されないのです。
実は公式のドキュメントに、

現時点では、DeviseのOmniauthableモジュールはそのままでは1つのモデルでしか利用できません。しかしご安心ください。
OmniauthableはOmniAuthの単純なラッパーにすぎないので、方法はあります。

と、記載があります。
手順にそって、複数modelにおいてもsns認証ができるようにしていきましょう。

まず、userとartistのcontrollerにある

1. deviseにおいてomniauthを使わない。

devise :omniauthable

を削除します。

2.ルーティング作成

また、routes.rbに、

get "/auth/twitter/callback" => "omniauth_callbacks#twitter"
get '/auth/another/twitter/callback' => 'anothers#twitter'

と記述し、deviseを使わずにそれぞれのルーティングを作成します。

3. controller設定

次にcontrollers/omniauth_callbacks.rbというコントローラーを作成し、

class OmniauthCallbacksController < ApplicationController
  def twitter
    callback_from :twitter
  end

  def failure
    @notice = "failure"
    redirect_to events_path, notice: 'failure'
  end


  private

  def callback_from(provider)
    provider = provider.to_s

    @user = User.find_for_oauth(request.env['omniauth.auth'])

    if @user.persisted?
      print("persisted true")
      @notice = "persisted true"
      flash[:notice] = I18n.t('devise.omniauth_callbacks.success', kind: provider.capitalize)
      sign_in_and_redirect @user, event: :authentication
    else
      print("persisted false")
      session["devise.#{provider}_data"] = request.env['omniauth.auth']
      @notice = "user is not persisted."
      redirect_to root_path, notice: 'user is not persisted.'
    end
  end
end

と記述します。
次に別のanothers_controllerというコントローラーを作成し、

class AnothersController < ApplicationController
  def twitter
    callback_from :twitter
  end

  def failure
    @notice = "failure"
    redirect_to events_path, notice: 'failure'
  end


  private

  def callback_from(provider)
    provider = provider.to_s

    @artist = Artist.find_for_oauth2(request.env['omniauth.auth'])

    if @artist.persisted?
      print("persisted true")
      @notice = "persisted true"
      flash[:notice] = I18n.t('devise.omniauth_callbacks.success', kind: provider.capitalize)
      sign_in_and_redirect @artist, event: :authentication
    else
      print("persisted false")
      session["devise.#{provider}_data"] = request.env['omniauth.auth']
      @notice = "user is not persisted."
      redirect_to root_path, notice: 'user is not persisted.'
    end
  end
end

とし、別のモデルからの設定をします。

4.プロバイダ作成

二つのルーティングができるようにプロバイダを変更します。

config/initializers/omniauth.rb
OmniAuth.config.full_host = "https://hogehoge.com" 
Rails.application.config.middleware.use OmniAuth::Builder do
  provider :twitter, ENV['TWITTER_KEY'], ENV['TWITTER_SECRET'],info_fields:"nickname, image", request_path: '/auth/twitter', callback_path: '/auth/twitter/callback'
  provider :twitter, ENV['TWITTER_KEY'], ENV['TWITTER_SECRET'],info_fields:"nickname, image", request_path: '/auth/another/twitter', callback_path: '/auth/another/twitter/callback'
end

5.viewの設定

これで、

<%= link_to 'twitterでログイン', '/auth/twitter', :id => "twitter" %>
<%= link_to 'twitterでログイン', '/auth/another/twitter', :id => "twitter" %>

と記述すると、無事ルーティングされます。

参考記事

https://qiita.com/RyosukeKawamura/items/ff1116b0a1d861a24c62

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

You must use Bundler 2 or greater with this lockfile.

解決コマンド

$ rm Gemfile.lock
$ gem install bundler
$ bundle
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby】マンデルブロ集合をNArrayで高速描出してみた

小ネタですよ。

mandel.png

旧NArrayの公式ページにサンプルがあるので、Numo::NArrayで書き直してみました。ほとんどそのままです。
https://masa16.github.io/narray/demo/mandel.en.html
実行に時間がかかるんじゃないの? いいえ、NArrayを使っているので計算時間は一瞬です。
(むしろ画像のサイズが大きくなるとgdkpixbufで画像化するのがもたつくかも)

gem install numo-narray
gem install gtk3
require 'numo/narray'
require 'gdk_pixbuf2'
include Numo

def mandel(w, h, w2, h2, zoom)

  z = (SComplex.new(1, w).seq / w - w2) * zoom +
      (SComplex.new(h, 1).seq / h - h2) * zoom * Complex(0, 1)

  c = z.dup
  a = UInt8.zeros(w, h)
  idx = Int32.new(w, h).seq

  (1..100).each do |i|
    z = z**2 + c
    idx_t = (z.abs > 2).where
    idx_f = (z.abs <= 2).where
    a[idx[idx_t]] = i
    break if idx_f.empty?

    idx = idx[idx_f]
    z = z[idx_f]
    c = c[idx_f]
  end
  a
end

data = mandel(600, 600, 0.8, 0.5, 2)

# Grayscale to RGB
data2 = UInt8.zeros(600, 600, 3)
data2[true, true, 1] = (SFloat.cast(data) / 100 * 255)

pixbuf = GdkPixbuf::Pixbuf.new(data: data2.to_string, width: 600, height: 600)

pixbuf.save('mandel.png')

mandel.png

冒頭の画像のようにカラフルにしたい? カラフルにしたいときはこんな感じです。

# frozen_string_literal: true

require 'numo/narray'
require 'gdk_pixbuf2'
include Numo

def mandel(w, h, w2, h2, zoom)
  z = (SComplex.new(1, w).seq / w - w2) * zoom +
      (SComplex.new(h, 1).seq / h - h2) * zoom * Complex(0, 1)

  c = z.dup
  a = UInt8.zeros(w, h)
  idx = Int32.new(w, h).seq

  (1..100).each do |i|
    z = z**2 + c
    idx_t = (z.abs > 2).where
    idx_f = (z.abs <= 2).where
    a[idx[idx_t]] = i
    break if idx_f.empty?

    idx = idx[idx_f]
    z = z[idx_f]
    c = c[idx_f]
  end
  a
end

# Cumoの実行速度対策
def uInt8_dstack(ar)
  x = UInt8.zeros(*ar[0].shape, 3)
  x[true, true, 0] = ar[0]
  x[true, true, 1] = ar[1]
  x[true, true, 2] = ar[2]
  x
end

# HSV を RGB に変換
def hsv2rgb(h)
  i = UInt8.cast(h * 6)
  f = (h * 6.0) - i
  p = UInt8.zeros *h.shape
  v = UInt8.new(*h.shape).fill 255
  q = (1.0 - f) * 256
  t = f * 256
  rgb = UInt8.zeros *h.shape, 3
  t = UInt8.cast(t)
  i = uInt8_dstack([i, i, i])
  rgb[i.eq 0] = uInt8_dstack([v, t, p])[i.eq 0]
  rgb[i.eq 1] = uInt8_dstack([q, v, p])[i.eq 1]
  rgb[i.eq 2] = uInt8_dstack([p, v, t])[i.eq 2]
  rgb[i.eq 3] = uInt8_dstack([p, q, v])[i.eq 3]
  rgb[i.eq 4] = uInt8_dstack([t, p, v])[i.eq 4]
  rgb[i.eq 5] = uInt8_dstack([v, p, q])[i.eq 5]
  rgb
end

data = mandel(600, 600, 0.8, 0.5, 2)

# Grayscale to RGB
data2 = hsv2rgb(SFloat.cast(data) / 100)

pixbuf = GdkPixbuf::Pixbuf.new(data: data2.to_string, width: 600, height: 600)

pixbuf.save('mandel.png')

あとは、(実際に試したわけではないですが)Cumoを導入することでGPUで動かすこともできますし、

GdkPixbufで画像データを扱っているので、Gtk3で簡単にデスクトップアプリ化することも可能ですね。

この記事は以上です。

関連資料

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

【Ruby】マンデルブロ集合をNArrayで描出してみた

小ネタですよ。

mandel.png

旧NArrayの公式ページにサンプルがあるので、Numo::NArrayで書き直してみました。ほとんどそのままです。
https://masa16.github.io/narray/demo/mandel.en.html
Rubyだし実行に時間がかかるんじゃないの? いいえ、NArrayを使っているので計算時間は一瞬です。
(むしろ画像のサイズが大きくなるとgdkpixbufで画像化するのがもたつくかも)

gem install numo-narray
gem install gtk3
require 'numo/narray'
require 'gdk_pixbuf2'
include Numo

def mandel(w, h, w2, h2, zoom)

  z = (SComplex.new(1, w).seq / w - w2) * zoom +
      (SComplex.new(h, 1).seq / h - h2) * zoom * Complex(0, 1)

  c = z.dup
  a = UInt8.zeros(w, h)
  idx = Int32.new(w, h).seq

  (1..100).each do |i|
    z = z**2 + c
    idx_t = (z.abs > 2).where
    idx_f = (z.abs <= 2).where
    a[idx[idx_t]] = i
    break if idx_f.empty?

    idx = idx[idx_f]
    z = z[idx_f]
    c = c[idx_f]
  end
  a
end

data = mandel(600, 600, 0.8, 0.5, 2)

# Grayscale to RGB
data2 = UInt8.zeros(600, 600, 3)
data2[true, true, 1] = (SFloat.cast(data) / 100 * 255)

pixbuf = GdkPixbuf::Pixbuf.new(data: data2.to_string, width: 600, height: 600)

pixbuf.save('mandel.png')

mandel.png

冒頭の画像のようにカラフルにしたい? カラフルにしたいときはこんな感じです。

# frozen_string_literal: true

require 'numo/narray'
require 'gdk_pixbuf2'
include Numo

def mandel(w, h, w2, h2, zoom)
  z = (SComplex.new(1, w).seq / w - w2) * zoom +
      (SComplex.new(h, 1).seq / h - h2) * zoom * Complex(0, 1)

  c = z.dup
  a = UInt8.zeros(w, h)
  idx = Int32.new(w, h).seq

  (1..100).each do |i|
    z = z**2 + c
    idx_t = (z.abs > 2).where
    idx_f = (z.abs <= 2).where
    a[idx[idx_t]] = i
    break if idx_f.empty?

    idx = idx[idx_f]
    z = z[idx_f]
    c = c[idx_f]
  end
  a
end

# Cumoの実行速度対策
def uInt8_dstack(ar)
  x = UInt8.zeros(*ar[0].shape, 3)
  x[true, true, 0] = ar[0]
  x[true, true, 1] = ar[1]
  x[true, true, 2] = ar[2]
  x
end

# HSV を RGB に変換
def hsv2rgb(h)
  i = UInt8.cast(h * 6)
  f = (h * 6.0) - i
  p = UInt8.zeros *h.shape
  v = UInt8.new(*h.shape).fill 255
  q = (1.0 - f) * 256
  t = f * 256
  rgb = UInt8.zeros *h.shape, 3
  t = UInt8.cast(t)
  i = uInt8_dstack([i, i, i])
  rgb[i.eq 0] = uInt8_dstack([v, t, p])[i.eq 0]
  rgb[i.eq 1] = uInt8_dstack([q, v, p])[i.eq 1]
  rgb[i.eq 2] = uInt8_dstack([p, v, t])[i.eq 2]
  rgb[i.eq 3] = uInt8_dstack([p, q, v])[i.eq 3]
  rgb[i.eq 4] = uInt8_dstack([t, p, v])[i.eq 4]
  rgb[i.eq 5] = uInt8_dstack([v, p, q])[i.eq 5]
  rgb
end

data = mandel(600, 600, 0.8, 0.5, 2)

# Grayscale to RGB
data2 = hsv2rgb(SFloat.cast(data) / 100)

pixbuf = GdkPixbuf::Pixbuf.new(data: data2.to_string, width: 600, height: 600)

pixbuf.save('mandel.png')

あとは、(実際に試したわけではないですが)Cumoを導入することでGPUで動かすこともできますし、

GdkPixbufで画像データを扱っているので、Gtk3で簡単にデスクトップアプリ化することも可能ですね。

この記事は以上です。

関連資料

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

Paperclip ~私的メモ~

動機

プロジェクトでコードの改修をするにあたりPaperclipの仕様を
理解する必要が出てきたため。

Paperclipとは

・Railsに入っているActiveRecord用のライブラリ
・ファイルアップロード用のgem

has_attached_fileメソッド

has_attached_file( name, options = {} )

・基本的にはアップロードしたファイルの保存先をオプションで指定するもの

指定できるオプション

①サイズ指定

:styles => { medium: "300x300>", thumb: "100x100>" }

②保存先URL

:url  => "/assets/photo/:id/:style/:basename.:extension"

③サーバ上の画像保存先パス

:path => "#{Rails.root}/public/assets/photo/:id/:style/:basename.:extension" 

所感

初めての記事になりますがこれを機にアウトプットを続けて行きたいと思います。
新しい発見があった際は適宜追記予定。

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

devise+ActiveAdmin+CancancanでAbilityクラスのuserがnilだった時に確認するところ

前提

先にdeviseでログイン回り構築済みの状態で、ActiveAdmin、cancancanの順に追加していった。
以下のサイトを参考に設定。

環境は以下

  • Ruby 2.5.3
  • Rails 5.2.3
  • activeadmin 2.0.0
  • cancancan 3.0.1
  • devise 4.6.2
  • rolify 5.2.0

Abilityクラスのuserがnilだったら...

ためしたところ、権限が効いていなかった。
解決策としては、active_admin.rbのconfig.current_user_methodのコメントアウトを外すことだった。

config/initialize/active_admin.rb(97行目あたり)
config.current_user_method = :current_user

その他の補足

左上のタイトルを変えたりリンクをつけたりする

config/initialize/active_admin.rb(7行目あたり)
  config.site_title = "マスタメンテナンス"
  config.site_title_link = "/admin"

サインアウトでエラーとならないよう、deleteのメソッドを追加する。

config/initialize/active_admin.rb(109行目あたり)
  config.logout_link_path = :destroy_user_session_path
  config.logout_link_method = :delete

ユーザのActiveAdminサンプル

ロールにrolifyを使用している。ユーザテーブルのマスタメンテ画面にロール情報も追加したいときのサンプル
stack overflow-Updating Roles in rolify using ActiveAdminより

app/admin/users.rb
ActiveAdmin.register User do
  permit_params :email, :password, :password_confirmation, :confirmed_at, role_ids: []

  form do |f|
    f.inputs "User Details" do
      f.input :email
      f.input :password
      f.input :password_confirmation
      f.input :confirmed_at
      f.input :roles, as: :check_boxes
    end
    f.actions
  end

  # Allow form to be submitted without a password
  controller do
    def update
      if params[:user][:password].blank?
        params[:user].delete "password"
        params[:user].delete "password_confirmation"
      end

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

【Ruby】カスタム例外を作成する簡単3ステップ

概要

Rubyでは以下の簡単なステップで独自の例外を作成する事ができます。

カスタム例外のクラスを作成

例外のクラスは他のRubyのクラスと同じです。
StandardErrorを継承する独自の例外クラスを作成しましょう。
(Rubyの慣習に従って、独自の例外クラスの名前の最後にErrorをつけます。)

class MyError < StandardError
end

メッセージを付け加える

全てのRubyの例外オブジェクトは messageの属性を持っています。

以下のようにして独自の例外にデフォルトのメッセージを定義してみましょう。

class MyError < StandardError
  def initialize(msg="My Message")
  super
  end
end

raise MyError #=> My Message

これは全ての例外に言える事ですが、以下のように表示するメッセージを明示する事もできます。

raise MyError, "Custom Message" #=> Custom Message

独自のデータを例外に付け加える

他のRubyのクラスと同様に、独自のデータを例外に付け加える事ができます。

class MyError < StandardError
  attr_reader :attr

  def initialize(msg="My default message", attr="My value")
    @attr = attr
    super(msg)
  end
end

begin
  raise MyError.new("my message", "My value")
rescue => e
  puts e.attr #=> My value
end

以上です。
簡単に独自の例外を作成する事ができました。

参照

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

(Ruby)インスタン変数について(備忘録)

変数の種類

種類 命名規則 有効範囲
(スコープ)
ローカル変数 英小文字
または_
作られた場所(メソッドなど)でのみ name
_name
インスタンス変数 @で始まる クラスのメソッド内で定義され、そのクラス内で参照可能 @name
クラス変数 @@で始まる クラスのメソッド外で定義され、継承されても参照可能 @@name
グローバル変数 $で始まる プログラムの
どこからでも
参照可能
$name

インスタンス変数とは、オブジェクトの情報を格納した変数のこと

例)

qiita.rb
class Fish
  def name=(name)
    @name = name
  end

  def name
    @name
  end
end

fish = Fish.new

fish.name = "さば"

puts fish.name

=>"さば"

解説

def name=(name)
  @name = name
end

:point_up_2:nameを@name(インスタンス変数)に格納している(セッター)

def name
  @name
end

:point_up_2:メソッドに定義して値を取り出せるようにしている(ゲッター)

fish = Fish.new

:point_up_2:クラスをオブジェクト化している

fish.name = "さば"

:point_up_2:セッターを呼び出して、"さば"と言う値を格納している

puts fish.name

:point_up_2:ゲッターを呼び出して、putsで出力する

セッターとゲッターの省略した形

・セッター

def name=(name)
  @name = name
end

||
\/

attr_writter :name

・ゲッター

def name
  @name
end

||
\/

attr_reader :name

:point_down:まとめた形

qiita.rb
class Fish
  attr_writter :name
  attr_reader :name
end

 fish = Fish.new

 fish.name = "さば"

 puts fish.name

=>"さば"

さらに省略した形

attr_writter :name
attr_reader :name

||
\/

attr_accesor :name

:point_down:まとめた形

qiita.rb
class Fish
  attr_accesor :name
end

 fish = Fish.new

 fish.name = "さば"

 puts fish.name

=>"さば"

まとめ

インスタンス変数は、セッターで格納し、ゲッターで値を取り出している

参考

【七海有里佳とRubyの基礎】Lesson15 いろいろある変数の種類
【Ruby基礎】インスタンス変数とは

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

【Ruby小ネタシリーズ】> ?a => "a"

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

$ irb
> ?{
=> "{" 
> ?\s
=> " " 
> ?a
=> "a" 

> ?ab
SyntaxError: (irb):4: syntax error, unexpected '?'


$ ruby -v
ruby 1.8.7 (2008-05-31 patchlevel 0) [i686-darwin18.5.0]

$ irb
> ?a
=> 97

> ?a.class
=> Fixnum

> ?ab
SyntaxError: compile error
(irb):2: syntax error, unexpected '?'
    from (irb):2

解説

この?はRuby1.8以下ではASCIIコードをFixnumで返していたのですが、1.9からは文字列を返すようになりました。

参考

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

いつも忘れるRailsでの nil? empty? blank? present? の使い分け

前置き

Railsで、StringやArrayの値のチェックをする際、nil? empty? blank? present? の使い分けをいつも調べ直しているので、共有メモとして投稿します。 (調べればたくさん記事は出てきますが...)

nil?

値がnilかどうか判定するメソッドです。
nilの場合は true
空のオブジェクト、または何かしら値が存在する場合は false となります。

nil.nil?
=> true

"".nil?
=> false

"hoge".nil?
=> false

empty?

空のオブジェクトかどうかを判定するメソッドです。
空の場合は true
値が存在する場合は false となります。
また、nilだった場合は、 NoMethodError が発生します。

"".empty?
=> true

"hoge".empty?
=> false

nil.empty?
=> 
Traceback (most recent call last):
     1: from (irb):1
NoMethodError (undefined method `empty?' for nil:NilClass)

blank?

空のオブジェクト、またはnilのオブジェクトかどうかを判定するメソッドです。
empty? || nil? と同等)
空のオブジェクト、またはnilのオブジェクトの場合は true
値が存在する場合は falseとなります
ただし、TAB文字・スペースは trueが返ります。

nil.blank?
=> true 

"".blank?
=> true

"hoge".blank?
=> false

"\t".blank? #TAB文字
=> true

" ".blank? #半角スペース
=> true

" ".blank? #全角スペース
=> true

present?

値が存在するかどうかを判定するメソッドです。
(blank?の反転のため !blank? と同等)
値が存在する場合は true
空のオブジェクト、またはnilのオブジェクトの場合は false となります

"hoge".present?
=> true

nil.present?
=> false 

"".present?
=> false
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[環境開発]Rails で scss が廃止された 話 by @penguin_fuyuno

結論

書き換える

# gem 'sass-rails', '~> 5.0'
gem 'sassc-rails'

理由

ただし、2019年3月に廃止予定であるため、乗り換えなければならないとのこと

作業向上labo
link: http://creative-agure.conohawing.com/efficiency_labo/?p=82

Github
link: https://github.com/sass/sassc-rails

エラー内容

Ruby Sass has reached end-of-life and should no longer be used.

* If you use Sass as a command-line tool, we recommend using Dart Sass, the new
  primary implementation: https://sass-lang.com/install

* If you use Sass as a plug-in for a Ruby web framework, we recommend using the
  sassc gem: https://github.com/sass/sassc-ruby#readme

* For more details, please refer to the Sass blog:
  https://sass-lang.com/blog/posts/7828841

環境

ruby '2.5.3'
gem 'rails', '~> 5.2.2'
gem 'mysql2', '>= 0.4.4', '< 0.6.0'

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

Rails5.2 migrate ファイルで出来ること

migrationファイルで出来ること(初学者向け)

migrationファイルとは、一言で言うと、
DBの設計図です。
マイグレーションファイルに必要なカラムを書き込み、実行することでDBにテーブルを作成できます。アプリケーションとデーターベースの間には構造的なギャップがありまして、インピーダンスミスマッチと言うそうですが、Rails ではO/RマッパーであるActiveRecordが提供されていて、SQLで書かずとも、データを取得できることになってます。

作成方法コマンド

modelと併せて作成できます:blush:

$ rails g model モデル名 

migrationファイル単体で作成できます:blush:

$ rails g migration AddXxxxxToテーブル名

書き込み例

changeメソッドで作成するカラムを指定します。
書き込んだ後、保存してください。

db/migrate/201xxxxxx_creat_books.rb
class CreateTweets < ActiveRecord::Migration[5.2]
  def change
    create_table :books do |t|
      t.string   :name
      t.text     :books
      t.text     :image
      t.timestamps null:true
   end
  end
end

主な利用できるデータ型

説明 用途
integer 数字 ユーザid
string テキスト パスワード,ユーザー名
text 文字 テキスト
boolean 真か偽か 真偽フラグ
datatime 日付と時刻 作成日時
float 浮動小数点数

実行コマンド

現時点で未実行のmigrationファイルを実行します。

rake db:migrate

schema_migrationsファイルにバージョンが記録される

db/migrate/schema.rb
ActiveRecord::Schema.define(version: 2019_05_20_151430) do

  create_table "books", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
    t.string "name"
    t.text "text"
    t.text "image"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

end

schema_migrationsファイルはmigrationファイルの実行履歴のようなもので、複数のmigrationファイルにおいても、記録されているテーブルです。最新のスキーマ情報をrubyスクリプトとして表現しています。

テーブルを確認する(phpMyadmin(Mysql))

versionが一致しているmigrationファイルであることがわかります。


mysql-pic.png


DBを元に戻したい時のコマンド

意図しないカラムができた場合や、何かしらの更新の変更の時など。

$ rake db:rollback

rake db:rollbackは最新の更新履歴の一つ前の状態に戻すコマンドです。意図しないカラムなどを削除して書き換えて、今一度、rake db:migrateで実行しても、更新はされません。新たなカラムを追加したい場合は、常にmigrationファイルを作成してください。

ActiveRecord::Base.connection.tables

DBが元に戻ったかターミナルで確認

#コンソールで確認
$ rails c

#booksがない場合は、成功してます
$ ActiveRecord::Base.connection.tables
=> ["schema_migrations",]

$ exit

これまでmigrateした履歴を表示

$ rake db:migrate:status

結果

database: book_development

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20190520151430  Create books

up で表示されるのはDBに反映されているファイルのこと。
down で表示されるのは、反映されていないファイルのこと。

migrationファイルを複数から選んでrollbackしたい時

特定のファイルを指定できます。

rails db:migrate:down Version=migrationID

まとめ

migrationファイルで出来ること(初学者向け)

migrationファイルはDB設計図であります。
今回はchangeメソッドとdbを使うときの扱い方法でした。

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

Rails6 のちょい足しな新機能を試す20(ActiveStorage::FileNotFoundError編)

はじめに

Rails 6 に追加されそうな新機能を試す第20段。 今回のちょい足し機能は、 ActiveStorage::FileNotFoundError 編です。
Rails 6.0 では、 ActiveStorage::Blob#open などで対応するファイルが見つからなかったときに ActiveStorage::FileNotFoundError が発生するようになりました。

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

$  rails --version
Rails 6.0.0.rc1

わざと動作しないコードを書く

今回は、 ActiveStorage::FileNotFoundError が発生するようにわざと動作しないコードを作成します。

Rails6 のちょい足しな新機能を試す16(UploadedFile#to_path編) で使ったコードを修正して動作しないようにします。
User モデルの avatar_file_format メソッドを動作しないように修正します。

app/models/user.rb
class User < ApplicationRecord
  ...
  private

  def avatar_file_format
    avatar.blob.open do |file|
      header = File.read file, 8
      if header != PNG_FORMAT_HEADER
        errors.add(:avatar, 'is not png format file')
      end
    end
  end
end

試してみる

Rails サーバーを立ち上げて http://localhost:3000/users/new にアクセスしてユーザー登録してみます。
アップロードする画像ファイルも指定します。
Create User ボタンを押すと ActiveStorage::FileNotFoundError が発生します。
2019-05-18-210935_790x291_scrot.png

ファイルのアップロード先に限らず、ファイルが見つからないときは、ActiveStorage::FileNotFoundError が発生するようになっています。 ファイルが見つからない場合のエラーを rescue したいときには、何も考えずに ActiveStorage::FileNotFoundError を指定しておけば良いので、便利ですね。

ソースコード

試したソースは以下にあります。
https://github.com/suketa/rails6_0_0rc1/tree/try020_active_storage_missing_file

参考情報

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

(Ruby)ハッシュについて(備忘録)

ハッシュ

・連想配列と言われ、キーワードで取り出すデータをもとめる仕組み
Webサイトのユーザー管理などに使われる

型)

qiita.rb
{key1 =>value1 , key2 => value2 ・・・・・ }

例)

qiita.rb
money = {"tom" => 400 , "ken" => 500 , ・・・・・}
user = {"name" => "tom" , "age" => "16" , "birthday" => "12/2" , ・・・・・・}

ハッシュのデータの取り出し方

qiita.rb
money["tom"]
=> 400
user["name"]
=>"tom"

シンボル

""や''と同じように扱える文字列オブジェクトのこと
シンボルはkeyにのみ使う

型)

qiita.rb
:シンボル名

例)

qiita.rb
:tom

注意・・・文字列と全く同じではないため、基本的に英文字のみで作り、_(アンダーバー)などは使用しない

シンボルを使ったハッシュの作り方

qiita.rb
user = {:name => "tom" , :age => "16" , :birthday => "12/2" , ・・・・・・}

user = {name: "tom" , age: "16" , birthday: "12/2" , ・・・・・・}

データの取り出し方

qiita.rb
user[:name]
=> "tom"

注意・・・name:となっていても、取り出すときは:nameにしなければ取り出せない

ハッシュの繰り返し処理

型)

qiita.rb
ハッシュ.each do |変数1,変数2|
  繰り返したい処理
end

メモ)
変数1はkey、変数2はvalueのこと
ハッシュはkey用とvalue用の2つの変数が必要

例)

qiita.rb
pet = {tom: "dog" , ken: "cat" , gai: "tiger"}

pet.each do |key , value|
puts "#{key}#{value} を飼っています"
end

=>
tom  dog を飼っています
ken  cat を飼っています
gai  tiger を飼っています

補足)変数は型と比較するためkeyとvalueにしましたが、実際はわかりやすい変数(nameやanimal)にする

まとめ

ハッシュの書き方

qiita.rb
{"one" => "1" , "two" => "2" ,・・・・・ }

{:one => "1" , :two => "2" ,・・・・・ }

{one: "1" , two: "2" ,・・・・・ }

ハッシュの繰り返し処理

qiita.rb
ハッシュ.each do |変数1,変数2|
  繰り返したい処理
end

pet.each do |key , value|
puts "#{key}#{value} です"
end

参考

【七海有里佳とRubyの基礎】Lesson10 配列と似て非なるもの-ハッシュ

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

GraphQLを試してみた2 -RubyでGraphQLを試してみた-

本稿はRubyでGraphQLのバックエンド側の処理を書いた際(というか調べた際)のメモ。
基本的にググったり、公式ページを見たりした。
(結果から言うと、公式の情報だけで十分だった気もする……)

尚、本稿の前提は以下の通り。

  • Rubyが分かる。
  • Bundlerをインストール済み (なくても意味は分かれば十分)。
  • GraphQLが何者を知ってる。

本稿の各サンプルの実行方法

最初に本稿の各サンプルコードの実行方法を示す。

  • 以下のようなGemfileを用意する。
Gemfile
source "https://rubygems.org"

gem "graphql"
  • GraphQLライブラリをインストールする。
$ bundle install --path vendor/bundle
  • サンプルコードを保存し、以下のように実行する。
$ bundle exec ruby xxxx.rb

ファイルの保存が面倒であれば以下のようにirb経由でも良い。

$ bundle exec irb

Query

GraphQLライブラリでQueryを書く方法は以下の2つ。

  • defineメソッドを使用する方法
  • 子クラスを定義する方法

defineメソッドを定義する方法

test1.rb
require 'graphql'

QueryType = GraphQL::ObjectType.define do
  name 'Query'
  field :name do
    type types.String
    description 'the name of this thing'
    resolve -> (obj, args, ctx) { 'Nanashi' }
  end
end

Schema = GraphQL::Schema.define do
  query QueryType
end

puts Schema.execute('{name}').to_json
puts Schema.execute('{n1:name, n2:name}').to_json
実行結果
$ bundle exec ruby test1.rb 
{"data":{"name":"Nanashi"}}
{"data":{"n1":"Nanashi","n2":"Nanashi"}}
$

子クラスを定義する方法

Nullableの設定有無などの差異はあるが、以下は基本的にtest1.rbと同じような内容である。

test2.rb
require 'graphql'

class QueryType < GraphQL::Schema::Object
  description 'query'
  field :name, String, null:true, description:'the name of this thing'
  def name()
    'Nanashi'
  end
end

class Schema < GraphQL::Schema
  query QueryType
end

puts Schema.execute('{name}').to_json
puts Schema.execute('{n1:name, n2:name}').to_json

実行結果はtest1.rbと完全に同じである。


Queryを使ったサンプル

動的にクエリ結果を変更する例

test3.rb
require 'graphql'

$id = 0

class QueryType < GraphQL::Schema::Object
  field :id, ID, null:true, description:'id'
  field :name, String, null:true, description:'name'
  def id()
    retval = $id
    $id += 1
    return retval
  end
  def name()
    'Nanashi'
  end
end

class Schema < GraphQL::Schema
  query QueryType
end

puts Schema.execute('{id}').to_json
puts Schema.execute('{id,name}').to_json
puts Schema.execute('{id1:id, id2:id}').to_json
実行結果
$ bundle exec ruby test3.rb
{"data":{"id":"0"}}
{"data":{"id":"1","name":"Nanashi"}}
{"data":{"id1":"2","id2":"3"}}
$

引数ありクエリ

test4.rb
require 'graphql'

class QueryType < GraphQL::Schema::Object
  field :func, String, null:false do
    argument :id, Int, required: true
    argument :sex, String, required: false
    description 'the name of this thing'
  end
  def func(id:, sex:'male')
    "No. #{id}_sex:#{sex}"
  end
end

class Schema < GraphQL::Schema
  query QueryType
end

puts Schema.execute('{func(id:123)}').to_json
puts Schema.execute('{func(id:123, sex:"female")}').to_json
実行結果
$ bundle exec ruby test4.rb
{"data":{"func":"No. 123_sex:male"}}
{"data":{"func":"No. 123_sex:female"}}
$

型定義

型定義については本稿では省略する。
興味がある場合は公式ページの下記を参照。

https://graphql-ruby.org/type_definitions/objects.html

Mutation

Rubyでは、Mutationを以下のように記載する。

test5.rb
require 'graphql'

class UpdateAddressMutation < GraphQL::Schema::Mutation
  null true

  argument :pcode, Int, required: false
  argument :address, String, required: false

  field :rc, String, null:false
  field :output, Int, null:false

  def resolve (pcode: nil, address: nil)
    str="pcode:#{pcode}, address:#{address}"
    puts str
    { rc:'OK', output:str }
  end
end

class MutationType < GraphQL::Schema::Object
  field :updateAddress, mutation: UpdateAddressMutation
end

class Schema < GraphQL::Schema
  mutation MutationType
end

puts Schema.execute('mutation { updateAddress(pcode:123456, address:"Fuga-shi, Hoge-ken"){ rc } }').to_json
実行結果
$ bundle exec test6.rb
pcode:123456, address:Fuga-shi, Hoge-ken
{"data":{"updateAddress":{"rc":"OK"}}}
$

Relay形式のMutation

Relayについての詳細は割愛するが、Relay準拠でMutationを使用する場合、
「GraphQL::Relay::Mutation」を使用する。

test6.rb
require 'graphql'

UpdateAddressMutation = GraphQL::Relay::Mutation.define do
  name 'UpdateAddress'
  description 'example'

  input_field :postal_code, !types.Int
  input_field :address, !types.String

  return_field :rc, !types.Int
  return_field :v, !types.String

  resolve ->(_obj, inputs, ctx) {
    { rc:200, v:"No.#{inputs[:postal_code]}"}
  }
end

class MutationType < GraphQL::Schema::Object
  field :updateAddress, field: UpdateAddressMutation.field
end

class Schema < GraphQL::Schema
  mutation MutationType
end

puts Schema.execute('mutation { updateAddress(input :{postal_code:123, address:"Hoge-Ken"}){ v } }').to_json
puts Schema.execute('mutation { updateAddress(input :{postal_code:123, address:"Hoge-Ken"}){ rc, v } }').to_json
puts Schema.execute('mutation { v1:updateAddress(input :{postal_code:123, address:"Hoge-Ken"}){ v }, v2:updateAddress(input :{postal_code:345, address:"Fuga-Ken"}){ rc } }').to_json
実行結果
$ bundle exec ruby test6.rb
{"data":{"updateAddress":{"v":"No.123"}}}
{"data":{"updateAddress":{"rc":200,"v":"No.123"}}}
{"data":{"v1":{"v":"No.123"},"v2":{"rc":200}}}
$

詳細は以下を参照。

https://graphql-ruby.org/relay/mutations.html

ググった感じでは、Ruby+GraphQLの日本語記事は大体Relay形式だった。

その他

エラーを返却

エラーを返却する場合は以下のように例外を送出する。
以下はtest5.rbのresolve定義を変更したものである。

test7.rb
require 'graphql'

class UpdateAddressMutation < GraphQL::Schema::Mutation
  null true

  argument :pcode, Int, required: false
  argument :address, String, required: false

  field :rc, String, null:false
  field :output, Int, null:false

  def resolve(pcode: nil, address: nil)
    raise GraphQL::ExecutionError.new('Oh!')
  end
end

class MutationType < GraphQL::Schema::Object
  field :updateAddress, mutation: UpdateAddressMutation
end

class Schema < GraphQL::Schema
  mutation MutationType
end

puts Schema.execute('mutation { updateAddress(pcode:123456, address:"Fuga-shi, Hoge-ken"){ rc } }').to_json
実行結果
{"data":{"updateAddress":null},"errors":[{"message":"Oh!","locations":[{"line":1,"column":12}],"path":["updateAddress"]}]}

Sinatra + GraphQL

Sinatra+GraphQLを使ったサンプルは以下の通りである。

Gemfile
source "https://rubygems.org"

gem "sinatra"
gem "sinatra-contrib"
gem 'rack-contrib'
gem "graphql"
sinatra_graphql.rb
require 'graphql'
require 'sinatra'
require 'sinatra/json'
require 'rack/contrib'
use Rack::PostBodyContentTypeParser

$list = {}
$id = 0

def reqId()
  rc = $id
  $id += 1
  return rc
end

class QueryType < GraphQL::Schema::Object
  field :user, String, null:true do
    argument :id, Int, required:false
  end
  def user(id:)
    $list[id]
  end
end

class CreateMutation < GraphQL::Schema::Mutation
  argument :name, String, required: true

  field :id, Int, null:false
  field :rc, String, null:false

  def resolve (name:)
    i = reqId()
    $list[i] = name
    { rc:'OK', id:i }
  end
end

class MutationType < GraphQL::Schema::Object
  field :create, mutation: CreateMutation
end

class Schema < GraphQL::Schema
  query QueryType
  mutation MutationType
end

post '/graphql' do
  json Schema.execute(params[:query], variables: params[:variables]).to_json
end
  • サーバ側でアプリを実行する。
サーバ側_実行開始
$ bundle exec ruby sinatra_graphql.rb
[2019-05-20 18:23:08] INFO  WEBrick 1.3.1
[2019-05-20 18:23:08] INFO  ruby 2.3.3 (2016-11-21) [x86_64-linux-gnu]
== Sinatra (v2.0.5) has taken the stage on 4567 for development with backup from WEBrick
[2019-05-20 18:23:08] INFO  WEBrick::HTTPServer#start: pid=1361 port=4567
  • ID=0のユーザを問い合わせる(ユーザなし)。
実行結果1
$ curl -H 'Content-type: application/json' -X POST http://localhost:4567/graphql -d '{ "query" : "query { user(id:0)}" }'
"{\"data\":{\"user\":null}}"
  • ユーザを登録する。
実行結果2
$ curl -H 'Content-type: applicatalhost:4567/graphql -d '{ "query" : "mutation { create(name:'Ken'){id} }" }'
"{\"data\":{\"create\":{\"id\":0}}}"
  • ID=0のユーザを問い合わせる(ユーザあり)。
実行結果3
$ curl -H 'Content-type: application/json' -X POST http://localhost:4567/graphql -d '{ "query" : "query { user(id:0)}" }'
"{\"data\":{\"user\":\"Ken\"}}"
  • 2ユーザをまとめて登録する。
実行結果4
$ curl -H 'Content-type: application/json' -X POST http://localhost:4567/graphql -d '{ "query" : "mutation { mike:create(name:'Mike'){rc,id}, john:create(name:'John'){rc,id} }" }'
"{\"data\":{\"mike\":{\"rc\":\"OK\",\"id\":1},\"john\":{\"rc\":\"OK\",\"id\":2}}}"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

docker imageを直接pullしてみた

https://github.com/cielavenir/pulldockerimage

背景

userlandの仮想化基盤としてだけでなく、ソフトウェアの直接導入にもdockerは使われてきている。dockerはimageという単位で取り扱うことが多いが、このimageをリモートリポジトリからpullしたり、リモートリポジトリにpushしたりすることが可能である。これによりimageを共有することができるのである。
ところが一部、このpullを直接はできない環境があり得る。当該マシンが直接インターネットに繋がっていないことや、イントラネットにすら繋がっていないこともあるだろう。そのような場合、docker loadにより標準入出力を介してimageをやり取りすることになる。この場合に使われるのはpullしてきたファイル群をTarで固めたものである。これは手元のdocker daemonからdocker saveすることで作成することができるが、一旦手元にdocker pullしなければ、docker saveは実行できない。バッチ処理に組み込む場合等、不便なことがあるかもしれない。
今回は、リモートからダウンロードしたdocker imageのTarを直接標準出力に生成することを試みる。[1]はそうした試みの先駆けであるが、単体ではログイン機構を有しておらず、docker.ioで使うには力不足である。これをBashからRuby/Pythonで書き直し、再利用をしやすくしたい。

方法

docker pullはHTTPSを用いており、認証 -> マニフェストダウンロード -> レイヤーダウンロード -> メタファイル生成という順序で行われる。
まずはマニフェストをダウンロードする。ここで401になったら、WWW-Authenticateヘッダの情報を用いて認証を行う。この認証はBasic認証であり、docker loginが済んでいれば、認証文字列は.docker/config.jsonから直接得ることができる。ここで帰ってきたtokenをBearer認証に用いる。レイヤーはマニフェストに書いてある順番で重ねれば良い。レイヤーのIDは[1]にならいSHA256(parentId+"\n"+layerDigest+"\n")とした。

結果

モジュール

Pythonは標準ライブラリで完結させることができた。Rubyはminitarとmechanizeについて外部ライブラリを利用する。minitarはTarライブラリで、mechanizeはプログラムベースブラウザとも言うべきウェブ操作ライブラリである。

WWW-Authenticateヘッダの取扱い

Pythonはurllib内にparse_keqv_listやparse_http_listといった関数群があり、これでparseすることができる。Rubyは標準ライブラリでは処理できないため、mechanizeを使用する。ただし、mechanizeはsavonというSOAPライブラリが導入されている環境に導入しようとするとruby-ntlm関係でエラーが発生することが知られているため、今回はMechanize::HTTP::WWWAuthenticateParserの中身を埋め込むこととした。

30xステータスコードの取扱い

mechanizeであれば30xステータスコードが来たら自動でLocationを読みに行ってくれるが、HTTPを直接触る限りそういうわけにも行かない。30xが来たら、今のレスポンスを捨てたあと、locationを読みに行き30xが来る限りレスポンスを捨てることを繰り返せば良い。この繰り返しはブロック(Pythonの場合はcontextmanagerを使う)を用いて行い、HTTP bodyはyieldで渡すようにすると、後処理が楽である。

Tarの取扱い

Tar(に限らずアーカイブファイル一般)はヘッダにメンバサイズを書かなければならない(またはアーカイブに追加したあとでシークが必要であるが、標準出力を扱うため認められない)。HTTPヘッダのContent-Lengthをこのメンバサイズとして書き込めば良い。Rubyの場合は、tar.add_fileではなくtar.add_file_simpleを使う必要があった。add_fileはメンバ内に書き込んだサイズを自動判別(しシーク)するバージョンだからである。

結論

今回はRuby/Pythonを用いてdocker imageをtarとしてpullすることができた。
https://github.com/cielavenir/pulldockerimage にて利用可能である。

参考

[1] https://raw.githubusercontent.com/moby/moby/master/contrib/download-frozen-image-v2.sh

余談

(英語で書いたとしても)Linux Journalとかに載せるレベルじゃないですよねー肝心な場所のアルゴリズムは既存の技術ですしorz
7bgzfは違う領域だからある程度の価値があるけども。

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

[Ruby] FizzBuzz問題各繰り返しによる処理速度の計測

はじめに

今回は7つの繰り返し条件式(for, while, each, until, times, upto, downto)を使ってそれぞれ検証します。
gem benchmark_driverを使います。

@scivola さん、アドバイスありがとうございます。

共通条件

  • 繰り返し上限値:10000
  • 前判定
  • FizzBuzz条件式は全て同じ

実行コード

benchmark.rb
require 'benchmark_driver'

Benchmark.driver do |x|

  n = 10000

  x.report  "#{n}回 for", %{ 
    for i in 1..#{n} do
      if i % 15 == 0
           "FizzBuzz"
      elsif i % 3 == 0
           "Fizz"
      elsif i % 5 == 0
           "Buzz"
      else
           i
      end
    end
  }

  x.report  "#{n}回 while", %{
    i = 1
    while i <= #{n} do
      if i % 15 == 0
           "FizzBuzz"
      elsif i % 3 == 0
           "Fizz"
      elsif i % 5 == 0
           "Buzz"
      else
           i
      end
    i += 1
    end
  }

  x.report  "#{n}回 each", %{
    (1..#{n}).each { |i| 
      if i % 15 == 0
           "FizzBuzz"
      elsif i % 3 == 0
           "Fizz"
      elsif i % 5 == 0
           "Buzz"
      else
           i
      end
    }
  }

  x.report  "#{n}回 until", %{
    i = 0
    until i >= #{n}
      if i % 15 == 0
           "FizzBuzz"
      elsif i % 3 == 0
           "Fizz"
      elsif i % 5 == 0
           "Buzz"
      else
           i
      end
    i += 1
    end
  }

  x.report  "#{n}回 times", %{
    #{n}.times do |i| 
      if i % 15 == 0
           "FizzBuzz"
      elsif i % 3 == 0
           "Fizz"
      elsif i % 5 == 0
           "Buzz"
      else
           i
      end
    end
  }

  x.report  "#{n}回 upto", %{
    1.upto(#{n}) do |i| 
      if i % 15 == 0
           "FizzBuzz"
      elsif i % 3 == 0
           "Fizz"
      elsif i % 5 == 0
           "Buzz"
      else
           i
      end
    end
  }

  x.report  "#{n}回 downto", %{
    #{n}.downto(1) do |i| 
      if i % 15 == 0
           "FizzBuzz"
      elsif i % 3 == 0
           "Fizz"
      elsif i % 5 == 0
           "Buzz"
      else
           i
      end
    end
  }
end

結果

一番早かったのがwhileでしたがuntilとどんぐりの背比べ程度の差でした。

Warming up --------------------------------------
          10000回 for    819.214 i/s -     860.000 times in 1.049787s (1.22ms/i)
        10000回 while     1.453k i/s -      1.460k times in 1.004510s (688.02μs/i)
         10000回 each    946.035 i/s -      1.001k times in 1.058100s (1.06ms/i)
        10000回 until     1.386k i/s -      1.480k times in 1.067445s (721.25μs/i)
        10000回 times    918.016 i/s -     920.000 times in 1.002161s (1.09ms/i)
         10000回 upto    867.162 i/s -     920.000 times in 1.060932s (1.15ms/i)
       10000回 downto    854.204 i/s -     890.000 times in 1.041906s (1.17ms/i)
Calculating -------------------------------------
          10000回 for    855.651 i/s -      2.457k times in 2.871499s (1.17ms/i)
        10000回 while     1.424k i/s -      4.360k times in 3.061448s (702.17μs/i)
         10000回 each    869.559 i/s -      2.838k times in 3.263725s (1.15ms/i)
        10000回 until     1.393k i/s -      4.159k times in 2.985758s (717.90μs/i)
        10000回 times    862.132 i/s -      2.754k times in 3.194408s (1.16ms/i)
         10000回 upto    845.573 i/s -      2.601k times in 3.076020s (1.18ms/i)
       10000回 downto    858.080 i/s -      2.562k times in 2.985737s (1.17ms/i)

Comparison:
        10000回 while:      1424.2 i/s 
        10000回 until:      1392.9 i/s - 1.02x  slower
         10000回 each:       869.6 i/s - 1.64x  slower
        10000回 times:       862.1 i/s - 1.65x  slower
       10000回 downto:       858.1 i/s - 1.66x  slower
          10000回 for:       855.7 i/s - 1.66x  slower
         10000回 upto:       845.6 i/s - 1.68x  slower

参考文献

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

Ruby で UTF-8 なのにマジックコメントが必要なケース

何の話?

Ruby のスクリプトは UTF-8 のほか CP9321 など複数の文字コードで書くとができる。
スクリプトを記述している文字コードを「スクリプトエンコーディング」2という。

Ruby はバージョン 1.9 以降,マジックコメントというものによってスクリプトエンコーディングをあらわに表現できるようになった。

具体的にはスクリプトの第一行に

# encoding: UTF-8

とか

# encoding: CP932

などと書く。

しかし,スクリプトエンコーディングは UTF-8 であることが多いので,Ruby 2.0 以降,デフォルトが UTF-8 になった。
つまり,マジックコメントを略した場合はスクリプトエンコーディングが UTF-8 であると解釈されることになった。
これにより,UTF-8 で書く限りはマジックコメントは不要になったはずである。
ところがそうとも言い切れないよ,というのが本記事の主旨。

開発環境が引き起こす問題

イマドキのテキストエディターや統合開発環境(IDE)では,書いているスクリプトを色分け表示してくれたり,構文上おかしいところなどに下線を引いてエラーメッセージや警告を表示してくれたりする。

ところが,マジックコメント無しだと,正しく書いているのに間違っているとツッコミを入れてくる場合がある。

私が見つけたのは以下のケース。
いずれも Atom 最新版(1.37.0)に linter-ruby パッケージ最新版(1.3.1)を入れてチェックをかけた。(linter-ruby パッケージを使うためには linter パッケージも必要)
スクリプトはすべて UTF-8 で書き,マジックコメントは入れない。

実験は Windows と macOS で行った。
前者 は RubyInstaller for Windows で,後者は rbenv でインストールした,いずれも Ruby 2.6.3 を使用。

ヒアドキュメント

再現コードは以下のとおり。

p <<EOTEOT

(べつに p は要らないんだけど,入れないと「リテラル使ってないやん」とツッコミが入る)
第 1 行について

unexpected end-of-input, expecting tSTRING_CONTENT or tSTRING_DBEG or tSTRING_DVAR or tSTRING_END

というエラーが出る。
どうやらヒアドキュメント中に非 ASCII 文字があるとダメなようだ。
式展開をしない'EOT' のタイプのヒアドキュメントだとエラーは出ない。

Windows だけかと思ったら macOS でも同じだった。

マジックコメントを付けるとエラーは出なくなる。

正規表現1

再現コードは以下のとおり。

p(/[あ]/)

これは Windows と macOS とで違うエラーが出た。

エラーメッセージをコピペし忘れたが,Windows の場合,「] が無いやんけ」というエラーだったと思う。

これはおそらく以下のような原因だ。

まず  は UTF-8 のバイト列として E3 81 82 である。
Windows ではこれを CP932 として解釈しようとしたようだ。
まず,E3 81 は CP932 で という文字になる。
そして次の 82]5D)とで一つの文字となる。
(0x825D は CP932 の文字として定義されていないがコードポイントとしては存在する)
その結果,正規表現の閉じの ] が無いことになる。

一方,macOS のほうでは,エラーとして /[]/ と表示された。
んあ? よく分からない。文字クラスの中身が空っぽ? もしかしたら ASCII-8BIT として解釈しようとしたのかもしれないが,確かな証拠はない。

Windows でも macOS でも,マジックコメントを付けるとエラーは出なくなる。

正規表現2

再現コードは以下のとおり。

p(/[あかさ]/)

Windows でも macOS でも,前の節の例と同じエラーが出る。

Windows の場合,それに加えて

character class has duplicated range: /[あかさ]/

という警告も出る。

これは以下のような原因であるらしい。

まず,linter-ruby パッケージは,/[abca]/ のような重複のある文字クラスについて,上記のような警告を出す。
あかさ という UTF-8 文字列(3 バイト × 3 文字)は以下のようなバイト列である。

E3 81 82 E3 81 8B E3 81 95

これを CP932 として解釈すると,2 バイト × 3 文字 + 1 バイト × 1 文字となるが,この中に 0xE381 の文字が 2 回出てくるのだ。

VSCode ではどうか

Visual Studio Code ではどうなのか。
Windows で実験してみたが,linter-ruby を使ったのに同じ現象は起きないようだった。
ただ,VSCode は今日初めて使ったので,使い方というか設定の仕方が正しいかどうかよく分からない。

追試頼む。

実行に支障が起きるケース

ここまでに述べたのは,開発環境でのスクリプトのチェックでエラーや警告が出る話だった。
いずれもストレスフルだが,スクリプトの実行自体には差し支えなかった。

しかし,実行自体に問題が起きる場合もある。
これについてはだいぶ前に書いた記事があるので,そちらを参照してほしい。
Rails テンプレートにはマジコメ必要 - Qiita

直接の題材は Ruby on Rails であり,しかも「Rails テンプレート」というマイナーなものに関する報告だった。
しかし,原理的には Rails と無関係に起きうる問題である。

詳細はリンク先を見ていただくとして,手短に書くと,スクリプトを require するのではなく文字列として読み込んで eval する場合に起こりうるという話。


  1. CP932(Codepage 932)は Windows 31J ともいう。Shift JIS(シフト符号化方式の JIS 系文字コード)の一種であり,JIS コード(JIS X 0208)の文字集合に漢字・記号などを追加したもの。 

  2. この記事では「文字コード」と「エンコーディング」は同じと思ってもらって大丈夫。 

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