# 誤り検出方法 checksumについて
ブロックチェーン関連の実装を行なっていた際に、チェックサムで引っかかり「アドレスがinvalidです」と表示されることがあった。その際に、「チェックサムって誤り検出のやつだよな?仕組み知らないわ」となったので、少し調べて簡易的に実装してみた。
## チャックサムとは?
チャックサムとは、データの信頼性を検査するための算出した値のことを言う。この値を元に、データが適切なものであるかを調べることができる
非常に単純な誤り検出方法の１つで、誤り検出の精度は低いが原理が簡単で容易に実装でき、計算コストも低いため、簡単な誤り検出方法として広く普及している
データの誤り検出の方法としてハッシュ関数を利用する方法もあるが、誤り検出のために送信する必要のあるデータのサイズが大きく、処理負荷が大きい。しかし、誤り検出の信頼性はチャックサムを利用する場合よりも高い。
## チェックサムの求め方
チャックサムの求め方については、様々な手法があるが簡単な方法としては、データ列を整数値の列としてみなし和を求め、これをある定数で割った余りを検査用データとするものがある。
## チャックサムの計算の例
[0xc0 0xA8 0xFF 0x58]から1バイトのチェックサムを算出するのは、
0xc0+0xA8 +0xFF+0x58 = 0x2BFを求め、0x100で割った余りの0xBFがチャックサムになる
データのサイズが大きい場合は、一定の大きさごとに区切ってチャックサムを算出付加する方法が利用される
## チャックサムを利用した誤り検知の実装
文字列に関してチェックサムを利用したデータの作成の関数と、誤り検出を行う関数を簡易的に実装した。
## チャックサムを付加したデータを作る関数
文字列を整数値に変換して、その和を100で割ったものをチャックサムにし、データに付加して送信用のデータを作成する関数def make_data(raw_data) checksum = 0 raw_data.unpack("C*").each do |byte| checksum += byte end return raw_data.unpack("C*").push(checksum / 100) end
## 付加されたチェックサムを確認する関数
受信したデータのチャックサムを確認し、適正なデータであれば表示し、データが破損していれば、その旨を表示する関数def check_data(data) sent_checksum = data.pop checksum = 0 data.each do |byte| checksum += byte end return sent_checksum == checksum / 100 end def show_data(hex_data) data = "" if(check_data(hex_data)) then hex_data.each do |byte| data += byte.chr end p data + ":データは破損していません" else p "データが破損しています" end end
## 利用例raw_data = "Hello" send_data = make_data(raw_data) show_data(send_data) // => "Hello:データは破損していません" raw_data = "Hello" send_data = make_data(raw_data) send_data[1] = 0 //通信経路で意図的にデータを書き換える show_data(send_data) // =>"データが破損しています"
今回の例では、利用できるchecksumは0から99の100通りである。そのため、通信経路で改ざんや破損があった際に、必ず検出することができるとは限らない。和を割る数を100から大きいものにすれば、誤りを検出できる確率は高くなるが、検出に必要なデータサイズが大きくなってしまう。そうなってしまうと、データのハッシュ値を利用する方がより信頼性の高い誤り検出ができる。あくまで簡易的な誤り検出であることがわかった。
また、アルゴリズム的にもチャックサムを用いる方法では矛盾が生じないような改ざんも行うことができる。これに対して、ハッシュ関数では基本的にこのようなことを行うことができない。
## まとめ
個人的に気になったことを簡単にまとめた。誤り検出に関しての知識がほとんどないので、これを機に理解を深めたい。
# 誤り検出方法 Checksumについて
# Capybaraで壊れにくいテストを書くために気を付けていること
この記事はSmartHR Advent Calendar 2019 2日目の記事です。
SmartHRではRuby on Railsを広く採用しています。アプリケーションを長期的にメンテナンスしていくためにテストは欠かせません。特にReact.jsなどを用いた複雑なUIにおいては、単なるAPIのテストやモデルのテストだけではなく「実際にブラウザを操作して、ユーザーが期待する結果を得られるかどうか」をテストすることが重要です。
Rubyではこのようなブラウザを操作するテストを書くために、Capybaraという便利なフレームワークがあり、比較的簡単にテストを書き始めることができます。ただ、この手のテストは保守が大変であったり、手間の大きさからテストが追加されなくなったり、ということがよくあります。本記事では、私がこれまでの経験から学んだ、壊れにくいテストを書くためのTipsを紹介します。
なお、特に説明のない限り、ここではCapybara + RSpec + Selenium + Chrome (Headless)の環境を想定しています。
sleepしない
出オチっぽいですが、非常に重要です。非同期なリクエストの結果を待っているときや、時間差でレンダリングされる画面など「いい感じに少し待って」と言いたくなる状況は確かにあります。
そういった場合に、単に
sleep 5などとしてしまうと
- テストを実行する環境によって適切な待ち時間が異なるため、落ちたり落ちなかったりするテストが生まれやすい
- テストが落ちたときに、適当に待ち時間を伸ばされることが多く、テストの実行時間が伸びがち
などの問題があります。このような場合では、何を待っているかを短いスパンで定期的にタイムアウトまで待つようなヘルパーメソッドを定義して、それを利用するようにしましょう。it "何かのアクションの結果、Successメッセージが帰ってくる" do click_button "何かのアクション" finally do expect(page).to have_content "Success!" end end def finally(timeout: Capybara.default_max_wait_time) start = Time.now begin yield return rescue RSpec::Expectations::ExpectationNotMetError, Capybara::ElementNotFound raise if Time.now > start + timeout sleep 0.1 retry end end
## XPathやclassに依存しない
Capybaraでは
have_buttonや
fill_inなど基本的なHTMLの要素に対するヘルパーが定義されているため、素直な画面に対しては比較的読みやすいテストを書くことができます。
しかし、現実には画面が複雑な構造になっていることが多く、これらのヘルパーだけでは力不足であることはよくあります。そういったときに、XPathやclassに依存したテストを書いてしまうこともきっとよくあるでしょう。find('form active-form button').click expect(page).to have_xpath '//*[@id="form"]/div[2]'
しかし、これでは後からテストだけ見たときに、何をテストしているのかわからなくなってしまいます。例えば、あなたが画面を大幅に弄った後に、「よーし、テスト直すかー」とこのテストを見たら... きっとこのテストごと消えてしまうことになるでしょう。
このような悲劇を産まないためにも、テストは後から読めるように、XPathやclassに依存しないことをおすすめしています。とはいえ、素直にヘルパーが利用できない画面というのは当然ありますから、話はそんな簡単ではありません。こういった場合にはデータ属性を利用し、さらにそれを指定するヘルパーメソッドを生やすと良い感じになります。click_form_button expect_to_have_error_messagedef click_form_button find(spec_selector('active-form-button')).click end def expect_to_have_error_message expect(page).to have spec_selector('active-form-error-message') end def spec_selector(name) "[data-spec='#{name}']" end
データ属性は他の用途に利用されることがなく、自由に目印をつけられるので、テストを見たときに何を指しているかわかりやすく、HTML側の編集時にも目を引く良い方法です。適切な単位でデータ属性を割り当てていれば「なぜかわからないけどdivをひとつズラしたらテストが通らなくなった」といった問題も起きにくくなるでしょう。
withinを活用する
この記事をテストすると仮定して、「本文の書き出しに"Capybara"というリンクが含まれていること」をテストするとします。expect(page).to have_link "Capybara", href: "
これでもテストは通りますが、これでは「本文の書き出しの中に」という重要な条件が抜けてしまっています。例えば、末尾の参考文献に"Capybara"を含むリンクを追加した途端、本当にテストしたかった書き出しのリンクが消えても、テストが通る状態になってしまいます。
他にも「保存」ボタンをクリックしたい状況があるとして、同じ画面中にいくつも「保存」ボタンがあると、単に
click_button "保存"では、Ambiguous matchを引き起こしてしまいます。
match: :firstや
allしてアクセスする方法もありますが、あまり良い方法ではありませんよね。click_button "保存", match: :first # firstって何？ all('button', text: "保存")[1].click # うーん...
こういった場合では、
withinによるスコープの絞り込みが役に立ちます。it "書き出しにリンクが含まれる" do within_introduction do expect(page).to have_link "Capybara", href: " end end it "ヘッダーの保存ボタンをクリック" do within_header do click_button "保存" end end def within_introduction within(spec_selector("introduction")) { yield } end def within_header within(spec_selector("header")) { yield } end
データ属性を使ったヘルパーメソッドの定義と合わせると、随分と読みやすく感じるはずです。
## ユーザーの目に見えないもの（気にしないもの）をテストしない
これは書き方というか、心構えの問題だと思うのですが、基本的にデータベースの中身だったり、DOMの構造など「ユーザーが意識しないもの」はテストするべきではない、と考えています。例えば、こんなテストです。visit edit_user_path(user) fill_in "名前", with: "新しい名前" click_button "保存" expect(user).to have_attribute(name: "新しい名前")
もちろん、こういったテストを書かざるを得ない状況というのもあると思うのですが、可能な限りユーザーの体験をテストしたいので、ユーザーが知ることができないデータベースの値をテストするのは望ましくないでしょう。実際にユーザーが更新済みの値を見ることができる画面でテストするべきです。visit edit_user_path(user) fill_in "名前", with: "新しい名前" click_button "保存" expect(page).to have_current_path user_path(user) expect(page).to have_content "新しい名前"
ボタンクリックなどの操作も同様です。ユーザーは「divタグの３番目の中のボタンをクリックするぞ！」とクリックすることはありませんよね。
withinなどと組み合わせて「新着メニューの中にあるボタンをクリックする」というように表現すると、後から見た時に読みやすくなります。# Bad all('button', text: "詳細")[2].click # Good within_new_menu do click_button "詳細" end
## ブラウザのサイズを大きくする
当たり前のことだからしれませんが、あまり言及されている印象がないので書いておきます。ブラウザのサイズは大きければ大きいほどいいです。Capybara.register_driver(:chrome_headless) do |app| options = Selenium::WebDriver::Chrome::Options.new(args: [ "window-size=3000,3000", "headless", "disable-gpu", ]) Capybara::Selenium::Driver.new(app, browser: chrome, options: options) end
ブラウザのサイズが小さい場合、別の要素が被ってくることによって、テストが落ちるなどの問題が起きることがあります。もちろん、ブラウザサイズが小さい画面でテストをしたい状況もあるので、必ずしもこの手が使えるわけではないのですが、特に理由がないならば、ある程度大きく設定しておくことをおすすめします。
## 簡単にブラウザを起動できる環境を用意する
CIでテストを回すことを考えると、ヘッドレスモードでChromeを動かすことになると思いますが、開発中やテストを書いている段階では、実際にブラウザが立ち上がって操作しているところを見れる方がテンションもあがりますし、問題を特定しやすくなります。
個人的には、環境変数でdriverを簡単に切り替えられるようにしておくと、さっとブラウザを起動してテストが落ちた原因を探ることができるので便利です。Capybara.configure do config.default_driver = ENV['FOREGROUND'] ? :chrome : :chrome_headless end
## おわりに
ブラウザを操作するテストはコストが高く、メンテが難しい、という意見をよく聞きますが、個人的にはコツを抑えて書けば、もっとうまくできるのではないかと思っています。
SmartHRもまだ十分と言える状況ではありませんが、QAチームとも協力しながら、うまくテストを増やしていきたいところです。
# 【ruby】エイリアスメソッドの生成/メソッドの削除 /クラスのネスト
## エイリアスメソッドの定義
独自に作成したクラスもエイリアスメソッドを定義することができる。
alias 新しいメソッド名 既存メソッド名とすればokclass User def hello 'hello' end alias greeting hello end user = User.new p user.hello p user.greeting
## メソッドの削除
undef 削除したいメソッド名とする
## ネストしたクラスの定義
クラスの内部に定義したクラスは次のようにして参照できる
外側のクラス::内側のクラスclass User class BloodType attr_reader :type def initialize(type) @type=type end end end blood_type = User::BloodType.new('B') p blood_type.type #=>B
## 演算子の挙動を独自に再定義する
rubyでは=で終わるメソッドを定義できる
=で終わるメソッドは変数に代入するような形式ででそのメソッドを呼ぶことができるclass User #=で終わるメソッドを定義する def name=(value) @name=value end end user=User.new #変数を代入するような形でname=メソッドを呼び出せる p user.name="Alice" #=> "Alice"
## ==を再定義する
次のようなコードがあったとする
rb:
a=Product.new('A-0001','A GREAT MOVIE')
b=Product.new('B-0001','A AWSOME MOVIE')
c=Product.new('A-0001','A GREAT MOVIE')
同じ商品コードであれば同じ商品であると判別したい
こうなってほしいa == b #=> false a == c #=> true
しかしどちらも結果はfalseになります。
なぜならスーパークラスのobjectクラスでは==はobject_idが一致したときにtrueを返す。
なにもしないとこうなるa==b #=> false a==c #=> false
本当は次のようになってほしい
a==b #=> false
a==c #=> true
# C++で作るRuby拡張
## はじめに
この記事はRuby Advent Calendar 2019の8日目の記事です。
C++でのRuby拡張実装について、つらつらと書いている記事になります。
内容としてはTataraというRubyで型を使えるライブラリを作ってみたで紹介した自作Ruby拡張を作るにあたって得たC++でのRuby拡張実装知見の記事になります。
## Ruby拡張って？
皆さんが普段使っているRuby(ここではCRubyのことです）はCによって実装されています。ですので、Cを使ってRubyの拡張機能を作成することもできます。
つまり、Cで既に作成されているライブラリなどをRuby拡張として作成することができるというメリットがあります。CでRuby拡張を実装した場合、Rubyで実装するよりも高速に処理できるケースもあるようです。
実際にCで拡張機能が実装されているgemとしてはsqlite3やmysql2などがあります。
またRustやC++でRubyの拡張機能を作成するケースもあります。
例えば最近面白いなぁと思ったのはRustでのRuby拡張を実装できるHelixですね。Rustを使うことでCやC++よりも安全にRuby拡張を書くことができます。
また実装コード自体もかなり読みやすく以下のようなコードでクラスとメソッドを実装できます(※ HelixのREADMEより引用)。ruby! { class Console { def log(string: String) { println!("LOG: {}", string); } } }
ただHelix公式のチュートリアルではRails向けに拡張機能を実装する内容になっています。そのためRuby向けの拡張を作成する際のドキュメントがあまりなく、少し辛いところがあります。
実際にHelixでRuby拡張を作成しているものとしては以下の記事などがあります。
ref: RubyからRustを呼び出すいくつかの方法のまとめ - Qiita
ref: Rustでgemを書く際のハマりどころ in 2017
またC++ではRiceやExt++などのRuby拡張を実装できるライブラリも存在しています。
RubyKaigi 2017ではImprove extension API: C++ as better language for extensionにてC++でのRuby拡張実装について紹介されています。
興味のある方はそちらも確認してみると良いでしょう。
今回はC++でのRuby拡張の実装方法について解説します。具体的にはRiceやExt++、C++のみでの実装方法などを解説していきます。
## つくるもの
今回は、
Helloというクラスを作成し、
Hello Ruby Extension!と画面に表示する
sayというメソッドを実装します。
具体的には以下のようなコードが実行できるRuby拡張を実装していきます。require 'hello' Hello.new.say # => "Hello Ruby Extension!"
今回はRice、Ext++、C++でそれぞれ実装していきます。
今回の記事作成にあたって各ライブラリでの実装サンプルをGitHubに上げておきました。興味のある方はこちらも見ると良いかも。
## 実装
## Riceでの実装
## Riceとは？
Riceとは、C++を使ってRuby拡張を簡単に作成できるライブラリになります。
RiceはgemとしてRubyGemsからインストールすることができます。
gem install rice
これでRiceが使えるようになります！
ちなみに、実際にRiceを使ったサンプルコードは以下のようになります。#include <iostream> #include <rice/Data_Type.hpp> #include <rice/Constructor.hpp> using namespace Rice; class Hello { public: Hello() {}; void say() { std::cout << "Hello Ruby Extension!" << std::endl; }; }; extern "C" { void Init_hello() { Data_Type<Hello> rb_cHello = define_class<Hello>("Hello") .define_constructor(Constructor<Hello>()) .define_method("say", &Hello::say); } }
このようにRiceを使う場合、非常に簡単にRuby拡張を作ることができます。
またC++のテンプレートなどを使って以下のようなコードを書くこともできます。template <class T> class CppArray { public: CppArray<T>() {}; }; Data_Type<CppArray<int>> rb_cIntArray = define_class<CppArray<int>>("IntArray") .define_constructor(Constructor<CppArray<int>>());
Riceを使うメリットととしては、非常に簡単にC++でのRuby拡張を作ることができる点ですね。C++のライブラリなどをRubyで使えるようにするラッパーなどは、Riceを使って実装するといいかもしれません。
デメリットとしては、日本語のドキュメントもあまりないことと、開発自体があまり活発でない印象があることですね。
日本語で書かれた記事はあまり（Rice以外での実装とかはあったりする）なく、IBMのRice を使用して Ruby の拡張機能を C++ で作成するが日本語で唯一詳しく書かれたRiceのチュートリアルになりそうです、
英語が読める方であれば、こちらのドキュメントを読み解けばよいかと思います。
GitHubのリポジトリでのコミットログなどを見るた印象ではあまり開発が活発な印象はないです。最近、いくつかPull Requestが取り込まれてはいるようですが……。
そのため、Rice側の開発が打ち切られると辛いことになりそうな気配がありますね……。
とはいえ、大きな変更が入る可能性は少ないのでとりあえずC++でのRuby拡張を作る分には良いライブラリだと思います。
それでは、Riceを使ってRuby拡張を実装してみましょう。
なにはともあれ、Riceをインストールしましょう。
インストールが無事終了した後は、
extconf.rbというファイルを作成します。これはC++のコードをビルドする
Makefileを自動生成するためのファイルになります。CでRuby拡張を作る場合も同様に
extconf.rbを作成します。extconf.rbrequire 'mkmf-rice' create_makefile('hello')
mkmf-riceはRiceを使ってかかれたC++のソースをもとに
Makefileを作成するためのライブラリになります。ちなみに、Cで拡張機能を実装する場合は
mkmfというライブラリを読み込んで
Makefileを自動生成していますね。
また
create_makefileに渡している文字列がビルドされた拡張ライブラリの名前になります。
次に、
hello.cppを
軽くコードの解説をすると、以下の二行でRiceのヘッダーを読み込んでいます。
Riceでは
Data_Typeを使い、既存のクラスをもとにRuby向けにコンバートしています。Data_Type<Hello> rb_cHello = define_class<Hello>("Hello")
上記のコードではC++で定義した
HelloクラスをRubyで呼び出す
Helloというクラスに変換しています。.define_constructor(Constructor<Hello>())
.define_constructor(Constructor<Hello>())ではC++で定義した
Helloクラスのコンストラクタ(Rubyでいうところのinitializeのようなもの）を使って、Rubyで
Helloクラスのインスタンスを作成できるようにしています。
つまり、Rubyのinitializeを実装しています。
最後に
.define_method("say", &Hello::say);で
sayというメソッドを
Helloクラスに追加しています。.define_method("say", &Hello::say);
これでC++側での実装は完了です。
extconf.rbを実行して
Makefileを生成します。
ruby extconf.rb # => Makefileを自動生成
あとは、
makeコマンドでビルドすれば
hello.oと
hello.soが生成されていると思います。
make # => hello.o と hello.so が生成される
最後に、作成したRuby拡張を実際に動かしてみましょう。
hello.rbを以下のように作成して実行してみましょう。hello.rbrequire './hello.so' Hello.new.say
ruby hello.rb # => Hello Ruby Extension!
Hello Ruby Extension!と表示されていればOKです！
## Ext++での実装
## Ext++とは？
Ext++はRice同様にC++を使って、Ruby拡張を作成できるライブラリです。
Ext++もRubyGemsで配布されているのでgemとしてインストールできます。
Ext++での実装は以下のようになります。#include <iostream> #include <ruby.hpp> RB_BEGIN_DECLS void Init_hello() { rb::Class klass("Hello"); klass.define_method("initialize", [](VALUE rb_self, int argc, VALUE *argv) { return Qnil; }); klass.define_method("say", [](VALUE rb_self) { std::cout << "Hello Ruby Extension!" << std::endl; return Qnil; }); } RB_END_DECLS
Ext++ではC++のラムダ式を引数に渡して実装することができる点が特徴的です。そのためラムダ式をうまく使うことでRubyのメソッドとC++の実装を一度に書くことができます。
また、Ext++では
ruby.hppをインクルードするだけで良いところも便利です。Riceの場合、必要なヘッダーを個別に読み込まなければならず
Riceではラムダ式を使ってメソッドの定義などはできないため、ラムダ式でメソッドを定義したい人はExt++を使うと良いかもしれません
Ext++を使うメリットとしては、実装が一か所で済む点かなと思います。また開発者が日本の方(というか @kou さん）ですので開発者本人にあって話が聴けるという点もメリットかもしれません。
デメリットとしては、サンプルのコードが一つしかなく、人によってはどのように実装を進めていけばいいのかが分かりにくい時がある点でしょうか？その点に関しては今後Pull Requestなどでサンプルコードを投げれたらと思っていますね。
また開発バージョンであり、今後のバージョンアップでは大きな変更も入る可能性もありそうです。
しかしながら、開発者本人に直接話を聞くことができそう（日本人からすると）なので採用するメリットはかなり大きいと思います。
またRiceと違い、Rubyの実装自体に近い実装コードを書くのでCRubyの実装を学んでみたいという人にもオススメかもしれませんね。
それでは、Ext++を使ってRuby拡張を実装していきましょう。
まずはExt++をインストールします。
インストール完了後、Riceでの実装の時と同じように
extconf.rbを作成します。extconf.rbrequire 'extpp' create_makefile('hello')
Riceの時とおおよそ同じコードですね。違う点としては
mkmf-riceではなく、
extppを読み込んでいます。
extconf.rbと同じ階層に作成します。hello.cpp#include <iostream> #include <ruby.hpp> RB_BEGIN_DECLS void Init_hello() { rb::Class klass("Hello"); klass.define_method("initialize", [](VALUE rb_self, int argc, VALUE *argv) { return Qnil; }); klass.define_method("say", [](VALUE rb_self) { std::cout << "Hello Ruby Extension!" << std::endl; return Qnil; }); } RB_END_DECLS
Ext++では
rb::Classで新しいクラスを作成します。また、作成した
klassと
define_methodを使うことで必要なメソッドを新しく定義しています。
QnilはRubyでの
nilを返しています。CRubyのメソッドなどで
nilが返ってきているメソッドでは、子のように
return Qnil;と書かれています。興味のある方はRuby Hack Challenge Holidayに参加したり、GitHubのruby/rubyのコードを読んでみると良いかもしれません。
あとは。
extconf.rbを実行し、
ruby extconf.rb # => Makefileが生成される
その後、
makeで作成したRuby拡張をビルドします。
make # => hello.o と hello.soが生成される
hello.rbを以下のように作成し、実行してみましょう。hello.rbrequire './hello.so' Hello.new.say
## C++での実装
最後にC++でのみで作成するRuby拡張について紹介します。
まずは
extconf.rbを作成します。extconf.rbrequire "mkmf" create_makefile("hello")
mkmfはCRubyに添付されているRuby拡張のためのMakefile作成ライブラリですね。
hello.cppを以下のように作成します。#include <ruby.h> #include <iostream> class Hello { public: Hello() {}; ~Hello() {}; void say() { std::cout << "Hello Ruby Extension!" << std::endl; }; }; static Hello* get_hello(VALUE self) { Hello *ptr; Data_Get_Struct(self, Hello, ptr); return ptr; } static void wrap_hello_free(Hello *ptr) { ptr->~Hello(); ruby_xfree(ptr); } static VALUE wrap_hello_alloc(VALUE klass) { void *ptr = ruby_xmalloc(sizeof(Hello)); ptr = std::move(new(Hello)); return Data_Wrap_Struct(klass, NULL, wrap_hello_free, ptr); } static VALUE wrap_hello_init(VALUE self) { return Qnil; } static VALUE wrap_hello_say(VALUE self) { get_hello(self)->say(); return Qnil; } extern "C" { void Init_hello() { VALUE rb_cHello = rb_define_class("Hello", rb_cObject); rb_define_alloc_func(rb_cHello, wrap_hello_alloc); rb_define_private_method(rb_cHello, "initialize", RUBY_METHOD_FUNC(wrap_hello_init), 0); rb_define_method(rb_cHello, "say", RUBY_METHOD_FUNC(wrap_hello_say), 0); } }
ポイントとしては
#include <ruby.h>でRuby拡張の実装で使用するマクロや関数などを呼び出している点ですね。これがないとRuby拡張を実装することができません。
get_hello関数はRubyのインスタンスを引数に受け取って、C++のインスタンスのポインタを返しています。この関数を使うことでC++のクラスのメソッドをラップ関数から呼び出して使うことができるようになります。
wrap_hello_free関数はRubyのGCが呼び出された際にメモリから解放する際の処理がかかれた関数になります。
wrap_hello_allocはインスタンスを作成する際のアロケータになります。
wrap_hello_initはRubyでの
initializeになりますね。
makeを実行してビルドしてみましょうruby extconf.rb # => Makfileが生成される make # => hello.o と hello.so が生成される
最後に、
hello.rbを以下のように作成して実行しましょう。hello.rbrequire './hello.so' Hello.new.say
C++でのRuby拡張についてRice、Ext++、C++それぞれでのでの実装を紹介しました。意外と簡単そうと思っていただければ幸いです。
あと今回の記事ではC++をベースに紹介しましたが、もちろんCでの実装を行う方法もあります。むしろ、そちらのほうが参考になる記事が多いので、Ruby拡張を作る際にはCで作ると良いかもしれません。
あと、この記事でRubyの実装に興味を持たれた方はRuby Hack Challenge Holidayなどに参加してみると良いかもしれません。
意外と簡単にC++でもRubyの拡張機能を作ることができるので、今後もC++の良さげなライブラリなどをRuby向けに実装していきたいと思います。
## 参考記事
ref: Rice
ref: Ext++
ref: Improve extension API: C++ as better language for extension
ref: Rice を使用して Ruby の拡張機能を C++ で作成する
ref: Rice - Ruby Interface for C++ Extensions
ref: ruby/ruby
ref: C++言語で簡単なRuby拡張ライブラリを書いてみた
ref: Rubyの拡張ライブラリの作り方
ref: Rubyソースコード完全解説
ref: TataraというRubyで型を使えるライブラリを作ってみた
# rails独学でポートフォリオを作成中
こんにちは！現在転職活動中のバスケンです。
初めての投稿です！
railsの独学を初めて3か月が経ちました。
この3か月間の私の学習経過は下記になります。
1、progateでhtml,css,ruby,ruby on rails,githubを1周
2、ruby on rails チュートリアル2週
3、railsでオリジナルのポートフォリオを作成途中
今日は
3にて私が作成したポートフォリオの 概要を説明します。
## ポートフォリオ「KuiShare」の概要
私は人の
後悔を聞いたとき似たような 後悔が自分に起きないように気をつけるよにします。
このことから、みんなとたくさんの
後悔が共有できればおのずと注意深くなり先々で自分に起きうる 後悔を減らすことができるのではないかと考えました。
みんなと気軽に後悔を共有することができるのが今回作成した
「KuiShare」です。
「KuiShare」のURL「
「KuiShare」でできること
・後悔したことを投稿/編集/削除
・後悔を共有したいユーザーをフォローする機能
★人の後悔にコメントをする機能
★人の後悔に『ドンマイ』(instagramでいういいね！)をつける機能
・プロフィールの編集機能
利用している技術
・S3(画像置き場)
★印をつけている機能はこれから追加しようとしている機能です。
今回は初投稿でしたのでただ自分の学習経過を記しただけになってしまい申し訳ございません！！
明日からはちゃんとした技術ブログを投稿していきたいと思います！
# RubyでDBMSを実装 字句解析(2日目)
この記事は RubyでDBMS Advent Calendar 2019 の2日目の記事です。
## 本日の概要
初日は特に中身のないechoサーバとクライアントで終わってしまいましたが、
本日から実際にサーバに渡ってきた文字列をSQLとして解析していきます。
本日はその中でも前半のフェーズである字句解析を実装します。
実装はこちらのGitHubリポジトリに置いてあります。
## 字句解析とは
字句解析とは、ただの文字列をそれ以上分割できない意味のある最小単位(トークン)に分割する処理です。
例として、以下のSQLを想定した文字列をトークンに分割してみます。SELECT id,email FROM users WHERE id=5;
文字列 種別 値 SELECT select keyword - id string literal id , comma - string literal FROM from keyword - users string literal users WHERE where keyword - id string literal id = equal - 5 numeric literal 5 ; semicolon
(種別の部分は筆者の勝手な命名となります)
一部トークン(リテラル)には値も含まれます。
とりあえず、classとして切り出しておきます。rbdb/query/token.rb# frozen_string_literal: true module Rbdb module Query class Token attr_reader :kind, :value def initialize(kind, value = nil) @kind = kind @value = value end end end end
## クエリ設計
では、早速分割処理を実装していきたいところですが、
今後の初期開発でサポートする最低限のクエリを決めておきます。
- 型はint, varcharのみ。
- 制約などは含まず。
- CREATE文でデフォルト値を指定できないので、全カラムを指定するもののみ。
- * による全カラム指定と一部カラムのみ指定どちらも可能。
- WHERE句あり。
- 条件部分は単純な
operand (=|<>|<=|>=|<|>) operandのようなもののみ。
## 解析処理
大分長いメソッドや、ネストが激しくなってしまいましたが………
StringIOクラスを用いて、先頭から一文字ずつ走査していきます。
'や
(など一文字目でそのトークン種別を決定できるものもある一方で、
<は、
<>や
<=,
<など複数の可能性があるため、
さらに文字の読み込みを進めることで判定します。
また、backというメソッドを用意して、不要な読み込みをしてしまった場合一文字前に戻るようにしています。
アルファベットから始まる文字列は、予約語に当てはまるかをチェックした上で、それ以外を文字列リテラルと解釈します。
## 動作確認
1日目でそのままechoしていたクエリの代わりに、
分割したTokenクラスをinspectしてそのままレスポンスで返してみます。rbdb/server.rbtokens = Rbdb::Query::Lexer.new(query).scan # TODO: tokens.each do |token| res.body += token.inspect res.body += "\n" end
大丈夫そうですね!
筆者は自作コンパイラなどに手を出したこともないので、
今回初めて字句解析を実装したのですが、なかなか思ってたよりも愚直な処理になりますね。
明日は分割したもののただの配列でしかないTokenたちをSQLの構文として解析していく予定です。
# Rails × CircleCI × ECSのインフラ構築
簡単なDocker RailsアプリをECSを利用して本番環境に上げるまでのまとめ
＊ あくまで参考に(実務でそのまま利用できるほどしっかり構築しておりません)
## 前提知識
ECSとは？クラスターとは？サービスとは？タスクとは？って人は
ECSの概念を理解しよう
などを読んでください。
## Railsアプリ作成
まずはローカルでRailsアプリを作成しましょう。
機能は簡単なものでいいので、scaffoldなどを利用してサクッと作成してしまいましょう。
脳死で作成したい人は下記をご覧下さい。
Docker Rails Sampleアプリ構築 - Qiita
## AWS上で利用するリソースの作成
コンソール上(or Terraformなど)からあらかじめ作成しておくべきものになります。
## IAMロール・ポリシーの作成
ECSで運用するための必要なIAMロール・ポリシーを作成していきます。
ちなみにポリシーとは、ロールに付与される権限情報です。なのでポリシーのないロールは何も権限がない状態なのでまずはポリシーを作成してロールを作成していきましょう。
## ポリシーの作成
## 作成手順
- IAMページに行って、サイドバーの「ポリシー」選択
- 「ポリシーの作成」ボタン押下
- JSONタブを開いて下記に記載したJSON内容をコピペして、「ポリシーの確認」押下
- それぞれのポリシー名を入力する
下記の4つのポリシーを作成する。
## ロールの作成
IAMページに行って、サイドバーの「ロール」→「ロールの作成」より下記のロールを作成する。
作成後、各ロールのページにて「ポリシーをアタッチする」を押下して上記で作成したポリシーを紐づける。
- ecsInstanceRole（→AmazonEC2ContainerServiceforEC2Roleに紐づける）
- AWSServiceRoleForECS（→AmazonECSServiceRolePolicyに紐づける）
- ecsTaskExecutionRole（→AmazonECSTaskExecutionRolePolicy,AmazonSSMReadAccessを紐づける）
## ALBの作成
ECSのサービス作成時にALBを登録しておけば、コンテナに動的にポートマッピングをしてくれるようになるので楽になります。
- Application Load Balancerを選択
- 名前を入力。サブネットを二つ選択。（ない場合は、適宜作成）
- セキュリティグループを選択。（ない場合は、適宜作成）
- ターゲットグループを選択or作成
- ターゲットグループにインスタンスを登録
## クラスターの作成
ECSのサイドバーにある「クラスター」から「クラスターの作成」ボタンを押下
「クラスターテンプレートの選択」は「EC2 Linux + ネットワーキング」を選択
1. クラスター名記載
2. EC2インスタンスタイプの選択（お好み）
3. キーペア（お好み。ただし、デバッグ時にSSHできた方がいいので設定しておくことをおすすめ）
4. コンテナインスタンスの IAM ロールに「ecsInstanceRole」を選択
## RDSの作成
aws-cliでのRDS作成。
参考
AWS CLI を使って RDS を作成する (自分用メモ) - Qiita
AWS-CLI Amazon Aurora インスタンス作成 - Qiita
## AWS Systems Managerの設定
AWS Systems Managerは、タスク実行時にコンテナに注入する秘匿情報（環境変数）の管理に使えるAWSサービスです。
初めての人は設定の仕方を含め、
ECSでごっつ簡単に機密情報を環境変数に展開できるようになりました！
を見れば大体分かると思います。
AWS Systems Managerの左側メニューから「パラメータストア」→「パラメータの作成」をクリック。パラメータの詳細画面が表示されるので、パラメータのキー名と値を入力します。タイプには「安全な文字列」を選択します。
パラメータのキー名と値一覧
キー名 値 /production/database_username [RDSに設定したusername] /production/database_password [RDSに設定したpassword] /production/database_host [RDSインスタンスのエンドポイント]
RDSインスタンスのエンドポイント（RDS→データベース→[インスタンス名]）
## CircleCIに設定する環境変数
CircleCIのプロジェクトの設定ページ（Settings→[アカウント名or組織名]→[プロジェクト名]）に行き、下記の画像の箇所から設定する

環境変数名 値 AWS_ACCESS_KEY_ID [AWSのアクセスキーID] AWS_ACCOUNT_ID [AWSのアカウントID] AWS_DEFAULT_REGION [AWSのデフォルトリージョン] AWS_ECR_REPOSITORY_URL [AWSのECRリポジトリURL] AWS_SECRET_ACCESS_KEY [AWSのシークレットアクセスキー] RAILS_MASTER_KEY [config/master.keyの値] TARGET_GROUP_ARN [ターゲットグループのarn]
## Task definitionの作成
## docker-compose.ymlrails-sample/ecs/production/app/docker-compose.ymlversion: "3" services: app: image: [ECRのリポジトリURI] entrypoint: bundle exec unicorn -c config/unicorn.rb env_file: - ../env working_dir: /projects/rails-sample logging: driver: "awslogs" options: awslogs-region: "ap-northeast-1" awslogs-group: "rails-sample-production/app" awslogs-stream-prefix: "rails-sample-app" nginx: image: [ECRのリポジトリURI] ports: - 0:80 links: - "app:app" env_file: - ../env working_dir: /projects/rails-sample logging: driver: "awslogs" options: awslogs-region: "ap-northeast-1" awslogs-group: "rails-sample-production/nginx" awslogs-stream-prefix: "rails-sample-nginx"
＊ Nginxの設定ファイルは適宜用意してください。上記のnginxの欄にnginx設定ファイル群の設置・起動用のスクリプト
entrypoint: /bin/bash /etc/nginx/start.shを用意するなど。
タスク実行時に実行ロールの指定やコンテナに注入する環境変数をAWS Systems Managerから取得するして設定するためのファイルrails-sample/ecs/production/app/ecs-params.ymlversion: 1 task_definition: # タスク実行時のロールを指定 task_execution_role: ecsTaskExecutionRole services: # 起動するコンテナを記載(app, nginx) app: # 何らかの理由で失敗・停止した際に、タスクに含まれる他のすべてのコンテナを停止するかどうか(デフォルトはtrue) essential: true # AWS Systems Managerから秘匿情報を取得してコンテナに環境変数を注入 secrets: - value_from: /production/database_username name: DATABASE_USERNAME - value_from: /production/database_password name: DATABASE_PASSWORD - value_from: /production/database_host name: DATABASE_HOST nginx: essential: true run_params: network_configuration: awsvpc_configuration: assign_public_ip: ENABLED
## コンテナ全体に注入する環境変数の設定
各環境(production, stagingなど)ごとのディレクトリ以下に
envファイルを用意してそこに記載する# ここのファイルに追加した環境変数は全てのコンテナに展開されます # Rails APP_HOST=54.238.241.230 RAILS_ENV=production RAILS_LOG_TO_STDOUT=1 RAILS_SERVE_STATIC_FILES=1 # RDS DATABASE_NAME=rails-sample_production DATABASE_PORT=3306 DATABASE_POOL=10 # Unicorn UNICORN_PORT=23380 UNICORN_TIMEOUT=180 UNICORN_WORKER_PROSESSES=2 # Nginx専用 NGINX_APP_SERVER_NAME=app NGINX_APP_SERVER_PORT=23380 NGINX_DOCUMENT_ROOT=/projects/rails-sample/public NGINX_FRONT_SERVER_NAME=54.238.241.230
## 構築の際に詰まる可能性のあるポイント
## ECSコンテナインスタンスの作成
- インスタンスへのIAMロールを付与すること
- ecs-agentのインストール ( Amazon ECS コンテナエージェントのインストール - Amazon Elastic Container Service )
- EC2インスタンスの
/etc/ecs/ecs.configに
CLUSTER_NAME=クラスター名の登録
- 所属するクラスターを変更する場合、
/var/lib/ecs/data/ecs_agent_data.jsonを削除してからecs-agentを再起動する
Defaultクラスター作成しているし、IAMロールにecs:CreateClusterの権限付与されているから自動で作成なんかもしてくれるのかと思ったら作成してくれなかった。
なので、クラスター作成→インスタンス作成の方が良い（ちな、クラスター作成時にインスタンスも作成するようにはできるっぽい）
→カスタマイズされてるAMI利用時のみ初期スクリプトによってDefaultクラスターを作成しているのかもしれない
Amazon ECS コンテナインスタンスの起動 - Amazon Elastic Container Service
## インスタンスタイプについて
ある程度余裕持たないとタスク実行するための容量を持たなくて死ぬ
（ほんとは、ローカルや本番環境で動かした時の使用量見てタスク実行に必要なメモリを設定した方が良い）
## ecs-cliでのタスク実行
ecs-params.ymlファイル内で
task_execution_roleを指定すること
task_execution_roleで指定した適切なポリシーを適用したIAM Roleを用意すること（エラーが出なくて、単純に実行されないので気づきにくい）
ECSについてググればたくさん記事出てくるのですが、実際に活用しようとしてみるとたくさん落とし穴があります。もし利用しようか考えている人は一度デモアプリで利用してみることをお勧めします。
## 最後に
UUUMではインフラに詳しいエンジニアを欲しています。
詳しくはこちら →→→→→→ UUUM攻殻機動隊の紹介
# Docker Rails Sampleアプリ構築
適当なRailsアプリを作成するのに
脳死で作成する
## 前提
- Ruby 2.6.5
- Railsバージョン6.0.1
- MySQL 5.7
- Node.js 8系
defaultに
- port: 3306
を追記
productionは
- database: <%= ENV["DATABASE_NAME"] %>
- username: <%= ENV["DATABASE_USERNAME"] %>
- password: <%= ENV["DATABASE_PASSWORD"] %>
- host: <%= ENV["DATABASE_HOST"] %>
- port: <%= ENV["DATABASE_PORT"] %>
を全部環境変数に変更$ docker-compose build $ docker-compose run --rm web rails db:create
ScaffoldでUserモデル作成$ docker-compose run --rm web rails g scaffold user name:string age:integer
マイグレーションして、コンテナを立ち上げる$ docker-compose run --rm web rails db:migrate $ docker-compose up -d
＝＞  にアクセスして確認
# つくってまなぶ★DES☆
この記事は信州大学kstmアドベントカレンダー2019の三日目の記事です。
去年と同様に
~~人不足のため~~わたくし @arsley の1/2回目の記事となります。よろしくお願いします。
## 導入
本記事では私がゼミにて勉強した DES (Data Encryption Standard) とかいう「暗号化方式の1手法として聞いたことはあるけど、どういうものか知らない」という技術について、
~~得た知識をすぐに他者に伝えマウントをとってしまう若き頃のような気持ちで~~実装例を挙げながら説明します。
これを機に暗号分野にホーーーーンの少しでも興味を持っていただければ幸いです
完成品はこちら(3分間クッキング)(Ruby実装)(雑README)
「手っ取り早くどういう挙動をするものなのか知りたい」という方はご参照ください。
## DES (Data Encryption Standard)
DES (Data Encryption Standard) は共通鍵暗号方式のブロック暗号の一つです。
64ビットをブロック長、鍵も同じく64ビットで与え一連の暗号化処理により暗号文を得ます。
ただし、鍵については64ビットのうち8ビットは誤り訂正ビット（パリティビット）として扱うため、実際の鍵長は56ビットとなります。
また、暗号化に用いた
共通の鍵を用いて暗号文の復号を行うことが可能です。
共通鍵暗号は字面でわかると思うので、ブロック暗号について少しだけ補足します。
## ブロック暗号
ブロック暗号とは、その名の通りデータを固定長の
ブロックという単位に区切り、ブロックごとに暗号化を行う方式のことを指します。
共通鍵暗号におけるもう一つの暗号化方式はストリーム暗号と呼ばれるもので、1ビットもしくは1バイト単位で逐次暗号化していく方式のことを指します。
一般的にブロック暗号は、ラウンド関数と呼ばれる処理を繰り返し適用し暗号文を得る構造となっており、これをFeistel構造と言います。
DESの開発者であるHorst Feistelに由来するそうです
1。
また、暗号には平文→暗号文といった暗号化のほかに、暗号文→平文といった復号が可能である必要がある（復号可能性）のですが、このFeistel構造は逆変換が自身と同じ形になることから復号可能性を保証できるそうです。
~~不思議ですね~~
もちろんDESもこのFeistel構造により実装されています。
ちょっとわかりにくいので「逆変換が自身と同じ形になる」ということを線形代数の一次変換でお話をすると、たとえば点 $(x,y)$ から $(x^\prime, y^\prime)$ への一次変換をA = \left( \begin{matrix} 1 & 0 \\ 0 & 1 \end{matrix} \right)\left( \begin{matrix} x^\prime \\ y^\prime \end{matrix} \right) = A \left( \begin{matrix} x \\ y \end{matrix} \right)
で与えると、これの逆変換すなわち$A^{-1}$も同じであり、これが「逆変換が自身と同じ形になる」ということの意です（多分）。
## DESのDEA (Data Encryption Algorithm)
以降、転置表などのデータはこちらのサイトのものを利用させていただいています。
DESにおいて暗号文を得るまでのプロセスをDEA（Data Encryption Algorithm）と呼ぶこともあるそうです
~~初耳でした~~。
上述したように、DESはラウンド関数を繰り返し適用するFeistel構造により構成されます。
64ビット長のデータを
ブロックとして扱い、同じく64ビットを鍵として扱います（実際に使う鍵長は56ビット）。
DESは大きく分けて次の手順で暗号化を行います。
- 元々の鍵64ビットから転置・シフト演算を用いて16個のサブ鍵を取得
- ブロックの初期データに対し初期転置を適用
- (2)にて得られたデータに対しサブ鍵と共にラウンド関数へ適用
- これを16回繰り返す
- (3)にて得られたデータに対して最終転置を適用、これを暗号文とする
図にするとこんな感じです。
## 転置
中身を明らかにする前に、転置という操作について説明します。
例えば8文字の文字列
abcdefghに対して次のような数列8 7 6 5 4 3 2 1
が与えられた時、これは元の文字列を
hgfedcbaに変換することを意味します。
具体的にいうと、「元の文字列における8番目を1文字目に、元の文字列における7番目を2文字目に、元の文字列における6番目を3文字目に...置き直す（転置する）」という操作を行わせることを意味します。
## サブ鍵生成
まずサブ鍵生成について説明します。
今回鍵としては文字列
以下は文字列→二進表記変換の例です
## 転置1 PC-1
まずはじめに最初の転置 PC-1 を行います。
この転置PC-1は次の数列により表されます
3。PC-157 49 41 33 25 17 9 1 58 50 42 34 26 18 10 2 59 51 43 35 27 19 11 3 60 52 44 36 63 55 47 39 31 23 15 7 62 54 46 38 30 22 14 6 61 53 45 37 29 21 13 5 28 20 12 4
転置を行い得られたビット列は $00000000111111111111111111100000011100011000111001110000$ となります（めっちゃ整っててびっくり）。
この転置から使われているのは
8,16,24,32,40,48,56,64を除く56ビットであり、鍵長が与えたデータよりも短いことがわかるかと思います。
転置処理には
mapを用いるのが楽かなと思います
4。PC1.map { |index| key_bin.chars[index] }
## シフト演算
シフト演算はその名の通り、与えられたビット列を左方向へシフトさせるものです。
ここで用いるシフト演算は循環シフトで、上位方向へ溢れた桁は一番下位の桁へと戻るものとなります。
シフト演算の適用に際して、先ほどの転置で得られたビット列を28ビットで半分に分割し、これをC_0 = 0000000011111111111111111110 \\ D_0 = 0000011100011000111001110000
とおくこととします。
そしてこのシフト演算を
16回適用するのですが、「何回目のシフト演算か」によりシフト量が異なります。
シフト量は以下の表の通りです。
何回目? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 シフト量 1 1 2 2 2 2 2 2 1 2 2 2 2 2 2 1
が得られ、この操作を$C_{16}, D_{16}$が求まるまで繰り返します。
コード例については次の説明で示します。
## 転置2 PC-2
前述したシフト演算により得られる$C_{1\dots 16}, D_{1\dots 16}$それぞれについて、各々を結合した $C_nD_n$に対して以下の転置を適用することで
16個のサブ鍵 $K_{1\dots 16}$ を得ます。PC-214 17 11 24 1 5 3 28 15 6 21 10 23 19 12 4 26 8 16 7 27 20 13 2 41 52 31 37 47 55 30 40 51 45 33 48 44 49 39 56 34 53 46 42 50 36 29 32
各シフトにより得られる$C_n,D_n$を利用するので、シフト演算の繰り返し操作と同時に行うといいと思います
## 暗号化
サブ鍵を生成したらいよいよ暗号化のプロセスに入ります。
今回は暗号化したい文字列として
入力として与えられたブロックに対し以下の転置を適用します。IP58 50 42 34 26 18 10 2 60 52 44 36 28 20 12 4 62 54 46 38 30 22 14 6 64 56 48 40 32 24 16 8 57 49 41 33 25 17 09 1 59 51 43 35 27 19 11 3 61 53 45 37 29 21 13 5 63 55 47 39 31 23 15 7
とおきます。
コード例はこんな感じです
6。def permutate_with_ip IP.map { |index| message_bin.chars[index] } end
## ラウンド関数
それではDESの要となるラウンド関数について説明していきます。
ラウンド関数を $F$ としたとき、DESにおける処理プロセスは以下の式で表されます（$n = 0\dots 15$、$\oplus$ はXORの意)。\begin{aligned} L_{n+1} &= R_n \\ R_{n+1} &= L_n \oplus F(R_n, K_{n+1}) \end{aligned}
$n$ の範囲からわかるようにラウンド関数は
16回適用します。おそろしい。
図で表すとこのような形です。
このラウンド関数には次の処理が含まれています。
- 拡大転置Eを$R_n$に対して適用 (32ビット→48ビット)
- 対応するサブ鍵 $K_{n+1}$ と(1)の結果とのXORをとる (48ビット→48ビット)
- (2)の結果に対して
Sボックスを適用 (48ビット→32ビット)
- (3)の結果を結合したものに対して転置Pを適用 (32ビット→32ビット)
(3)のSボックスの説明は後に譲ることとして、拡大転置E・転置Pおよびサブ鍵とのXORの説明を簡単にしておきます。
## 拡大転置Eと転置P
拡大転置Eと転置Pは以下のように表されます。E,P# E 32 1 2 3 4 5 4 5 6 7 8 9 8 9 10 11 12 13 12 13 14 15 16 17 16 17 18 19 20 21 20 21 22 23 24 25 24 25 26 27 28 29 28 29 30 31 32 1 # P 16 7 20 21 29 12 28 17 1 15 23 26 5 18 31 10 2 8 24 14 32 27 3 9 19 13 30 6 22 11 4 25# E def permutate_with_e(r) E.map { |index| r[index] } end # P def permutate_with_p(transposed_r) P.map { |index| transposed_r[index] } end
またサブ鍵とのXORについてですが、RubyにおけるXORは他言語と同じく (?)
^で表現できるため以下のように書きました
9。def xor_with_key(permutated_r, key_index) r_xor_key = [] permutated_r .zip(keys[key_index]) .each { |right, key| r_xor_key << (right.to_i ^ key.to_i).to_s } r_xor_key end
あんまりいい書き方ではなさそうですね...
## Sボックス
Sボックスも今までに示した転置と同様に「与えられた入力を一定の法則に基づいて並べ換える」操作であることには変わりありません。
ただし単純な転置と異なり、
表の中から対応する数値を一つ選択しそれの2進数表記を返すというものとなっています。
この操作の前に、
ddaattaaに対し拡大転置Eを適用しサブ鍵 $K_1 = 111100001011111011100110000000011110111010101000$ とのXORをとった結果を $I = 111100001010100100011000100000011110111010101000$ とおきます（用意しておきます）。
この $I$ (48ビット)をまず6ビットごと8つに分割します。I = 111100\quad 001010\quad 100100\quad 011000\quad 100000\quad 011110\quad 111010\quad 101000
簡単のためそれぞれのビットの塊を $i_n$ としてまとめI = i_1 i_2 i_3 i_4 i_5 i_6 i_7 i_8
と表すこととします。
この各々の $i_1 \dots i_8$ 対して $S_1 \dots S_8$ というSボックスをそれぞれ適用します。
ということです。
お気づきかもしれませんが、このSボックスは
8つあります。
解説のために $S1$ のみ示します。
$S1$ row\col 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0 14 4 13 1 2 15 11 8 3 10 6 12 5 9 0 7 1 0 15 7 4 14 2 13 1 10 6 12 11 9 5 3 8 2 4 1 14 8 13 6 2 11 15 12 9 7 3 10 5 0 3 15 12 8 2 4 9 1 7 5 11 3 14 10 0 6 13
Sボックスは入力ビット列における
最初と最後を合わせたビット列を 行番号とし、残った 中間の4ビット列を 列番号とし、対応する数値の 4ビット二進表記を返す関数のような働きをします。
入力ビット列を $b_1 b_2 b_3 b_4 b_5 b_6$ と表すならば、$(b_1 b_6)_2$ 行 $(b_2 b_3 b_4 b_5)_2$ 列に相当する数値 $y$ の2進表記を返します。
$S1$ へ適用する $i_1 = 111100$ を例にとって説明すると、$10_2$ 行 $1110_2$ 列目すなわち2行14列目に相当する $5_{10}$ の2進表記 $0101_2$ を返します。
が得られます。
8つあるSボックスはここを参考にしてもらうとして、コード例はこんな感じです
## 最終転置 IPinverse 暗号文
暗号文を得る最後のプロセスとなる最終転置について説明します。
この2つを
左右を逆にして結合します。
となります。
これに対して下記の最終転置 $IP^{-1}$ を適用します。ipinverse40 8 48 16 56 24 64 32 39 7 47 15 55 23 63 31 38 6 46 14 54 22 62 30 37 5 45 13 53 21 61 29 36 4 44 12 52 20 60 28 35 3 43 11 51 19 59 27 34 2 42 10 50 18 58 26 33 1 41 9 49 17 57 25
はい。これにて
が得られました
無理やり文字化すると
@'Z4H\x12^\xB6だそうです。読めませんね。
## 復号?
最後に復号について話します。
ブロック暗号の説明でFeistel構造のはなしをしました。
Feistel構造は
逆変換が自身と同じになるという性質がありました。
そのため、
暗号化のプロセスを全て逆に行うことで平文が得られるということになります。
結論をいうと、暗号文に対してサブ鍵を $K_1, K_2 \dots K_{15}, K_{16}$ のような昇順ではなく、 $K_{16}, K_{15} \dots K_2, K_1$ のように降順にして適用することで平文が得られます。
~~不思議ですね...~~
## おしまい
あんまり「つくってまなぶ」要素がなくなってしまいましたね、残念。
まあでも数多あるプログラミング言語はどれも「目的を達成するためのツール」でしかないと思っているので、実装したいものを理解し、実装方針を立てれば自ずと作れるはずですよね...?
今回お話しした「DESをつくる」というものは車輪の再発明になってしまうものの典型ですが、これを機に「他の暗号方式はどんな実装になっているんだろう...?」というような興味をもつ一歩になってくれれば嬉しいですね
RSA暗号とか楕円曲線暗号とかになると多少の数学要素が混じってくるので厳しいものもあるとは思いますが...
あ、Gistを見てくださった方はわかると思うのですが、参考にさせていただいたサイトの数値を使ってテストも書いてあります、TDDで作ってました。
欲しい答えとか欲しい結果が明確な場合にはTDDはさいっこうにキマりますねえ、皆さんもTDDキメていきませんか?
## 参考文献
# 【Ruby | Rails】Dockerfileの中で"ADD Gemfile ~ RUN bundle install"をするのはやめませんかという話
## 今回の検証環境
2.6.5
- Docker
19.03.5
1.24.1
- Railsの設定を例にすると結構複雑になっちゃうので、今回は単純にRubyをDocker上で使用する例で解説します。
Railsを使用する場合も要点は同じなので適宜読み替えてください。
- 今回は
hogehogeディレクトリ配下で作業します。サンプルコード中の
hogehogeの部分は自由に変更して構いません。
## やめませんか
RailsやRubyのDocker環境構築の解説をしている記事で、
RUN bundle installしている記事を本当にたくさんよく見かけます。DockerfileFROM ruby:2.6.5 WORKDIR /hogehoge RUN gem install bundler # ↓こういうの↓ ADD Gemfile Gemfile ADD Gemfile.lock Gemfile.lock RUN bundle install
これ、やめませんか？
## なんでやねん
Dockerfileの中で
RUN bundle installをすることには以下のようなデメリットがあります。
- Gemfileを編集するたびに毎回
docker buildや
docker-compose buildをしないといけなくなる。
- Gemを１つ追加するだけでも全Gemをインストールし直す羽目になる。
nokogiriとかインストール遅いよね！毎回待たされるの嫌だよね！
何よりRubyistの皆さんとしては、
Gemfileを編集したら本能的に
bundle installしたいですよね？したくないですか？
したいですよね？
## じゃあどうすんねん
docker-composeを上手く使えばもっと効率よく楽しく開発できます。
containersディレクトリ配下に
こんだけ。
## docker-compose.yml
次に、アプリケーションのルートディレクトリに
docker-compose.ymlを作成します。docker-compose.ymlversion: '3' services: app: build: context: . dockerfile: containers/Dockerfile environment: # これがないとGemを`vendor/bundle`以下から読み込んでくれないので注意 # (正確には、`.bundle/config`の設定を読み込んでくれない) BUNDLE_APP_CONFIG: /hogehoge/.bundle volumes: - .:/hogehoge
単純にカレントディレクトリ全体をマウントしてるだけですね。
## ビルドしよう
さぁ
docker-compose buildしていきましょう。$ docker-compose build Building app Step 1/4 : FROM ruby:2.6.5 ---> d98e4013532b Step 2/4 : ENV APP_ROOT /hogehoge ---> Using cache ---> 97b5a8bca2d0 Step 3/4 : WORKDIR $APP_ROOT ---> Using cache ---> 54066d2ae384 Step 4/4 : RUN gem install bundler ---> Using cache ---> 290d99a58c5b Successfully built 290d99a58c5b Successfully tagged hogehoge_app:latest
すぐ終わりますね。
(初回だけ
ruby:2.6.5のDocker imageのpullに時間がかかります。)
## Gemをインストールしてみよう
とりあえず
Gemfileを作成します。$ docker-compose run --rm app bundle init Writing new Gemfile to /hogehoge/Gemfile
適当に
bcryptでも入れてみますかね。Gemfile# frozen_string_literal: true source " git_source(:github) {|repo_name| " } # gem "rails" # ↓追加↓ gem 'bcrypt'
それでは念願の
bundle installです。
docker-compose経由で実行するのと、
--pathオプションを指定するのを忘れずに。
補足: 一度
--pathオプションを付けて
bundle installを実行すると、
.bundle/configが作成されて設定が追加されるため、次回以降
bundle installの際に
ちゃんとインストールできているか確認してみましょう。$ docker-compose run --rm app bundle exec gem list * LOCAL GEMS * bcrypt (3.1.13) bundler (2.0.2)
できていますね。
次回以降もGemfileを編集した際には
docker-compose run --rm app bundle installするだけで大丈夫です。
docker-compose buildし直す必要はありません。
## 試しにRubyスクリプトを実行してみよう
test.rbを作って適当に
bcryptを使ってみます。test.rb# vendor/bundle配下から読み込むようにしてくれる require 'bundler/setup' # Gemfileの中のGemを一発でrequireしてくれる Bundler.require # NOTE: ↑上の２つはRailsの場合は勝手にやってくれるため必要ないです↑ puts BCrypt::Password.create('password')$ docker-compose run --rm app ruby test.rb $2a$12$xWXitLplfvcIuxUdTg.1I.bb/Jo0btGGnqWE02ZiMFsne.hDQXaDW
実行できましたね。
## １つ問題点が！！
現状だとインストールしたGemは
ローカルの
vendor/bundleディレクトリ配下に配置されます。
docker-compose run ...を実行するたびにこの
vendor/bundleディレクトリ配下が毎回マウントされるため、
Gemが増えてくると
docker-compose run ...を実行するたびにマウントに時間がかかり、コマンド実行が遅くなってしまいます。(この問題はDocker for Macを使用している場合のみ発生するらしいです)
「毎回
docker-compose buildし直すのが面倒だからこうしたのに、本末転倒じゃねぇか！！」
落ち着いてください。こんな時のためにDockerには
volumeという機能があるじゃないですか。
vendor/bundleをボリュームに切り出す
docker-compose.ymlを以下のように修正するだけで解決します。docker-compose.ymlversion: '3' services: app: build: context: . dockerfile: containers/Dockerfile environment: BUNDLE_APP_CONFIG: /hogehoge/.bundle # これがないとGemを`vendor/bundle`以下から読み込んでくれないので注意 volumes: - .:/hogehoge # ↓追加↓ - bundle:/hogehoge/vendor/bundle # ↓追加↓ volumes: bundle: driver: local
ローカルの方の
vendor/bundle配下のファイルはもう必要ないため削除しちゃいましょう。$ rm -rf vendor/bundle/*
ボリュームとして切り出したら改めて
これで
vendor/bundleディレクトリ配下は
bundleボリュームとして切り出されて毎回ローカルからマウントされることがなくなるため、
docker-compose runで余計な時間がかかることはなくなります。
## 注意しておくこと
Gemのコマンドを使う際には
bundle execを付け足すのを忘れないようにしてください。
こんな感じで↓$ docker-compose run --rm app bundle exec rpsec
え？
docker-compose run --rm appだけでも長いのに
bundle execまで毎回付けるのは面倒くさいって？
alias設定するなり
Makefile使うなりやりようはいくらでもあるじゃないですか。
僕自身エンジニア歴1年ちょっと、Dockerを使い始めて2ヶ月程度なので知識不足が否めません。
見当違いの事を言っている可能性も十分にあります。
誤った表現や設定等ありましたらコメントにてご指摘をお願いします。
Docker便利ですね！
# Railsチュートリアル学習メモ3
## renderの中身はルーティングパスじゃなくてcontrollerの階層を記述するrender("users/login_form")
## viewで
<%= @user.id %>などとやらずにechoする方法<% if @user.id == @current_user.id concat link_to("編集", "/users/#{@user.id}/edit") end %>
concatを使う。なんでputsとかprintとかできないんだろ。
## 簡単にループ<% 10.times do concat "cat" end %>
# Railsでmodelに動的なカウンターをつける
## 要約
railsで一覧画面に何かしらの集計値とかフラグを表示したいときがあると思います。
いいね数とかそういうの。
それをcounter_cacheとかでデータとして持たずに、動的に集計してきれいに組み込む方法です。
サブクエリ
(select count(*) from likes l where l.likable_type='Post' and l.likable_id=posts.id)の中は好きなように書けるので、かなり汎用性高くフラグなりなんなりつけられるので、これだけ覚えておけば集計付き一覧はだいたいできると思います。
個人的にrailsのjoin+preload/eager_load/includesなどを使ってrailsのオブジェクトとしてしっかりつくる、という方法にこだわってきたのですが、これだとassociationの書かれ方に影響されて毎回違う書き方になってしまいます。フラグがひとつふたつ付けばいい場合にはやりすぎになってしまいがちです。
一方で、この書き方は多少生のsql書いてしまいますが、使い回しが効くのではないかと思います。
# RailsのGeneratorGeneratorが便利すぎたので早く帰れる
どうも、株式会社Fusicでプリンシパルエンジニアやってる南部です。この記事はFusic その2 Advent Calendar 2019の2日目の記事です。
## 経緯
エンジニアは怠惰であれ、という言葉は誰が言ったのか、知るよしもありませんが、私もその例にもれず、怠惰な人間です。
ルーチンで作るものは5秒でつくってしまって、本当に頭を使うべきものに時間を割きたいと思うのは私だけではないでしょう。
RailsにはGeneratorをGenerateするGeneratorGeneratorというものがあります。もう何を言ってるのかわかりませんが、とにかくそういうものがあるのです。
これを導入すると、ルーチンワークを5秒で終わらせる夢が叶います。
みなさんも是非お試しください。
## GeneratorGeneratorでGeneratorのテンプレートを作ろう$ bundle exec rails generate generator nantoka create lib/generators/nantoka create lib/generators/nantoka/nantoka_generator.rb create lib/generators/nantoka/USAGE create lib/generators/nantoka/templates
このように
lib/generators/nantokaの中にファイルやディレクトリが作られます。
本体になるのは
まず、試しにこんなコードを書いてみます。lib/generators/nantoka/nantoka_generator.rbclass NantokaGenerator < Rails::Generators::NamedBase source_root File.expand_path('templates', __dir__) def step1 puts "step1: #{self.name}" end def step2 puts "step2: #{self.name}" end end$ bundle exec rails g nantoka hoge step1: hoge step2: hoge
おわかりの通りに、このGeneratorに実装されたメソッドが順番に実行されるようです。
## ファイルを作る
ファイルを作るには
create_fileを使って実装します。lib/generators/nantoka/nantoka_generator.rbclass NantokaGenerator < Rails::Generators::NamedBase source_root File.expand_path('templates', __dir__) def create_generator_test_file create_file "generator_test.txt", "これはgeneratorのテストです: #{self.name}" end end
create_fileは第一引数がRailsのrootパスからの相対パスを指定し、第二引数はそのファイルに書かれる内容です。$ bundle exec rails g nantoka hoge create generator_test.txt $ cat generator_test.txt これはgeneratorのテストです: hoge
ちゃんとファイルができてますね。
## テンプレートからファイルを作る
create_fileはちょっとした内容をファイルに出力するには便利ですが、コードを出力するには結構難ありです。
やっぱりテンプレートエンジンほしくないですか？ほしいですよね？
ほら、我らがERBがあるじゃないですか。lib/generators/nantoka/nantoka_generator.rbclass NantokaGenerator < Rails::Generators::NamedBase source_root File.expand_path('templates', __dir__) def create_generator_test_file template "generator_test.txt.erb", "generator_test.txt" end endlib/generators/nantoka/templates/generator_test.txt.erbthis is test <%= name %>
これを実行してみましょう。$ bundle exec rails g nantoka hoge create generator_test.txt $ cat generator_test.txt this is test hoge
## 引数がほしい
引数がほしい？じゃあ、作ればいいじゃない。lib/generators/nantoka/nantoka_generator.rbclass NantokaGenerator < Rails::Generators::NamedBase source_root File.expand_path('templates', __dir__) # 引数を追加 argument :words, type: :array, default: [] def create_graphql_schema template "generator_test.txt.erb", "generator_test.txt" end endlib/generators/nantoka/templates/generator_test.txt.erbthis is test <%= name %> <% words.each do |word| -%> <%= word %> <% end -%>$ bundle exec rails g nantoka hoge arg-foo arg-bar create generator_test.txt $ cat generator_test.txt this is test hoge arg-foo arg-bar
## 実際何に使うか？
例えば、
graphql-rubyを使ってスキーマ定義したりするとき、似たようなRubyのコードがいっぱい出てくると思います。
そういうときに、一つGeneratorをつくっておけば、あら簡単。
どこかでバグが見つかっても、作り直すことも簡単です。
時間を無駄に浪費して残業してしまうなんてことがなくなりますように。
エンジニアとは自らの幸せを自ら掴み取る人間である
# 既存のRailsアプリをDocker上で起動させる
railsを勉強しているのですが、勉強のために、サンプルコードなどを自分の環境上で動かしたかったのですが、
githubなどに上がっているサンプルコードをdocker上で立ち上げるのに苦労したのでメモします。
新規のプロジェクトを立ち上げる際のDockerFileの書き方は情報がたくさんあったのですが、
既存のものを立ち上げる際の情報はほとんど無くて、あってもRuby2.4のものだったりしたので、
いろいろなサイトを参考にして、Ruby2.5環境で既存のプロジェクトを立ち上げるためのコンテナ構築をしました。
## Ruby2.5でのDocker環境構築
ディレクトリ構造(完成形)
workディレクトリでrailsアプリ(DockerFileのないもの)をクローンします。sampleapp/ ├ work │ └ app/ │ └ config/ │ └ ... │ ├ Dockerfile └ docker-compose.yml
作業用ディレクトリを作成し、GithubからRailsプロジェクト(DockerFileのないもの)をクローンします。
クローンしたら、dockerコマンドを入力するためにsampleappディレクトリに戻ります。$ cd $ mkdir sampleapp $ cd sampleapp $ mkdir work $ cd work $ git clone ....(URLを入れる) $ cd ..
railsアプリのconfig/database.ymlでデータベースとの接続情報を編集します。
以下、work/config/database.ymldefault: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: host: db # development環境だけ書き換えてます。 development: <<: *default username: root password: database: docker_development test: <<: *default database: docker_test production: <<: *default database: docker_production username: root password: <%= ENV['DATABASE_PASSWORD'] %>
設定が終わったらビルドします。$ docker-compose build $ docker-compose exec web rails db:create db:migrate
railsアプリに必要なパッケージやgemがインストールできるので、
終わったらdocker-compose upで起動させます。
localhost:3000でアプリのトップページにアクセスできます。
3306や、3000の部分はポートがかぶらないように、お好みの番号に設定できます。
※指定できないポートもあるので、エラーが出る際は下記のサイトなどを参考にするといいと思います。
開発中、ChromeでERR_UNSAFE_PORTエラーが出たときにチェックすべきこと(312エラー)：
あとは新規からアプリを作成する時と同じように開発できます。
## 参考にしたサイト
既存railsプロジェクトのdocker運用開始時の作業録：
Docker+既存Rails(+Puppeteer) やっぱりdockerで環境作るのを諦められなかった話：
# Rubyのmap.flatten(1)よりもflat_mapの方が高速な理由をふわっと調べてみる
## 概要
タイトルの通りですが、具体的にソースを見るまで納得できなかったので調べてみました。
## Rubyのソースコードを見てみる
本当はバージョンとかも考慮したほうがいいですが、下記リポジトリの最新版で見てみます。

## まずはflat_mapを見てみる
どうやらEnumeratorのソースは
enum.cにありそうでしたので、flat_mapに関連する箇所を確認してみました。
単純に配列を生成して、yieldした結果を格納しているというシンプルな内容でした。enum.cenum_flat_map(VALUE obj) { VALUE ary; RETURN_SIZED_ENUMERATOR(obj, 0, 0, enum_size); ary = rb_ary_new(); rb_block_call(obj, id_each, 0, 0, flat_map_i, ary); return ary; } flat_map_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, ary)) { VALUE tmp; i = rb_yield_values2(argc, argv); tmp = rb_check_array_type(i); if (NIL_P(tmp)) { rb_ary_push(ary, i); } else { rb_ary_concat(ary, tmp); } return Qnil; }
## map.flatten(1)を見てみる
## 結論
確かに
map.flatten(1)の方が配列の生成、ループ処理が1回づつ多かったので、早くなるのは納得でした。
# JScript 覚書（実践編その壱）
この記事は、富士通ソーシアルサイエンスラボラトリ Advent Calendar 2019の 3 日目の記事です。
@GORO_Nekoです。ご存知の方ご無沙汰してます。初めての方お初にお目にかかります。
えーっと、先にお断りをば一言。
以下は、自分が所属する会社の意向を反映したものでもスタンスを示すものでもなく、単なる一個人の趣味の活動から産まれた記述です。
## JScriptって知ってます？
知らない方、まずこちらの記事をご覧ください。
てぇわけでこの記事、じつは単品記事じゃなくて上で紹介した記事「JScript 覚書」の続編デス。
前の記事で「JScript とは何か」および「JScript で Windows の標準入力・標準出力・標準エラー出力を利用する方法」を紹介してみたつもりでいたのですが、読み返してみるとなんか座りが悪い。
使い方の説明として、若干コードも載っけてみたけど、どうも中途半端に感じちゃったんですよね。
てなわけで、もう少し意味のあるコードを記載して、「JScript で Windows の標準入力・標準出力・標準エラー出力を利用する方法」をコードを通して再解説しようと思います。
## 数あてゲームを作ってみる
正式名称かどうか実はよくわかっていませんが「数あてゲーム」ってありますよね？
コンピュータが考えた数値がいくつか、言い当てるゲーム。
人間が「コンピュータが考えた数は、xxx だろ？」とキーボードを通して通知すると、コンピュータが「もっと大きな数」「もっと小さな数」「あたり！」を答えるアレです。
以下、JScript で実装した数あてゲームのソースコードを掲載します（注意: 入力内容のチェック等エラーチェック処理ちゃんとやってません(:p ）。
コード中のコメント等を読んで「JScript で Windows の標準入力・標準出力・標準エラー出力を利用する方法」を体感してみてください（体感してもらえるといいなぁ）。// 0 ～ 999 の数字を一つ生成 var random_no = Math.floor(Math.random() * 1000); // 人間が撃ち込んだ数値を読み取る変数を用意する var input_number = 0; // 人間が行った発言回数を記録するカウンタを用意する var cnt = 0; // 発言数の上限を定義する var MAX_CNT = 20; // あたり判定フラグを用意する var hit_flg = false; // コンピュータが返すメッセージ文字列を定義する var OOKII = "大きいです。"; var TIISAI = "小さいです。"; var ATARI = "あたりです。"; // コンピュータが返すメッセージの格納変数を用意する var pcAns = ""; // 開始メッセージを出力する WScript.StdOut.WriteLine("0～999の数字を入力してください。"); WScript.StdOut.WriteLine("私の考えた数より大きい数だった場合「大きいです。」"); WScript.StdOut.WriteLine("私の考えた数より小さい数だった場合「小さいです。」"); WScript.StdOut.WriteLine("私の考えた通りの数だった場合「あたりです。」とお答えします。"); while(cnt < MAX_CNT){ // ユーザに入力を促す WScript.StdOut.WriteLine("数を入力して[Enter]キーを押してください。"); // 標準入力に入力された情報を読み取る input_number = WScript.StdIn.ReadLine(); //当たりはずれを判定する if(random_no < input_number){ pcAns = OOKII; }else if(random_no > input_number){ pcAns = TIISAI; }else{ pcAns = ATARI; hit_flg = true; } // 結果表示 WScript.StdOut.WriteLine(pcAns); // 状態確認処理 if(true == hit_flg){ // ループを脱出する break; }else{ // 入力数カウンタをカウントアップしてループ処理を続ける cnt++; } } // 終了処理 if(true == hit_flg){ WScript.StdOut.WriteLine("おめでとうございます。" + (cnt + 1) + "回で正解です。"); }else{ WScript.StdOut.WriteLine("残念。" + MAX_CNT + "回以内で正解できませんでした。"); }
先の記事で解説していますが、一応上記のコードの実行方法を書きます。
上記コードを "kaduate.js" ファイルに書き込んだとします。
その場合、実行方法は以下のようになります。x:\> cscript kaduate.js
cscript にマイクロソフトのロゴを出させたくない場合は以下の通り。x:\> cscript /nologo kaduate.js
うまく動きましたでしょうか？
なお、いつものごとく Ruby で書くとこんな感じ…かな？require 'readline' # # 注意: # windows OS 上で実行する場合は、管理者モードで起動し、以下のコマンドを実行したCMD上で実行すること # > chcp 650001 # このコード自体はエンコード UTF-8 でファイル化して実行のこと # # 0 ～ 999 の数字を一つ生成 random_no = Random.new.rand(0..999) # 人間が撃ち込んだ数値を読み取る変数を用意する input_number = 0; # 人間が行った発言回数を記録するカウンタを用意する cnt = 0; # 発言数の上限を定義する MAX_CNT = 20; # あたり判定フラグを用意する hit_flg = false; # コンピュータが返すメッセージ文字列を定義する OOKII = '大きいです。' TIISAI = '小さいです。' ATARI = 'あたりです。' # コンピュータが返すメッセージの格納変数を用意する pcAns = ""; # 開始メッセージを出力する p '0～999の数字を入力してください。' p '私の考えた数より大きい数だった場合「大きいです。」' p '私の考えた数より小さい数だった場合「小さいです。」' p '私の考えた通りの数だった場合「あたりです。」とお答えします。' while cnt < MAX_CNT do # ユーザに入力を促す p '数を入力して[Enter]キーを押してください。' # 標準入力に入力された情報を読み取る input_number = Readline.readline.to_i # 当たりはずれを判定する if random_no < input_number then pcAns = OOKII elsif random_no > input_number then pcAns = TIISAI else pcAns = ATARI hit_flg = true end # 結果表示 p pcAns # 状態確認処理 if true == hit_flg then # ループを脱出する break else # 入力数カウンタをカウントアップしてループ処理を続ける cnt = cnt + 1 end end # 終了処理 if true == hit_flg then p 'おめでとうございます。' + (cnt + 1).to_s + '回で正解です。' else p '残念。' + MAX_CNT.to_s + '回以内で正解できませんでした。' end
では、また。
## ダッシュボードで確認
発行された customer_id がURLにも反映されるみたいだ
# Rails+DeviseへのOmniauthの導入（ざっくり仕組み、CSRF対策、単体テスト含む）
## 内容
RailsアプリにOmniauth認証(google_oauth2, facebook）を導入する方法とその過程で学んだことを紹介する記事です。全体の流れやコードの意図を説明した記事はあまり見つからなかったと思ったので、その辺りを中心に解説したいと思います。
## 対象
rails初心者でOmniauth認証の導入に挑む人。
もし某スクールの後輩で見てくれた人がいた場合、
非常に面白い機能なので、まずは自力で挑戦することをお勧めします。
(そもそも違う環境でちゃんと動くとも、正解とも限りません。。。)
少しでもご参考になれば、と思って書きます。
## 前提条件
-ruby 2.5.1p57
-Rails 5.2.3
-gem 'devise' 4.7.1
（ローカル環境のみの対応です）
## 1.そもそも
omniauthでは、あるアプリにおけるログイン認証の代わりや、外部機能の使用許可をすることができます。
本実装においては、前者の機能を使用しています。
流れとしては、本アプリのパスワード入力を、SNSへのログイン(≒Cookieによりほぼ自動ログイン)で代用するイメージです。以下に全体図のイメージを示します。
青矢印のフローを実装していきます！
## 2.実装
## 2-1.gemの導入
まず、gemfileに以下を追記後、bundle installを実施します。Gemfilegem 'omniauth-facebook' gem 'omniauth-google-oauth2'
## 2-2.設定
各SNSサイト(Google developers console, facebook for developers)にて、
URLの登録および、ID, SECRET KEYを取得します。
(ご参考サイト様：
ターミナルにて、"vim ~/.bash_profile"を実行し、取得したIDとキーを記入します。bash_profileexport FACEBOOK_ID="取得したID" export FACEBOOK_KEY="取得したキー" export GOOGLE_ID="取得したID" export GOOGLE_KEY="取得したキー"
"source ~/.bash_profile"を実行し、環境変数を有効化しましょう。
## 2-3.routingの設定
SNS側からcallbackが来た際に使用するコントローラーを定義してあげます。routes.rbdevise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }
## 2-4.callbackコントローラーの作成、記述 (イメージ図の手順④⑤に当たります）
"rails g devise:controllers users"を実行し、users/omniauth_callbacks_controllerを作成し、以下を記述します。ここでは、callbackが来た際に行うアクションを設定しています。SNS側から来た情報であるauth_hashは、request.env["omniauth.auth"]として使用していきます。
クラスメソッド"from_omniauth"は次のステップでUser.rbに定義します。controllers/users/omniauth_callbacks_controller.rbdef facebook @user = User.from_omniauth(request.env["omniauth.auth"]) if @user.persisted? #もし@userがDBに既にいたら、ログイン状態にします sign_in_and_redirect @user, event: :authentication set_flash_message(:notice, :success, kind: 'Facebook') if is_navigational_format? else #もし@userがDBにいない場合、新規登録ページにリダイレクトします session["devise.facebook_data"] = request.env["omniauth.auth"] #データをsessionに入れることによって、新規登録ページの入力欄に、予め情報を入れておくなどが可能になります。 redirect_to 新規登録ページ end end def google_oauth2 @user = User.from_omniauth(request.env["omniauth.auth"]) if @user.persisted? sign_in_and_redirect @user, event: :authentication set_flash_message(:notice, :success, kind: 'google') if is_navigational_format? else session["devise.google_data"] = request.env["omniauth.auth"][:info] #google認証の場合は、なぜかauth_hashの容量が大きく、一瞬で容量オーバーとなるため、新規登録時に必要な情報のみをsessionに渡すこととしました。（おそらく画像データのせい？） redirect_to 新規登録ページ end end
## 2-5.メソッドの定義 (イメージ図の手順④⑤に当たります）
## ユーザー登録の流れを設定します。 ここは設計により異なります！
既存ユーザーであるかの識別は,uidやemailアドレスにて、実施されている記事を多く見受けましたが、
本実装では、usersテーブルとsns_credentialsテーブルを別で用意したため、
（1人のuserが複数のsns_credentialsを持つことを想定しています。）
ユーザーの識別はemailで行うことにしました。
さらに、既存ユーザーがいなかった場合に関して、
ここでuser, sns_credentialをDBへ登録することもできますが、
本アプリにて必要な情報が、auth_hash上で欠けている場合を想定し、
インスタンスの作成に留めました。models/user.rbdef self.from_omniauth(auth) user = User.where(email: auth.info.email).first sns_credential_record = SnsCredential.where(provider: auth.provider, uid: auth.uid) if user.present? unless sns_credential_record.present? SnsCredential.create( user_id: user.id, provider: auth.provider, uid: auth.uid ) end elsif user = User.new( id: User.all.last.id + 1, email: auth.info.email, password: Devise.friendly_token[0, 20], nickname: auth.info.name, last_name: auth.info.last_name, first_name: auth.info.first_name, ) SnsCredential.new( provider: auth.provider, uid: auth.uid, user_id: user.id ) end user end
## 2-6.リンクの導入
## 2-7.CSRF対策
と言いたいところですが、Omniauth認証はCSRF脆弱性が指摘されているので、
## 3.テストコード（一例）
対象：sns_credential.rbのuidのunique制約が作動するか
ダミーのauth_hashを作成したり、omniauthをtestモードにするなど、少し設定が必要です。
↓設定rails_helper.rbmodule OmniauthMocks def facebook_mock OmniAuth.config.mock_auth[:facebook] = OmniAuth::AuthHash.new( { provider: 'facebook', uid: '12345', info: { name: 'mockuser', email: 'sample@test.com' }, credentials: { token: 'hogefuga' } } ) end end RSpec.configure do |config| OmniAuth.config.test_mode = true config.include OmniauthMocks end
↓テストコードspec/models/sns_credentials_spec.rbRSpec.describe SnsCredential, type: :model do describe '#facebook validation' do before do Rails.application.env_config['omniauth.auth'] = facebook_mock end context '認可サーバーから返ってきたメールアドレスを、すでに登録済みのuserが持っていた場合' do before do user = create(:user, email: 'sample@test.com') end context '認可サーバーから帰ってきた情報とprovider名が異なるが、同じuidを持つSnsCredentialレコードがあった場合' do before do SnsCredential.create(provider: 'google_oauth2', uid: '12345', user_id: '1') end example 'uidのvalidation(unique制約）が機能するか' do expect(SnsCredential.create(provider: 'facebook', uid: '12345', user_id: '1').errors[:uid]).to include('はすでに存在します') end end end end end
## 4.考察
・設計が良くなかったと思いますが、結局、"本アプリに登録したemailアドレス"と"SNS側からトークンで帰って来るemailアドレス"の照合をしているだけと言えます。結果、SNSに登録したemailとパスワードがあれば、本アプリの認証をパスされてしまう事になるので、セキュリティ的な甘さを感じました。。。
(SNS側は別デバイスでのログインを見張る、SMS認証等、強固なようなので、そこは安心と思います)
対策としては、認証時にもう１ハードルが必要かもしれません。
・また、アドレスや住所等の個人情報がサーバーサイド側に飛ぶので、ユーザー目線としては、信頼できないサイトでは使うべきではない、と思いました。。。
## 5.参考にさせて頂いた記事様
## 長文にも関わらず、最後までお読みいただきありがとうございました。
## 初投稿記事なので、ご意見、修正点などいただけましたら、幸いです！
# Rails6 のちょい足しな新機能を試す109（while_preventing_writes 編)
Rails 6 に追加された新機能を試す第109段。 今回は、while_preventing_writes 編です。
Rails 6 では、
while_preventing_writesが追加されました。
multi-db 関連のメソッドで、 while_preventing_writes のブロック内では、 DBへの書き込みができません。
同じDBに対してDBのConnection を別に作成しても書き込みはできないようになっています。
Ruby 2.6.5, Rails 6.0.0 で確認しました。$ rails --version Rails 6.0.0
今回は、簡単なスクリプトを作って確認します。
## Rails プロジェクトを作成する$ rails new rails_sandbox $ cd rails_sandbox
## User モデルを作成する
Userモデルを作成します。
$ bin/rails g model User name
## User モデルを編集する
User モデルのDBの Connection を
ActiveRecord::Base.connectionとは別になるように変更します。app/models/user.rbclass User < ApplicationRecord connects_to database: { writing: :primary, reading: :primary } end
## 動作確認のスクリプトを作成する
ActiveRecord::Base.connectionと
User.connectionが違うことを確認し、
while_preventing_writesのブロック内では、DBへの書き込みができないことを確認します。scripts/while_preventing_writes.rbputs User.count if ActiveRecord::Base.connection.object_id != User.connection.object_id puts 'ActiveRecord::Base.connection != User.connection' end # ActiveRecord::Base.connection.while_preventing_writes do # Rails 6.0.0.rc1 ActiveRecord::Base.connection_handler.while_preventing_writes do # Rails 6.0.0 User.create!(name: 'Taro') end puts User.count
## マイグレーションを実行する
## スクリプトを実行する
スクリプトを実行します。
## ちなみに
Rails 6.0.0rc1 では、エラーにならず、保存できてしまいます。(6.0.0rc1 では、メソッドの定義場所が異なるため、
ActiveRecord::Base.connection.while_preventing_writesとする必要があります。）
## 試したソース
試したソースは以下にあります。
## 参考情報
# Reformで親子関係のあるフォームオブジェクトを作ってみる
Ateam cyma Adevent Calendar 2019、4日目です！
本日は株式会社エイチームでcymaのエンジニアの @bayasist が務めさせていただきます。
Ruby on Railsで書かれたプログラムでは、フォームオブジェクトを利用することで、分かりやすく書くことができる場合があります。RailsでFormObjectを簡単に作れるtrailblazerというgemのReformというものがあり、使いこなせばなかなか便利です。
フォームオブジェクトが楽に作れるようにいくつか機能はありますが、ドキュメント(特に日本語)が少ないので、今回は特に親子関係をもつデータのReformを用いたフォームオブジェクトの作り方を解説できたらと思います。
## フォームオブジェクトを利用する利点
フォームオブジェクトはmodelをそのままformにするのではなく、バリデートなどformに関係する処理を行うオブジェクトです。これらのオブジェクトを作成することで、以下のようなメリットがあります。
- 複数モデルをまたがったFormの処理をコントローラに書かなくても済む
- ActiveRecordのモデル以外のデータの更新などでも同じようなお作法でView,Controllerが書ける
## Reformのメリット
Reformは下記のような特徴があります
フォームオブジェクトを簡易的に作成できるtrailblazerというgemのReformというものがあります。それを用いると下記のようなメリットを受けることができます。
- ActiveModelと同じような記法でValidateや要素などが記載できる
- ActiveModel以外のデータの読み書きでも同様の記法で扱える
- 親子関係など多少データ構造が多少複雑になってもプログラムが複雑になることはない
## まずはReformでフォームオブジェクトの基本形を作る
まずは、一つのmodelのみでReformを使ったフォームオブジェクトを作っていきます。nameカラムを持ったparentというモデルを更新するだけのものを作ります。(あとでchildというモデルをparentの子供にします)app/models/parent.rbclass Parent < ApplicationRecord endapp/forms/parent_form.rbclass ParentForm < Reform::Form property :name validates :name, length: { maximum: 5} endapp/controllers/parent_controller.rbclass ParentController < ActionController::Base def edit @form = form end def update @form = form if @form.validate(update_param) @form.save end end private def form ParentForm.new(Parent.find(params[:id])) end def update_param params.require(:parent).permit(:name) end endapp/views/parent/edit.html.rb<%= form_with model: @form do |form| %> <%= form.text_field :name %> <%= form.submit %> <% end %>
formオブジェクトができました。
Controllerを見ていただきたいのですが、ほとんどActiveRecordのお作法で書くことができます。
一点大きく違うところはvalidateの部分。Reformでは、validateメソッドでパラメータのバリデートをし、フォームオブジェクトの各要素ににパラメータを渡していきます。
またsaveメソッドでは、フォームオブジェクトの値をもとのモデルに渡してから、モデルのsaveメソッドが呼び出されています。モデルのsaveメソッドを呼び出さず、元のモデルへのデータの移動のみをやりたい場合はsyncメソッドを用います。
## parentモデルにchildという子要素を作る
parentモデルの子要素としてchildモデルを作ります。app/models/parent.rbclass Parent < ApplicationRecord has_many :children endapp/models/child.rbclass Child < ApplicationRecord belongs_to :parent endapp/forms/parent_form.rbclass ParentForm < Reform::Form property :name validates :name, length: { maximum: 5} collection :children, populate_if_empty: Child do property :name validates :name, length: { maximum: 5} end endapp/controllers/parent_controller.rbclass ParentController < ActionController::Base def edit @form = form end def update @form = form if @form.validate(update_param) @form.save end end private def form ParentForm.new(Parent.includes(:children).find(params[:id])) end def update_param params.require(:parent).permit(:name, children_attributes: [:name]) end endapp/views/parent/edit.html.rb<%= form_with model: @form do |form| %> <%= form.text_field :name %><br /> <%= form.fields_for :children do |child_form| %> <%= child_form.text_field :name %><br /> <% end %> <%= form.submit %> <% end %>
Formオブジェクトでcollectionを使うこと以外はActiveRecordを使用したときとほとんど変わらずに実装できます。
複数モデルのバリデーションや保存処理はフォームオブジェクトが引き受けるため、モデルが複数になってもControllerが散らかったりすることなく記述できます。また、Reformではそれらの処理をほとんど書くことなく行えます。
## 上記プログラムの重要な問題点
上記プログラムではChildのアップデートの際に、Textboxの値を上からDBで検索された順にあてはめていきます。/editの表示からアップデートまでにほかのブラウザなどでChildの一部要素がdeleteやinsert等されると予期せぬ動作につながります。
そこで、/editの表示の際にHiddenFieldにchildのidを入れておき、保存の際にChildのidとPOSTで送られてきたidを突合しながら保存していく必要があります。
そのために下記のプログラムを変更する必要があります。app/controllers/parent_controller.rbclass ParentController < ActionController::Base # (中略) def update_param params.require(:parent).permit(:name, children_attributes: [:id, :name]) end endapp/views/parent/edit.html.rb<%= form_with model: @form do |form| %> <%= form.text_field :name %><br /> <%= form.fields_for :children do |child_form| %> <%= child_form.text_field :name %><br /> <%= child_form.hidden_field :id %> <% end %> <%= form.submit %> <% end %>app/forms/parent_form.rbclass ParentForm < Reform::Form property :name validates :name, length: { maximum: 5} collection :children, populate_if_empty: Child, populator: ->(fragment:, ) { children.find_by(id: fragment["id"].to_i) } do property :name validates :name, length: { maximum: 5} end end
これを行うことでHiddenFieldのidとDBのIDが同一のものを更新するようになります。ほかのブラウザで該当レコードが削除されていた際はvalidateを行う際にエラーとなり、ほかの関係ないデータを更新しに行くということはありません。
ここではfragmentはvalidateの際に送られてきたデータ(今回でいうとformで入力したデータ)、childrenはDBから持ってきたデータとなります。
この機能を用いれば、複合キーなどID以外で突合することもできます。
例)app/forms/parent_form.rbclass ParentForm < Reform::Form property :name validates :name, length: { maximum: 5} collection :children, populate_if_empty: Child, populator: ->(fragment:, ) { children.find(*_id: fragment["*_id"].to_i, ~~~_code: fragment["~~~_code"].to_i) } do property :name validates :name, length: { maximum: 5} end end
## ChildのFormを外だしする
Childが複雑になってきたら、Childのフォームを別のファイルに移したくなるかもしれません。そのようにFormObjectの子要素を外に出すことも可能です。app/forms/parent_form.rbclass ParentForm < Reform::Form property :name validates :name, length: { maximum: 5} collection :children, populate_if_empty: Child, populator: ->(fragment:, ) { children.find_by(id: fragment["id"].to_i) }, form: ChildForm endapp/forms/child_form.rbclass ChildForm < Reform::Form property :name validates :name, length: { maximum: 5} end
## 他にもいろいろなことができます
Reformを使うことで、子要素の追加や削除、デフォルト値の設定等がModel,Controllerを大きく汚すことなく比較的簡単に行えます。また、ActiveRecord以外のインスタンスにも利用できるため、FormからDBと関係ないインスタンスにデータを移すときに重宝します。
もっと調べてみたい人は公式ドキュメントを見てみてくださいね。
Ateam cyma Adevent Calendar 2019 の 4日目、いかがでしたか。
5日目は cymaのインフラつよつよエンジニアの @ihsiek がSQLのチューニング入門の記事を書くそうですよ！SQL苦手な人、早く動くSQLを書きたい人は必見ですよ！
株式会社エイチームでは、一緒に働けるチャレンジ精神旺盛な仲間を募集しています。
エンジニアとしての働き方に興味を持たれた方はcymaの Qiita Jobs をご覧ください。
そのほかの職種は、エイチームグループ採用サイトをご覧ください。
# Classiの新卒エンジニア向け研修、「万葉研修」について
皆様こんにちは！この記事はClassi Advent Calendar 4日目の記事です。
新卒の小野優子(@yukoono)と申します。ポートフォリオチームで、高校生がやったことを記録し、振り返るための「ポートフォリオ」の開発を行っております。
5月にClassiにjoinして以来、合同会社Fjordさんの「Fjord boot camp」で2ヶ月の研修→社内で2ヶ月半の研修→チームに配属され業務へ、というフローで動いておりました。
今回は社内で受けた新卒研修、通称「万葉研修」について書いていきます。
## 万葉研修とは
株式会社万葉さんがgithub上に公開している、Ruby on Railsのプログラマーになるための教育プログラムです。リンクはこちら。25ステップ+αあり、エンジニアとしてClassiにjoinした新卒メンバーは、この研修プログラムに2ヶ月程かけて取り組みます。メンターとして、「ゼロからわかるRuby超入門」の著者であるigaigaさん、12/21のTokyoGirls.rbで登壇されるただあきさんをはじめとした社内のエンジニアの皆様にお世話になりました。
## 大まかな流れ
プログラムのステップ3を終えた後、ステップ4で自分が作りたいアプリの仕組みやDB構造を考えます。私の場合は、「暇な時間を使って、普段先延ばしにしていることをやるアプリ」をコンセプトに置きました。利用の流れとしては、
1. アプリに筋トレや英語の勉強など、「やりたいけれど緊急ではない」ことを登録する
2. 暇な時間ができたら、何分程度暇なのかを登録する
3. 所要時間に合わせて、やることをアプリが提案する
4. 終わった後、記録をアプリに入力し、保存する
という感じです。このアプリのペーパープロトタイピングやDB構造案を見せながら、メンターとどう実装するかを相談します。私の場合は最初にやりたいことをかなり盛りだくさんで考えていたため、「まずはやりたいことを登録するだけの、最小の機能で実装してみよう。研修が進むのに合わせて、コンセプトの要素を取り入れていこう」という話になりました。
このようにしてアプリの方向性を決定した後は、研修のステップに沿って実装していきます。方向性がそれぞれ違うため、同じ研修を受けていても出来上がるアプリは人によってかなり違ったものになります。例として、同期の@ruru8は社内で利用できる書籍サービスを作っていました。詳細は22日の投稿をお楽しみに。
## 研修期間中の過ごし方
- プログラムに沿って、アプリに機能を実装する
- 取り組んでいたステップの実装が終わる、または途中でもキリのいい単位で実装が行えたら、Githubの自分のリポジトリにローカルの内容をpushする
- 見て欲しいところにコメントを書いてプルリクエストを出し、メンターにレビューをお願いする
- LGTM(Looks Good To Me…レビューがOKであるという意味)がもらえれば次の実装に取り掛かる。修正が必要な場合は、ローカルで修正を行い、pushして再レビューをお願いする
という感じです。
わからないことがあったときは、会社で買っていただいている参考書籍を読むか、メンターや社内の先輩に質問します。参考書籍についてはこちらにまとめられています。
また、Classiはコミュニケーションツールとしてslackを使っておりまして、個人が分報チャンネルを持ち、困りごとや思ったことなどをつぶやく文化が活発です。分報についてはこちら。私も自分の分報である「times_ono」というチャンネルに研修の進捗を書くようにしておりまして、ここで質問するとチャンネルを見ていただいている先輩からよくアドバイスを頂けます。
これはtimes_onoの一幕でして、私がわからない！と言ったことに対して、同期の@hxrxchang君が席に助けに来てくれて、参考になるリンクも貼ってくれたところです。こんな感じで、気軽にSOSが出せて、助けてくれる人が社内にたくさんいる、すごく温かい環境です。
## 成果物
出来上がったアプリがこんな感じです。
私はレビューで時間をかけたため、ステップ4で考えていたコンセプトの実現は十分には行えず、シンプルに万葉研修のステップを進めたアプリ、という感じの成果物になりました。ただ、私の場合はレビューで得た知識が評価され、チームで研修内容の共有会を開かせていただきました。
研修のゴールは人それぞれです。私はステップ23まで+研修内容の共有会の開催がゴールになりましたが、今研修中のベトナム人の新卒二人は1ヶ月で早くもステップ25を完了し、オプション要件に取り組んでいます。また、フロントエンドのスキルがあるメンバーはRailsのプログラミングと並行してAngular jsで画面の作成を行い、操作性に優れた格好いいアプリを作っています。Classiの先輩方に協力してもらい、ユーザビリティテストを行なったメンバーもいます。
## 研修を終えて
研修を通して、「プログラマーの仕事の大部分は、人への気遣いである」という感想を持ちました。なぜなら、メンターからの指摘事項のほとんどは、「他のエンジニアがアプリをメンテナンスする時に分かりやすい/後で困らないコーディング」についてのアドバイスだったためです。
- こうした方が、他のプログラマーが分かりやすい！
- この方がRailsや他のgemのアップデートに関わらず使える！
- このコメントをつけた方が、レビュアーや他のエンジニアに親切！
などのコメントをたくさん頂くうちに、プログラマーの仕事像が「黙々と実装だけ行えれば良い、他の人とは関わらない」から、「他の人へ気遣い、情報を伝達し、理解して貰えるコードを書く」に変わっていきました。この意識は、現在チームで仕事をする上でも、チームメンバーへの気遣いや、伝わりやすいプルリクエストという形で活きています。
## 終わりに
この研修について社内の先輩エンジニアに話したところ、「羨ましい」という声を多く頂きました。2ヶ月以上かけてコードの書き方を教えてもらえる機会はとても貴重で、多くのプログラマーは仕事の中でコードの書き方やプルリクエストの仕方を覚えていくそうです。手厚い研修プログラムを組んでいただいたigaigaさん、ただあきさん、わからないところを教えて頂いた先輩エンジニアの皆さん、また充実した研修を受けさせて貰える会社の懐の広さに感謝しつつ、この投稿を締めます。
明日の投稿は@onigraさんです。お楽しみに。
# Classiの新卒エンジニア向け研修について
今回は社内で受けた新卒研修について書いていきます。
## 研修内容
株式会社万葉さんがgithub上に公開している、Ruby on Railsのプログラマーになるための教育プログラムを使っています。リンクはこちら。
25ステップ+αあり、エンジニアとしてClassiにjoinした新卒メンバーは、この研修プログラムに2ヶ月程かけて取り組みます。メンターとして、「ゼロからわかるRuby超入門」の著者であるigaigaさん、12/21のTokyoGirls.rbで登壇されるただあきさんをはじめとした社内のエンジニアの皆様にお世話になりました。
私はレビューで時間をかけたため、ステップ4で考えていたコンセプトの実現は十分には行えず、シンプルに研修プログラムのステップを進めたアプリ、という感じの成果物になりました。ただ、私の場合はレビューで得た知識が評価され、チームで研修内容の共有会を開かせていただきました。
研修のゴールは人それぞれです。私はステップ23まで+研修内容の共有会の開催がゴールになりましたが、今研修中のベトナム人の新卒二人は1ヶ月で早くもステップ25を完了し、オプション要件に取り組んでいます。また、フロントエンドのスキルがあるメンバーはRailsのプログラミングと並行してAngularで画面の作成を行い、操作性に優れた格好いいアプリを作っています。Classiの先輩方に協力してもらい、ユーザビリティテストを行なったメンバーもいます。
# CarrierWaveでpng画像を処理したときに色空間がGRAYになってしまってた
## バージョン
- ruby 2.6.1
- Ruby on Rails 5.2.3
- MacOS Catalina 10.15.1
- ImageMagick 7.0.9-5
- gem mini_magick 4.9.5
- gem carrierwave 1.3.1
## 問題
CarrierWaveでMiniMagickを使って画像を処理する時。例えば、resize_to_fit とかresize_and_pad とか resize_to_fill とかを使うと、png画像の色空間が元々RGBだったのにGRAYになってしまう現象があった。
## 調査
どうやら、色空間をカラーで持っていても、グレイスケールっぽい画像だとImageMagickがカラースペースをグレイスケールに変換してしまうようだった。
なぜ・・、やめてくれ・・！
## 解決方法
上記した resize_to_fit とかのメソッドには、 combine_options という名前付き引数が渡せる。これはImageMagickのオプションと対応しているので、とにかく大量のものが指定できて、その中で、png画像での色の設定は、
-defineオプションで
png:color-type=?を指定できる。
今回はRGBAで色情報を持ってほしかったので、
png:color-type=6となる。
つまり、
combine_options: {define: 'png:color-type=6'}を指定することで解決した。
Carrierwaveがどうとかいう問題ではなく、ImageMagick側の話だったようだ。
## 注意
これで解決したんだけど、このオプションは要注意なことが書いてあるので、png:color-typeの説明は見ておいた方が良さそう。
最初は色空間が変わるという現象だったから、combine_optionsに
-colorspace sRGBを指定してたんだけど、これでは問題は解決できなかったのでちょっとハマった・・。
# 最近勉強して、知った言葉まとめてみた
私が最近、知った言葉を、自分なりにまとめてみました。参考にしてもらえると、嬉しいです
## ハッシュ化
パスワードを、保管する際に用いられる手法。
平文のパスワードからハッシュ値と呼ばれる値を求め、そのハッシュ値という値でパスワードを保管する手法。暗号化とは違い、不可逆変換であり、元の平文のパスワードに戻すことが不可能である。
## データベースのNOTNULL制約
データベースに、nullを入れることを許容しない制約。制約をつけることにより、nullが入るとエラーが起きることにより、nullを入れることが出来なくなる。データベースの必須項目などに付けるといいと思います。
## キーワード引数(Ruby)
関数を作った際の引数の末尾に
:をつけることで、関数を呼び出す際にどの引数に、どの数や文字、変数を渡すのか指定することが出来る。引数が多くなった場合などに便利。
## CSSセレクタ
CSSを指定することにより、要素を取得できる。多くのセレクタの種類があり、それらを組み合わせることにより、細かいとこまで指定して、要素を取得できる。おすすめチートシート
説明が曖昧ですが、もっと上手く説明できるように精進致します。
# 【Rails】パンクズリストの作り方
## まえがき
みなさんパンくずリストは知っていますでしょうか。どこかで耳にした方もいるかもしれません。ちょっと変わった名前ですがbreadcrumbsと書くとかっこいいですね。このパンくずリストですが名前によらずとても便利な物なのでぜひ作り方を覚えましょう。
## パンクズリストとは
ホーム＞おすすめ一覧＞家電製品
のような現在いる位置を視覚的に見ることができる物です。大抵画面上部に用いられることが多いです。
これはよくSEO対策的に使われることが多く、SEOに詳しい人にとってはおなじみなのではないでしょうか。
パンクズリストを作るメリットは以下の通りですです。
## 設置するメリット
## ユーザビリティーの向上
パンくずリストを用いることで視覚的に現在いる位置を構造的に見ることができるので、そのwebアプリを使うユーザーがサイト内で迷子になることがなくなり、ストレスなくサイト内巡回をすることができます。また、様々なユーザーはみなトップページからサイトに訪れるわけではなくそれぞれ必要なページに直接飛んでくるので、本当に目的のページを開けているか確認をするという使い方もされます。
## 検索ページに表示される時がある
googleなどで検索をかけた時にページの説明欄にパンクズリストが表示されているのを見たことがある人もいるかもしれません。パンくずが表示されていると検索ページにいるだけでサイト内の構造を見ることができ、飛びたいページの上層カテゴリも表示されるのでクリック率対策になります。
## 内部SEO対策
googleはページ内を評価する際に一つ一つのページに飛びリンクの文字などを認識しそのページが有用なのかどうかを判断しています。その際にパンクズリストがあるとその作業がスムーズになるために高く評価されSEO対策に効くとされています。
## 設置方法
では本題です。今回はRailsでの設置になります。
Railsでは
Gretelというgemを用意してくれています。
このようにgemfileの一番下に追加します。$ bundle install
そしてコマンドを打ち、設定ファイルを作っていきます。
すると以下のようなファイルが生成されます。config/breadcrumbs.rbcrumb :root do link "Home", root_path end # crumb :projects do # link "Projects", projects_path # end # crumb :project do |project| # link project.name, project_path(project) # parent :projects # end # crumb :project_issues do |project| # link "Issues", project_issues_path(project) # parent :project, project # end # crumb :issue do |issue| # link issue.title, issue_path(issue) # parent :project_issues, issue.project # end # If you want to split your breadcrumbs configuration over multiple files, you # can create a folder named `config/breadcrumbs` and put your configuration # files there. All *.rb files (e.g. `frontend.rb` or `products.rb`) in that # folder are loaded and reloaded automatically when you change them, just like # this file (`config/breadcrumbs.rb`).
今回はマイページを作っていきたいのでrootを設定した後にマイページを表記していきます。config/breadcrumbs.rb# ルート crumb :root do link "ホーム", root_path end # マイページ crumb :mypage do link "マイページ", mypage_users_path end
crumbs :mypage doはなんという名前でhtml上に表記し呼び出すかを書きます、後々使います。
linkはパンクズリストに表示される文字とそのページがどこのパスに属しているかを表記します。
パンクズリストはリンクになっている場合が多いのでそのリンクはここで設定します。
ではビューファイルに表記していきますmypage.html.haml- breadcrumb :mypage = breadcrumbs pretext: "You are here:",separator: " › "
- breadcrumb :mypageはconfig/breadcrumbs.rbに定義したmypageを呼び出すことができ、
= breadcrumbs pretext: "You are here:",separator: " › "
で表示したい位置を指定することができます。
›という表記はHTML特殊文字と言われる物で
＞の部分を表記しています。
また親子の関係を示すために以下のような表記方法もありますprofile.haml.haml# プロフィール crumb :profile do link "プロフィール", edit_user_path parent :mypage end
parentと表記しdoとendで挟むことにより
crumb :profile doの親を書くことができます。
この表記だと次のような表示になります。
ホーム > マイページ > プロフィール
といった感じでしょうか。
いかがだったでしょうか、今回書いて行ったパンクズリストは企業としても重宝する技術ですし、習得していて損はないのではないでしょうか。ぜひポートフォリオなどにも実装してみてください！
# Rubyで「大石泉スキ」を標準出力する & idol++
この記事は 「大石泉すき」アドベントカレンダー 2日目の記事となります。
2日目は、1日目のPythonに対抗してRubyで標準出力してみます。
以下のような方を対象にした内容になります。
- 大石泉P
- 大石泉に興味がある人
- Rubyでプログラミングを始めたいと思っている人
なお、すそ野を広げるという観点にたって記事内のコードは以下の環境で実行しています。OS: Windows 10 Rubyバージョン: ruby 2.6.3p62 (2019-04-16 revision 67580) [x64-mingw32]
## とりあえず、Rubyで出力してみよう
## 初歩的な標準出力
Rubyがインストールされている状態でコマンドプロンプトを立ち上げて以下のコマンドを入力して実行します。
ruby -e "文字列"は
"文字列"のコードをRubyが解釈して実行します。
いわゆるevalですね。ruby -e "puts '大石泉スキ'"
## 複数回出力するruby -e "5.times { puts '大石泉スキ' }"
n.times { (処理) }は
nで指定した回数分だけ
{}内の処理を実行するループ構文です。
この場合は
n = 5を指定しているので5回文字列の出力が実行されます。
## ランダム要素を加えてみよう
文字列を出力するだけでは面白味もないし、
idol++ではないのでランダム要素を加えてみましょう。ruby -e "puts ['大石泉スキ', 'idol++', 'ネイビーウェーブ', 'ビット・パフォーマー', 'ニューウェーブ・ネイビー', 'ニューウェーブ・バースデー'].sample"
実行する毎に以下の要素のなかからランダムに出力されます。
Aray#sample を利用しています。
- 大石泉スキ
- idol++
- ネイビーウェーブ
- ビット・パフォーマー
- ニューウェーブ・ネイビー
- ニューウェーブ・バースデー
## Rubyのclassファイルを作成してみよう
コマンドラインからの入力だけでは
ビット・パフォーマーにはなれないのでRubyでclassを定義してコードを書いてみましょう。
## 初歩的なclass定義とコマンドラインからの実行
テキストエディタで以下のコードを入力して
izumi.rbという名前で保存します。class Izumi def perform puts '大石泉スキ' end end # コマンドラインから呼び出された時に実行される処理 izumi = Izumi.new izumi.perform
コマンドプロンプトで以下のコマンドを入力して
izumi.rbを実行します。ruby izumi.rb
## irbからの実行
コマンドラインからの実行では1回限りの操作しか行えないので複数回の操作を繰り返して変化が起こるようにしてみましょう。
まずは、Rubyの対話型の実行環境である
irbを立ち上げましょう。
--noreadlineオプションをつけないと日本語入力が行えないので気を付けてください。
irb --noreadline
以下のようにRubyのコードを実行することができます。
izumi.rbを以下のように書き換えます。class Izumi def initialize @idol = 0 end # Rubyはインクリメント演算子がないので全角文字を利用したメソッド名にしています def idol＋＋ @idol += 1 end def perform puts '大石泉スキ' idol＋＋ if @idol % 3 == 0 idol_rank = @idol / 3 puts '' puts "大石泉のアイドルランクが上がった！ => アイドルランク #{idol_rank}" end @idol end end
izumi.rbファイルのあるディレクトリに移動してirbを起動し、以下のコマンドを入力して
izumi.rbの内容をirbに読み込みます。require './izumi'
class定義から
izumiインスタンスを作成して
performメソッドを呼び出します。izumi = Izumi.new izumi.perform
performメソッドを呼び出す毎に
idol++されて3回毎にアイドルランクがアップしていきます。
# Active Storageを用いた複数ファイルアップロード機能の実装〜過程〜
## 今日のアウトプット
Railsから、
「Argument is too long」
というお叱りを受け、Active Storageでのファイルアップロード機能実装からの、Pythonプログラムにコントローラーでアップロードしたファイルを渡す部分を修正。
## 開発環境
Rails 5.2
Ruby 2.4
## ファイルアップロードの流れと実現したいこと
1,3つのファイルをViewからアップロードさせる。new.html.erb<div id="input-search"> <%= form_with model: @context, local: true do |form| %> <%= form.file_field :files, multiple: true %> <%= form.submit "プログラムを実行する" %> <% end %> </div>
2,inputされたファイルをDBに登録し、登録したファイルのフルパスを取得する。contexts_controller.rbclass ContextsController < ApplicationController def new @context = Context.new end def create @context = Context.create context_params redirect_to context_path(@context.id, path_info) #→このように、paramsにそれぞれのファイルのフルパス が入ったarrayを渡して、showアクションに飛ばしたい。 end def show @context = Context.find(params[:id]) @file_result = ` python3 /~~/test.py "#{フルパス}" "#{フルパス}" "#{フルパス}" ` end private def context_params params.require(:context).permit(files: []) end def params_info params[:context].permit! end end
## 現状、どのような情報がViewからコントローラーへ渡っているのか？
上記のようになっている。
## 3つのファイルのフルパス取得実現のために試したこと(コメントアウトして書いてあります)
## その１
→上記で確認したparamsの内容から、@tempfileについての情報を抜き出せば良いと考えた。ContectsControllerclass ContextsController < ApplicationController def new @context = Context.new end def create @context = Context.create context_params #その1 paramsからtempfileについての情報を抜き出して、配列化し、それをparamsに渡して利用する。 path_info = [] path_info.push(params[:context][:files][0].tempfile) path_info.push(params[:context][:files][1].tempfile) path_info.push(params[:context][:files][2].tempfile) redirect_to context_path(@context.id, path_info) end def show @context = Context.find(params[:id]) @file_result = ` python3 /Users/~省略~/test.py "#{フルパス}" "#{フルパス}" "#{フルパス}" ` end private def context_params params.require(:context).permit(files: []) end def params_info params[:context].permit! end end
それぞれのファイルのフルパスがしっかりと代入されているのがわかる。
・showアクションで一度動作を止めて、paramsにpath_infoに渡したフルパスが入っているかを確認。<ActionController::Parameters {"controller"=>"contexts", "action"=>"show", "id"=>"92", "format"=>"#<File:0x007fd74e106558>/#<File:0x007fd74e948130>/#<File:0x007fd74e928f60>"} permitted: false>
formatとして、意図していない情報が入っている。フルパス を渡したはずなのに・・・
この時点で詰まりそうな予感がしている。
## その2
params file read について
→上記サイトからの情報を参考にし、「ActionDispatch::Http::UploadedFileのインスタンスに対して、readメソッドを使用することができる」とのことだったので、それぞれアップロードしたファイルの要素に対してreadメソッドを適用してみた。同様に、pathメソッドについても試して見た。
ソースコード(.readメソッド)ContextsControllerclass ContextsController < ApplicationController def new @context = Context.new end def create @context = Context.create context_params path_info = [] #その2 @temfileを用意し、paramsからそれぞれ情報を取り出して.readメソッドをかける。これについてはソースサイトがあったのだが、、 @tempfile_1 = params[:context][:files][0].read @tempfile_2 = params[:context][:files][1].read @tempfile_3 = params[:context][:files][2].read path_info.push(@tempfile_1) path_info.push(@tempfile_2) path_info.push(@tempfile_3) #結果=> path_infoには何も代入されない。["","",""]となる redirect_to context_path(@context.id, path_info) end def show @context = Context.find(params[:id]) @file_result = ` python3 /Users/~省略~/test.py "#{フルパス}" "#{フルパス}" "#{フルパス}" ` end private def context_params params.require(:context).permit(files: []) end def params_info params[:context].permit! end end
・createアクションで一度動作を止めて、path_infoに意図した情報が入っているか確認。>> path_info => ["", "", ""]
そもそもパスが取れていないので、これ以上は何もしない。
pathメソッドがそれぞれのpathを取得してくれた。
これをparamsに絡めて、showアクションへと送れば良い！！
## しかし、実際にやってみると、そんなURLはないぞ！とルーティングエラーが出てしまったので、そもそもparamsに絡める方法はダメなことがわかった。(今までなんとかparamsに絡めようとしていたのが馬鹿だったんだ)
しかし、pathメソッドを用いることで、それぞれのファイルのpathを得ることはできた。
ということはだ。paramsに絡めずに、createアクションで取得した取得した内容を、showアクションへ渡せばいい。
しかし、どうすれば良い？？
考えついたのは以下の2パターン、これ等でいいのか？を吟味しよう。
・アクションの遷移に伴って、paramsには情報を含めずに、別の形でクラスインスタンスメソッド間でのやりとりをする方法を探す。
・クラスインスタンスメソッド(createアクション)が呼ばれた場合、欲しい値を取得するためのメソッドを新たに定義し、クラスインスタンスメソッド(showアクション)でこのメソッドを呼び出して、欲しい情報を取得する。
これ等について調べていこう。
# プログラミング未経験者向けにRubyワークショップをやった話
【島根県内学生限定】初心者向けプログラミング講座を実施する機会をもらったので、
そこで、やったこと、わかったこと、次にやることをまとめて残しておく（YWT）。
## 用意した題材
## やったこと(Y)
- プログラミングとはなにかを伝える
- チームで取り組む楽しさを伝える
- 参加者に次のステップを伝える
今回は題材として、Railsを使わない素の状態のRubyを使用。
島根県が推しているということもあるけど、県内にしっかりしたコミュニティがあること、学生の間は授業やイベントで触れる機会が多いことが大きな選定理由。
もし、職業としてのソフトウェアエンジニアに進むなら、遅かれ早かれ複数言語に触れることになるだろうし、県外に出ると「じゃあ、Rubyできるんでしょ？」と聞かれることもそれなりにあるので、触れたことがあるのは無駄にはならない(と思う)。
プログラミングの初期に学んでもらいたいこと様々あるけど、今回はプログラミングが目的達成のための道具という観点から、手ごろな課題に対して手持ちの武器（知識）をひねくり回して解決することを第一に。
ずっと以前に、勉強会で体験したDevnomiを下敷きにして「ポーカーの役判定プログラムをつくる」「みんなでひとつの画面をみながらキーを叩く人間を交代しながら」「用意されたテストコードをクリアする」というかたちで企画した。
コードを書くこと自体は、孤独にやらざるをえない場合も多いけど、同じようなレベルの人間が集まってわいわいやる楽しさを通して、学ぶことでレベルアップしていき、できることが増えるという正のフィードバックがあるということを体験してもらいたかった。
ちょっと前とはまた状況が変わったようで、情報関係の学生であっても自分のパソコンを持っていないことも多いので、無料のクラウドIDEPaiza CloudとGitHubを組み合わせることで、ワークショップ後も持ち帰って再現したり、続きに取り組めるようにすることで、エコシステムの一端に触れつつ、今後プログラミングに触れる際の足掛かりとしてもらうことを期待。
## わかったこと(W)
- 講座資料にScrapBoxがとても便利
- モブプロはチーム学習として有効
- 未経験者には構造化の話がもっと必要
講座の資料作成は、ScrapBoxがとっても便利だった。全体の構成を粗くきめておきつつ、細部を思いつく都度作り込んでいくというやり方で資料作成をするのにぴったり。後からの推敲もとてもやりやすい。簡単なプレゼンテーション機能もついているので、資料＝プレゼンテーションにもなり、最初にURLを共有すれば完全にペーパーレスで実施することができる。
加えてワークショップなので手を動かしてもらうシーンも多いのだけど、説明にスクリーンショットを追加したかったらGyazoと連携した画像の貼り付けで一発だし、シンタックスハイライトは完璧とはいわないけど、サンプルコードも組み込みやすく、臨機応変に資料を改変＆共有するのには、ものすごく便利だった。
今回は初心者向けとターゲットを設定していたけど、次のイベントにつなげるという目的があるため、割と難しめのハードルをぶつけて、そこをチームで会話、協力しながら超えてほしいという期待を参加者にぶつけてみた。
これがうまくいった部分、いかなかった部分はあったけど、チームの中で議論しながらつくることを強制するやり方は、ある程度機能しており、事後アンケートでもチームだからついていけたという感想も得られた。学習については、とっかかりができればある程度ひとりでも進めることができるが、推進力＝楽しさや好奇心というモチベーションは、体験や仲間がいることで得やすくなってくれると考えた。
とはいえ、サポートで各チームをまわっていると、比較的初歩の部分のとっかかりでつまづいたり、苦労していたので、導入部分のステップをもっと小刻みにしたり、例題を増やして理解度をサポートする小さな階段、をもっとつくるべきだったと痛感した。
参加者アンケートでは配列、ハッシュあたりから理解に苦労したという話もあったが、加えてチームで議論を進めるためには、メソッドの分割とか構造化のあたりの設計よりをもうちょっと丁寧にやらないといけなかったと感じた。
## 次にやること(T)
主に課題、資料のブラッシュアップの観点で、３つ。まだまだ改善の余地はいろいろある。
- 構造化の観点の追加
- シンプルなアルゴリズムの実装例題
- よりよく書く観点の追加
「この部分をプライベートメソッドとして切り出して」とか、モノシリックなメソッドではなく、ある程度構造化するという考え方について、今回思い切り抜けてしまっていたので、ここは本当に簡単な例題を用意するなり対策が必要だった。
また、初歩のアルゴリズムの例題もなにかあったほうがスムーズだった。カードの並び替えにしても、たしかにEnumerableのsort使えば確かに一発なんだけど、いったんべったべたな実装でバブルソートを実装してみて、それを少しずつ改善して読みやすくして、最終的にはこの便利メソッド使って、と漸進的に進化させることを体感してもらったほうがよかった。
外のワークショップでもほとんどの場合、やりたいことから逆算して、必要なものを調べながら学ぶという方法をとっているのだけど、未経験者向けにやるということから、もっと足がかりとなるとなる材料が必要だった。これは、当日その場で出したような直接的なサンプルより、もっと抽象度をあげたアルゴリズムの形で例題を事前にこなすよう、１日目のインプット時間の使い方を変えた方がよかった。
## 参考サイト、当日持ち込んだ書籍等