- 投稿日:2019-05-21T23:36:44+09:00
[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
をページ内検索してみましょう。やはりありました[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 を信じるのです
今度は Array のリファレンスで
slice
をページ内検索してみましょう。Array#slice というメソッドが見つけるはずです。girls.slice(0, 2) #=> ["ゆの", "宮子"]お見事!さすが Ruby
ちなみに 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
![]()
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 (それをする明白な方法が一つあるはず。そして望ましいのは一つだけであること)" という正反対の思想があります。 ↩
かつて Ruby はよく 驚き最小の原則 (あるいは「Matz の驚き最小の原則」)とともに語られていたということですが、このように Ruby にはプログラマがほしいと思う機能が自然な形で存在するというのも理由のひとつなのでしょう。 ↩
Array#sum もこの例に該当します。
Ruby 2.4で追加されるArray#sumで配列の要素の総和が正確かつ高速になる ↩
もし Ruby にほしい機能が存在しない場合は、外部のライブラリである Active Support コア拡張機能 を調べてみるのも良いでしょう。Active Support とは Web フレームワークである Ruby on Rails の提供する一つの機能で、Array や Hash など Ruby の既存のクラスを拡張する形で便利なメソッドを提供しています。 ↩
- 投稿日:2019-05-21T23:24:08+09:00
Lチカセブン
7つの言語でLチカ。
- C
- Go
- Node.js
- Python
- Ruby
- Rust
- 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/bashSwift以外は上記だけで動作可能です。
Lチカ
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.3import 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_piperrequire '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.3buildが完了したら他の言語と同様
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が選ばれることが多いですが、たまには他の言語でやってみるのも楽しそうですね。
- 投稿日:2019-05-21T23:04:03+09:00
Giphy APIを使ってみる
Twitterでもお馴染みの色々なgif画像が検索できるGiphy。
そのAPIを使ってみた話です。コードはここ。RubyとSinatraを使って以下のような簡単な検索画面を作りました。楽しい。
試し方
ここからは大まかな流れを書きます。見た目とか詳細気になる!な方はこちらへ
フォームの準備
とりあえず下地として検索フォームと受け皿を作ります。
index.erb<form action="/search" method="POST"> <input type="text" name="keyword" placeholder="Search"> </form>app.rbpost '/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.rbrequire '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の詳しいパラメータについて言及していないので、もっと知りたい場合は☦️公式ドキュメント☦️を見てください。全てがそこにあります。至らぬ点が随所にある今回の記事でしたが、温かい目で見守っていただければ幸いです。
- 投稿日:2019-05-21T21:51:04+09:00
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です。
Gemfilegem 'bcrypt'インストール
コマンドライン$ bundle installuserモデルにパスワード暗号化用のおまじないとバリデーションを追記
app/models/user.rbclass User < ApplicationRecord validates :email, presence: true,uniqueness: true validates :password, presence: true has_secure_password endpresence:Eメールを登録する際に空白だった場合エラーを返す
ルーティングの設定
config/routes.rbRails.application.routes.draw do resources :users root "users#index" endユーザーコントローラーの作成
index new createコントローラーとビュー作成
コマンドラインrails g controller Users index new createコントローラーの修正
app/controller/users_controller.rbclass 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 endView関連を作成
全体の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に追記
Gemfilegem 'bootstrap-sass' gem 'jquery-rails'インストール
コマンドライン$ bundle installcustom.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-21T21:16:35+09:00
[Rails]文字数バリデーション
- 投稿日:2019-05-21T19:00:01+09:00
[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
- 投稿日:2019-05-21T17:43:42+09:00
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.rbOmniAuth.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' end5.viewの設定
これで、
<%= link_to 'twitterでログイン', '/auth/twitter', :id => "twitter" %> <%= link_to 'twitterでログイン', '/auth/another/twitter', :id => "twitter" %>と記述すると、無事ルーティングされます。
参考記事
https://qiita.com/RyosukeKawamura/items/ff1116b0a1d861a24c62
- 投稿日:2019-05-21T16:42:31+09:00
You must use Bundler 2 or greater with this lockfile.
- 投稿日:2019-05-21T15:32:23+09:00
【Ruby】マンデルブロ集合をNArrayで高速描出してみた
小ネタですよ。
旧NArrayの公式ページにサンプルがあるので、Numo::NArrayで書き直してみました。ほとんどそのままです。
https://masa16.github.io/narray/demo/mandel.en.html
実行に時間がかかるんじゃないの? いいえ、NArrayを使っているので計算時間は一瞬です。
(むしろ画像のサイズが大きくなるとgdkpixbufで画像化するのがもたつくかも)gem install numo-narray gem install gtk3require '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')冒頭の画像のようにカラフルにしたい? カラフルにしたいときはこんな感じです。
# 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で簡単にデスクトップアプリ化することも可能ですね。
この記事は以上です。
関連資料
- 投稿日:2019-05-21T15:32:23+09:00
【Ruby】マンデルブロ集合をNArrayで描出してみた
小ネタですよ。
旧NArrayの公式ページにサンプルがあるので、Numo::NArrayで書き直してみました。ほとんどそのままです。
https://masa16.github.io/narray/demo/mandel.en.html
Rubyだし実行に時間がかかるんじゃないの? いいえ、NArrayを使っているので計算時間は一瞬です。
(むしろ画像のサイズが大きくなるとgdkpixbufで画像化するのがもたつくかも)gem install numo-narray gem install gtk3require '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')冒頭の画像のようにカラフルにしたい? カラフルにしたいときはこんな感じです。
# 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で簡単にデスクトップアプリ化することも可能ですね。
この記事は以上です。
関連資料
- 投稿日:2019-05-21T15:13:33+09:00
Paperclip ~私的メモ~
動機
プロジェクトでコードの改修をするにあたりPaperclipの仕様を
理解する必要が出てきたため。Paperclipとは
・Railsに入っているActiveRecord用のライブラリ
・ファイルアップロード用のgemhas_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"所感
初めての記事になりますがこれを機にアウトプットを続けて行きたいと思います。
新しい発見があった際は適宜追記予定。
- 投稿日:2019-05-21T14:34:00+09:00
devise+ActiveAdmin+CancancanでAbilityクラスのuserがnilだった時に確認するところ
前提
先にdeviseでログイン回り構築済みの状態で、ActiveAdmin、cancancanの順に追加していった。
以下のサイトを参考に設定。
- Deviseインストール済環境でActiveAdminの導入
- CanCanCanを使ってみる
- 【Rails】ActiveAdmin+CanCanCanで権限毎に色々したい
- [rails]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.rbActiveAdmin.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
- 投稿日:2019-05-21T13:57:16+09:00
【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以上です。
簡単に独自の例外を作成する事ができました。参照
- 投稿日:2019-05-21T12:20:06+09:00
(Ruby)インスタン変数について(備忘録)
変数の種類
種類 命名規則 有効範囲
(スコープ)例 ローカル変数 英小文字
または_作られた場所(メソッドなど)でのみ name
_nameインスタンス変数 @で始まる クラスのメソッド内で定義され、そのクラス内で参照可能 @name クラス変数 @@で始まる クラスのメソッド外で定義され、継承されても参照可能 @@name グローバル変数 $で始まる プログラムの
どこからでも
参照可能$name インスタンス変数とは、オブジェクトの情報を格納した変数のこと
例)
qiita.rbclass 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
nameを@name(インスタンス変数)に格納している(セッター)
def name @name end
メソッドに定義して値を取り出せるようにしている(ゲッター)
fish = Fish.new
クラスをオブジェクト化している
fish.name = "さば"
セッターを呼び出して、"さば"と言う値を格納している
puts fish.name
ゲッターを呼び出して、putsで出力する
セッターとゲッターの省略した形
・セッター
def name=(name) @name = name end||
\/attr_writter :name・ゲッター
def name @name end||
\/attr_reader :name
まとめた形
qiita.rbclass 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
まとめた形
qiita.rbclass Fish attr_accesor :name end fish = Fish.new fish.name = "さば" puts fish.name =>"さば"まとめ
インスタンス変数は、セッターで格納し、ゲッターで値を取り出している
参考
- 投稿日:2019-05-21T12:18:47+09:00
【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からは文字列を返すようになりました。
参考
- 投稿日:2019-05-21T11:57:09+09:00
いつも忘れるRailsでの nil? empty? blank? present? の使い分け
前置き
Railsで、StringやArrayの値のチェックをする際、
nil? empty? blank? present?
の使い分けをいつも調べ直しているので、共有メモとして投稿します。 (調べればたくさん記事は出てきますが...)nil?
値がnilかどうか判定するメソッドです。
nilの場合はtrue
空のオブジェクト、または何かしら値が存在する場合はfalse
となります。nil.nil? => true "".nil? => false "hoge".nil? => falseempty?
空のオブジェクトかどうかを判定するメソッドです。
空の場合は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? #全角スペース => truepresent?
値が存在するかどうかを判定するメソッドです。
(blank?の反転のため!blank?
と同等)
値が存在する場合はtrue
空のオブジェクト、またはnilのオブジェクトの場合はfalse
となります"hoge".present? => true nil.present? => false "".present? => false
- 投稿日:2019-05-21T10:27:16+09:00
[環境開発]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=82Github
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'
- 投稿日:2019-05-21T10:19:03+09:00
Rails5.2 migrate ファイルで出来ること
migrationファイルで出来ること(初学者向け)
migrationファイルとは、一言で言うと、
DBの設計図です。
マイグレーションファイルに必要なカラムを書き込み、実行することでDBにテーブルを作成できます。アプリケーションとデーターベースの間には構造的なギャップがありまして、インピーダンスミスマッチと言うそうですが、Rails ではO/RマッパーであるActiveRecordが提供されていて、SQLで書かずとも、データを取得できることになってます。作成方法コマンド
modelと併せて作成できます
$ rails g model モデル名migrationファイル単体で作成できます
$ rails g migration AddXxxxxToテーブル名書き込み例
changeメソッドで作成するカラムを指定します。
書き込んだ後、保存してください。db/migrate/201xxxxxx_creat_books.rbclass 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:migrateschema_migrationsファイルにバージョンが記録される
db/migrate/schema.rbActiveRecord::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 endschema_migrationsファイルはmigrationファイルの実行履歴のようなもので、複数のmigrationファイルにおいても、記録されているテーブルです。最新のスキーマ情報をrubyスクリプトとして表現しています。
テーブルを確認する(phpMyadmin(Mysql))
versionが一致しているmigrationファイルであることがわかります。
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 booksup で表示されるのはDBに反映されているファイルのこと。
down で表示されるのは、反映されていないファイルのこと。migrationファイルを複数から選んでrollbackしたい時
特定のファイルを指定できます。
rails db:migrate:down Version=migrationIDまとめ
migrationファイルで出来ること(初学者向け)
migrationファイルはDB設計図であります。
今回はchangeメソッドとdbを使うときの扱い方法でした。
- 投稿日:2019-05-21T09:26:36+09:00
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.rbclass 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
が発生します。
ファイルのアップロード先に限らず、ファイルが見つからないときは、
ActiveStorage::FileNotFoundError
が発生するようになっています。 ファイルが見つからない場合のエラーをrescue
したいときには、何も考えずにActiveStorage::FileNotFoundError
を指定しておけば良いので、便利ですね。ソースコード
試したソースは以下にあります。
https://github.com/suketa/rails6_0_0rc1/tree/try020_active_storage_missing_file参考情報
- 投稿日:2019-05-21T08:39:38+09:00
(Ruby)ハッシュについて(備忘録)
ハッシュ
・連想配列と言われ、キーワードで取り出すデータをもとめる仕組み
・Webサイトのユーザー管理などに使われる型)
qiita.rb{key1 =>value1 , key2 => value2 ・・・・・ }例)
qiita.rbmoney = {"tom" => 400 , "ken" => 500 , ・・・・・} user = {"name" => "tom" , "age" => "16" , "birthday" => "12/2" , ・・・・・・}ハッシュのデータの取り出し方
qiita.rbmoney["tom"] => 400 user["name"] =>"tom"シンボル
""や''と同じように扱える文字列オブジェクトのこと
シンボルはkeyにのみ使う型)
qiita.rb:シンボル名例)
qiita.rb:tom
注意・・・文字列と全く同じではないため、基本的に英文字のみで作り、_(アンダーバー)などは使用しない
シンボルを使ったハッシュの作り方
qiita.rbuser = {:name => "tom" , :age => "16" , :birthday => "12/2" , ・・・・・・} user = {name: "tom" , age: "16" , birthday: "12/2" , ・・・・・・}データの取り出し方
qiita.rbuser[:name] => "tom"注意・・・name:となっていても、取り出すときは:nameにしなければ取り出せない
ハッシュの繰り返し処理
型)
qiita.rbハッシュ.each do |変数1,変数2| 繰り返したい処理 endメモ)
変数1はkey、変数2はvalueのこと
ハッシュはkey用とvalue用の2つの変数が必要例)
qiita.rbpet = {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参考
- 投稿日:2019-05-21T04:03:32+09:00
GraphQLを試してみた2 -RubyでGraphQLを試してみた-
本稿はRubyでGraphQLのバックエンド側の処理を書いた際(というか調べた際)のメモ。
基本的にググったり、公式ページを見たりした。
(結果から言うと、公式の情報だけで十分だった気もする……)尚、本稿の前提は以下の通り。
- Rubyが分かる。
- Bundlerをインストール済み (なくても意味は分かれば十分)。
- GraphQLが何者を知ってる。
本稿の各サンプルの実行方法
最初に本稿の各サンプルコードの実行方法を示す。
- 以下のようなGemfileを用意する。
Gemfilesource "https://rubygems.org" gem "graphql"
- GraphQLライブラリをインストールする。
$ bundle install --path vendor/bundle
- サンプルコードを保存し、以下のように実行する。
$ bundle exec ruby xxxx.rbファイルの保存が面倒であれば以下のようにirb経由でも良い。
$ bundle exec irbQuery
GraphQLライブラリでQueryを書く方法は以下の2つ。
- defineメソッドを使用する方法
- 子クラスを定義する方法
defineメソッドを定義する方法
test1.rbrequire '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.rbrequire '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.rbrequire '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.rbrequire '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.rbrequire '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.rbrequire '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.rbrequire '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を使ったサンプルは以下の通りである。
Gemfilesource "https://rubygems.org" gem "sinatra" gem "sinatra-contrib" gem 'rack-contrib' gem "graphql"sinatra_graphql.rbrequire '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}}}"
- 投稿日:2019-05-21T02:01:09+09:00
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は違う領域だからある程度の価値があるけども。
- 投稿日:2019-05-21T01:13:34+09:00
[Ruby] FizzBuzz問題各繰り返しによる処理速度の計測
はじめに
今回は7つの繰り返し条件式(for, while, each, until, times, upto, downto)を使ってそれぞれ検証します。
gem benchmark_driverを使います。@scivola さん、アドバイスありがとうございます。
共通条件
- 繰り返し上限値:10000
- 前判定
- FizzBuzz条件式は全て同じ
実行コード
benchmark.rbrequire '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参考文献
- 投稿日:2019-05-21T00:50:37+09:00
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 <<EOT あ EOT(べつに
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
する場合に起こりうるという話。