- 投稿日:2020-04-03T22:49:40+09:00
Rails deviceでログアウト時に【No route matches[Get]"/users/sign_out"】とエラー
エラー内容
gemのdeviseを導入してログアウトするとNo route matches[GET]"/users/sign_out"とエラー。
ログアウト先ページへ行けない。解決策
config/initializers/devise.rbにある
config.sign_out_via = :deleteを
config.sign_out_via = :getに変更。
これでrails s -b 0.0.0.0で再起動→ログアウト後、無事ページに推移。
[参考文献]http://gaku3601.hatenablog.com/entry/2014/08/24/204538
- 投稿日:2020-04-03T22:49:40+09:00
Rails deviceでログアウト時に【No route matches[GET]"/users/sign_out"】とエラー
エラー内容
gemのdeviseを導入してログアウトするとNo route matches[GET]"/users/sign_out"とエラー。
ログアウト先ページへ行けない。解決策
config/initializers/devise.rbにある
config.sign_out_via = :deleteを
config.sign_out_via = :getに変更。
これでrails s -b 0.0.0.0で再起動→ログアウト後、無事ページに推移。
[参考文献]http://gaku3601.hatenablog.com/entry/2014/08/24/204538
- 投稿日:2020-04-03T21:39:57+09:00
【Ruby on Rails】ERROR: Error installing rails: sprockets requires Ruby version >= 2.5.0.【環境構築時エラー】
railsインストール時のエラー
Rubyは「Ruby+Devit 2.4.6-1 (x64)」をインストールしました。
Rubyのバージョンや環境を切り替えることができるuru導入しました。
そしてrailsをインストールするために以下コマンドを入力しました。gem install rails --version="5.2.2"するとエラー発生。
ERROR: Error installing rails: sprockets requires Ruby version >= 2.5.0.Rubyのバージョンが低いことが原因のようです。
今度は「Ruby+Devkit 2.6.5-1 (x64)」をダウンロードし、以下コマンドで登録。uru admin add C:\Ruby26-x64\bin --tag Ruby26Ruby26に変更してみます。
uru Ruby26再びrailsインストールコマンドを入力します。
gem install rails --version="5.2.2"今度はエラーが表示されなくインストールできました。
- 投稿日:2020-04-03T20:59:55+09:00
Ruby要点整理
Ruby要点整理
Rubyを学ぶ中で、理解が曖昧で、何度も書籍に立ち戻り確認してしまう点について、知識の整理のために記事を作成します。
アクセスメソッド
オブジェクトの外部から直接インスタンス変数を参照したり、インスタンス変数に代入したりすることができないため、オブジェクトの内部情報にアクセスするためのメソッドを定義する必要がある。
例1
example1.rbclass Menu def initialize(name) @name = name end def name @name end def name=(name) @name = name end end上の例1では、nameメソッドはインスタンス変数を参照するもので、name=メソッドは、インスタンス変数を変更するもの。
しかし、インスタンス変数をいくつも扱う場合、それぞれについてこれらのメソッドを定義する必要があり、手間やミスの可能性が増える。
そこで用意されているのがアクセスメソッド。例2
example2.rbclass Menu attr_accessor :name def initialize(name) @name = name end end例2のように、 「attr_accessor インスタンス変数名を示すシンボル」
とすることで、例1のnameメソッドとname=メソッドの定義を1行で書くことができる。
このように、「attr_accessor」を用いることで、Menuクラスのインスタンスに、nameという情報(=インスタンス変数)を持たせることができる。
- 投稿日:2020-04-03T20:59:00+09:00
Kinx ライブラリ - XML
XML
News!
Kinx - 3rd Preview Release!
これまでの修正も含め、3rd Preview Release を行いました。ここで触れている Fiber の修正 も含まれてます。
しかし未だ プレビュー。もし宜しければバグ報告等頂けると大変助かります。特に今回の XML はあまりテストできていない感満載。もうちょっとテストできてから紹介しようかとも思ったものの、せっかくのプレビュー版で使い方が分からないのもどうかと思い公開することにしました。実装自体はしてあり、サンプルは動くことを確認済です。
はじめに
「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。言語はライブラリが命。ということでライブラリの使い方編。
今回は XML です。
- 参考
- 最初の動機 ... スクリプト言語 KINX(ご紹介)
- 個別記事へのリンクは全てここに集約してあります。
- リポジトリ ... https://github.com/Kray-G/kinx
- Pull Request 等お待ちしております。
XML もよく利用するのでスクリプト言語でサクッと扱いたい要素の一つ。
XML
DOMパース
Xml.parseFile()
またはXml.parseString()
を使って DOM ツリーを構築する。ファイルを読み込む場合は以下の通り。var doc = Xml.parseFile("xmlfile.xml");下記は文字列を直接パースする例。
var doc = Xml.parseString(%{ <?xml version="1.0" encoding="UTF-8" ?> <artists> <artist country="US" id="1"> <name>BON JOVI</name> <price>2400</price> <img file="bonjovi.jpg"/> </artist> <artist country="US" id="2"> <name>GUNS N ROSES</name> <price>21000</price> <img file="GNR.jpg"/> </artist> <artist country="DE" id="3"> <name>Helloween</name> <price>2400</price> <img file="helloween.jpg"/> </artist> </artists> });返されたドキュメント・オブジェクトは以下のメソッドを持つ。
メソッド 内容 documentElement()
ルートドキュメントを取得 createElement(tagname)
Element ノードを作成する createTextNode(text)
Text ノードを作成する createComment(comment)
Comment ノードを作成する createCdataSection(content)
CDATA Section ノードを作成する createProcessingInstruction(target, data)
Processing Instruction ノードを作成する createEntityReference(name)
Entity Reference ノードを作成する createElementNS(nsUri, qname)
Element ノードを名前空間を指定して作成する getElementById(id)
id を指定してノードを検索する getElementsByTagName(tagName)
tagName のノードを配列にして返す xpath(expr)
expr の XPATH を評価し、結果を配列にして返す ルートノード
ルートノードは
documentElement()
メソッドを使用して以下のように取得する。var root = doc.documentElement();XMLノード
ルートノードを含む XML ノードは以下のプロパティとメソッドを持つ。
プロパティ
プロパティ 内容 type
ノード種別 name
QName tagName
タグ名 localName
ローカル名 namespaceURI
名前空間 URI prefix
プレフィックス メソッド
メソッド 内容 attributes()
属性一覧を配列で返す。 setAttribute(qname, value)
属性を設定する。 setAttributeNS(nsUri, qname, value)
名前空間を指定して属性を設定する。 removeAttribute(qname)
属性を削除する。 removeAttributeNS(nsUri, localName)
名前空間を指定して属性を削除する。 parentNode()
親ノードを返す。 children()
子ノードを配列で返す。 firstChild()
最初の子ノードを返す。 lastChild()
最後の子ノードを返す。 nextSibling()
次のノードを返す。 previousSibling()
前のノードを返す。 appendChild(node)
子ノードにノードを追加する。 removeChild(node)
子ノードからノードを削除する。 replaceChild(node1, node2)
子ノードのノードを置き換える。 replaceNode(node)
自分自身のノードを別のノードでを置き換える。 insertBefore(node)
前のノードとしてノードを追加する。 insertAfter(node)
次のノードとしてノードを追加する。 remove()
ノードを削除する。 textContent()
テキストを取得する。 innerText()
テキストを取得する。 hasChildren()
子ノードが存在すれば 1 を返す。 hasAttributes()
属性があれば 1 を返す。 getElementById(id)
id を指定してノードを検索する getElementsByTagName(tagName)
tagName のノードを配列にして返す xpath(expr)
expr の XPATH を評価し、結果を配列にして返す XPath
XPath は XPATH 式にマッチしたノードをノードセット(配列)の形で返す。ノードセットにも
xpath()
メソッドがあり、絞り込んだノード群に対し XPATH をチェインして使うことができる。先ほどのサンプル XML のドキュメントに対して実行。
var nodes = doc.xpath("//artist") .xpath("price") .map(&(p) => p.innerText()); nodes.each(&(text) => { System.println(text); });結果。
2400 21000 2400サンプル・ソース
同梱しているサンプルソースを載せておきます。説明していない
Xml.Writer
とかありますが、こんな感じの DOM パースができる例ということで、参考になると思い。function displayXml(doc, node, indent) { System.print(" " * indent); if (node.type == Xml.ELEMENT_NODE) { System.print("ELEM %s" % node.name); } else if (node.type == Xml.TEXT_NODE) { System.print("TEXT %s" % node.value.trim()); } var attr = node.attributes(); for (var i = 0, len = attr.length(); i < len; ++i) { System.print("[%s=%s]" % attr[i].name % attr[i].value); } System.println(""); var child = node.firstChild(); while (child) { displayXml(doc, child, indent + 1); child = child.nextSibling(); } } var doc = Xml.parseString(%{ <?xml version="1.0" encoding="UTF-8" ?> <artists> <artist country="US" id="1"> <name>BON JOVI</name> <price>2400</price> <img file="bonjovi.jpg"/> </artist> <artist country="US" id="2"> <name>GUNS N ROSES</name> <price>21000</price> <img file="GNR.jpg"/> </artist> <artist country="DE" id="3"> <name>Helloween</name> <price>2400</price> <img file="helloween.jpg"/> </artist> </artists> }); var root = doc.documentElement(); displayXml(doc, root); var el = root.getElementById("3"); if (el) { el.remove(); } System.println(""); System.println("getElementByTagName:"); var els = root.getElementsByTagName("img"); if (els.isArray) { els.each(&(el) => displayXml(doc, el)); } System.println(""); System.println("XPath:"); var nodes = doc.xpath("//artist").xpath("price"); if (nodes.isArray) { nodes.each(&(el) => displayXml(doc, el)); } var xmlWriter = new Xml.Writer(System); xmlWriter.write(doc); xmlWriter.write(root);実行結果。
ELEM artists TEXT ELEM artist[country=US][id=1] TEXT ELEM name TEXT BON JOVI TEXT ELEM price TEXT 2400 TEXT ELEM img[file=bonjovi.jpg] TEXT TEXT ELEM artist[country=US][id=2] TEXT ELEM name TEXT GUNS N ROSES TEXT ELEM price TEXT 21000 TEXT ELEM img[file=GNR.jpg] TEXT TEXT ELEM artist[country=DE][id=3] TEXT ELEM name TEXT Helloween TEXT ELEM price TEXT 2400 TEXT ELEM img[file=helloween.jpg] TEXT TEXT getElementByTagName: ELEM img[file=bonjovi.jpg] ELEM img[file=GNR.jpg] XPath: ELEM price TEXT 2400 ELEM price TEXT 21000 <artists> <artist country="US" id="1"> <name>BON JOVI</name> <price>2400</price> <img file="bonjovi.jpg" /> </artist> <artist country="US" id="2"> <name>GUNS N ROSES</name> <price>21000</price> <img file="GNR.jpg" /> </artist> </artists> <artists> <artist country="US" id="1"> <name>BON JOVI</name> <price>2400</price> <img file="bonjovi.jpg" /> </artist> <artist country="US" id="2"> <name>GUNS N ROSES</name> <price>21000</price> <img file="GNR.jpg" /> </artist> </artists>おわりに
XPath が使えると便利だ。
そして、XML と Zip(以前の記事)を組み合わせると、実は Xlsx ファイル(Excel ファイル)の読み書きができたりします。Xlsx ファイルは Office Open XML という名前で標準仕様化されており(色々問題もあるが)、XML ファイルを Zip で固めたものになってるので読めたりするといった具合。
ただ、実際問題として Office Open XML の全てをサポートするってのは相当量のコードになるので、すぐにできそうなのは簡易的な読み書きくらいですね。時間があればチャレンジしよう。
ではまた次回。
- 投稿日:2020-04-03T18:58:47+09:00
Perl と Ruby で解く AtCoder ABC146 B - ROT N
はじめに
Pocket詳解 Perl/CGI辞典をパラパラっと眺めておりましたら、pack/unpack 関数が目に留まりましたので、投稿いたします。
次の文字を取得する場合
'A' の次の文字 'B' を取得する場合、
C言語でしたら、'A' + 1 より 'B' を得られます。
しかし Perl の場合、'A' + 1 の結果は、1 となってしまいます。pack/unpack 関数
よって、次の文字を取得する場合、一旦バイナリ値に変換し足し算を行ってから、char 値に逆変換することにより、期待する操作が行えます。
pack char値をバイナリ値に変換
unpack バイナリ値をchar値に変換
Perl Ruby Python pack/chr pack/chr chr unpack/ord unpack/ord ord unpack.plunpack("C*", 'A'); # => 65 pack("C*", 66); # => Bunpack.rb"A".ord # => 65 66.chr # => BPython に pack/unpack があるかどうかは不明
適当B - ROT N
perl.pluse v5.18; use warnings; chomp (my $n = <STDIN>); chomp (my $s = <STDIN>); my @s = split '', $s; map {$s[$_] = chr((ord($s[$_]) + $n) % ord("A") % 26 + ord("A"))} (0..@s-1); say join('', @s);ruby.rbn = gets.chomp.to_i s = gets.chomp.split('') (0..s.size-1).each do |i| s[i] = ((s[i].ord + n) % "A".ord % 26 + "A".ord).chr end puts s.join()むむっ、ruby のスッキリ感は凄いですね。
まとめ
- Perl の関数 pack/unpack を覚えた
- Ruby のメソッド chr/ord も覚えた
- 投稿日:2020-04-03T16:43:08+09:00
【初心者向け】ピラミッド(三角形)問題
環境,前提
Ruby 2.5.1
MacOS Mojave Ver.10.14.6本記事はRubyがインストールされた前提の記事です。
Rubyをインストールしたあと、とにかくRubyをいろいろ触ってみて慣れていくための記事です。お役に立てば幸いです。採用試験に出ました。
採用試験で実際に出題されたのでやはり大事なのかなと思い今回記録に残します。
ピラミッド問題とは?
"#"をうまく出力して"#"だけで10段の三角形を作る問題です。
⬇︎こう言った出力を求められました。
#
##
###
####
#####
######
#######
########
#########
##########私が書いたコード
実際の試験では「言語は問わない、形式も自由」だったので今回はRubyのfor文を用いてやってみます。
pyramid.rbfor x in 1..10 do for y in 1..10do if y<=x then print '#' end end puts '' end他にも逆ピラミッドなども実現可能なのでぜひやってみてください!!
- 投稿日:2020-04-03T15:47:24+09:00
active hashでdbに登録させる時の注意点
1 はじめに
active hashについては以下の記事に書いています。
「active hash でセレクトボックスをつくる」
今回は某フリマサイトを作成中に起こった思わぬ動作について書いています。
2 生じた問題
今回のアプリケーションではactive hashで複数のモデルを作成していました。
年、月、日、都道府県、配送方法、配送負担、発送日数などです。
すべて入力必須項目なのでモデルにnull falseのバリデーションをかけていました。しかし、ユーザー登録の動作を確認中に問題が生じました。
ミスで生年月日を何も選択せずに登録ボタンを押してしまった時です。
バリデーションをかけているので、当然エラーメッセージが出ると思っていたのですが、登録できてしまいました。すぐに登録先のモデルとマイグレーションファイルを確認。
null falseのバリデーションは問題なく効いていました。2 原因
1) active hashのコード
以下が使用していた、生年月日の年のactive hashのモデルです。
Birthdayy.rbclass Birthdayy < ActiveHash::Base self.data = [ {id: 0, year: '---'}, {id: 1, year: '2020'}, {id: 2, year: '2019'}, {id: 3, year: '2018'}, {id: 4, year: '2017'}, {id: 5, year: '2016'}, {id: 6, year: '2015'}, {id: 7, year: '2014'}, {id: 8, year: '2013'}, {id: 9, year: '2012'}, {id: 10, year: '2011'}, {id: 11, year: '2010'}, {id: 12, year: '2009'}, {id: 13, year: '2008'}, {id: 14, year: '2007'}, {id: 15, year: '2006'}, {id: 16, year: '2005'}, {id: 17, year: '2004'}, {id: 18, year: '2003'}, {id: 19, year: '2002'}, {id: 20, year: '2001'}, {id: 21, year: '2000'}, {id: 22, year: '1999'}, {id: 23, year: '1998'}, {id: 24, year: '1997'}, {id: 25, year: '1996'}, {id: 26, year: '1995'}, {id: 27, year: '1994'}, {id: 28, year: '1993'}, {id: 29, year: '1992'}, {id: 30, year: '1991'}, {id: 31, year: '1990'}, {id: 32, year: '1989'}, {id: 33, year: '1988'}, {id: 34, year: '1987'}, {id: 35, year: '1986'}, {id: 36, year: '1985'}, {id: 37, year: '1984'}, {id: 38, year: '1983'}, {id: 39, year: '1982'}, {id: 40, year: '1981'}, {id: 41, year: '1980'}, {id: 42, year: '1979'}, {id: 43, year: '1978'}, {id: 44, year: '1977'}, {id: 45, year: '1976'}, {id: 46, year: '1975'}, {id: 47, year: '1974'}, {id: 48, year: '1973'}, {id: 49, year: '1972'}, {id: 50, year: '1971'}, {id: 51, year: '1970'}, {id: 52, year: '1969'}, {id: 53, year: '1968'}, {id: 54, year: '1967'}, {id: 55, year: '1966'}, {id: 56, year: '1965'}, {id: 57, year: '1964'}, {id: 58, year: '1963'}, {id: 59, year: '1962'}, {id: 60, year: '1961'}, {id: 61, year: '1960'}, {id: 62, year: '1959'}, {id: 63, year: '1958'}, {id: 64, year: '1957'}, {id: 65, year: '1956'}, {id: 66, year: '1955'}, {id: 67, year: '1954'}, {id: 68, year: '1953'}, {id: 69, year: '1952'}, {id: 70, year: '1951'}, {id: 71, year: '1950'}, {id: 72, year: '1949'}, {id: 73, year: '1948'}, {id: 74, year: '1947'}, {id: 75, year: '1946'}, {id: 76, year: '1945'}, {id: 77, year: '1944'}, {id: 78, year: '1943'}, {id: 79, year: '1942'}, {id: 80, year: '1941'}, {id: 81, year: '1940'}, {id: 82, year: '1939'}, {id: 83, year: '1938'}, {id: 84, year: '1937'}, {id: 85, year: '1936'}, {id: 86, year: '1935'}, {id: 87, year: '1934'}, {id: 88, year: '1933'}, {id: 89, year: '1932'}, {id: 90, year: '1931'}, {id: 91, year: '1930'}, {id: 92, year: '1929'}, {id: 93, year: '1928'}, {id: 94, year: '1927'}, {id: 95, year: '1926'}, {id: 96, year: '1925'}, {id: 97, year: '1924'}, {id: 98, year: '1923'}, {id: 99, year: '1922'}, {id: 100, year: '1921'}, ] end2) registrationコントローラー new.html.haml
ユーザー登録ページのhamlの記述です。
new.html.haml= f.collection_select :birthdayy_id, Birthdayy.all, :id, :year3) userマイグレーションファイルのバリデーション
必須事項にバリデーションをかけています。
devise_create_users.rbclass DeviseCreateUsers < ActiveRecord::Migration[5.2] def change create_table :users do |t| ## Database authenticatable ---省略--- t.integer :birthdayy_id, null: false t.integer :birthdaym_id, null: false t.integer :birthdayd_id, null: false ---省略--- end end4) userモデルのバリデーション
必須事項にバリデーションをかけています。
user.rbclass User < ApplicationRecord ---省略--- extend ActiveHash::Associations::ActiveRecordExtensions belongs_to_active_hash :birthdayy belongs_to_active_hash :birthdaym belongs_to_active_hash :birthdayd ---省略--- validates :birthdayy_id, presence: true validates :birthdaym_id, presence: true validates :birthdayd_id, presence: true ---省略--- end一見問題なさそうですが、原因はこれでした。
1)Birthdayy.rb
の '---' のidが 「0」
2)devise_create_users.rb
でカラムの型をintegerにしている
3)collection_select
で:idをparamsで送っている1~3により、何も選択しなかった場合、「0」が
createアクション
に送られたためバリデーションをすり抜けてしまったようでした。3 解決策
解決方法はいくつかあると思います。
- 保存させる数字の範囲を指定する
- integerのうち「0」という数字以外を登録させる
- '---'を使うのを諦める など
今回は以下の方法を教えてもらったので使用しました。
= f.collection_select :birthdayy_id, Birthdayy.all, :id, :year, prompt: '---'最後の
prompt: '---'を記述することで、セレクトタブでは問題なく選択肢に'---'が表示されます。
また、'---'を選択した状態でsubmitを押すと、nullとして送信されるため、null falseでバリデーションに引っ掛かることになるため設定も簡単です。
4、終わりに
チームで開発すると繋がりができて今まで話したことがない人から情報を得られる機会が増えるので大事な期間です。
積極的に他チームの人と繋がりましょう。
以上です。最後までご覧いただきありがとうございました。
- 投稿日:2020-04-03T15:36:35+09:00
隠キャ元美容師
自分
野本 彬央 (nomoto akihisa)
読みにくいので、カタカナで表現することが多いです。
1995.01.23生まれ。常にhappyを目指して生きてます。
経歴
20〜23歳まで美容室の現場で活躍してました。
24歳で最年少店長になり、ゆるっとやってました。美容師だとチャラいイメージあると思うんですが、隠キャ美容師でした。
休日の過ごし方
6:00〜7:00 起床・風呂・洗濯
7:00〜8:00 瞑想・読書・白湯
9:00〜10:30 トレーニング
11:00〜15:00 カフェで作業
15:30〜18:00 映画観賞・YouTube
18:30〜20:00 買い物・料理・食事
20:30〜22:00 風呂・寝る
- 投稿日:2020-04-03T13:46:31+09:00
active hash でセレクトボックスをつくる
1 active hashとは
某フリマサイトを作成する際に、active hashというgemを使ってセレクトタグを作成しました。
active hashは都道府県などデータベースを作成するほどではなく、かつ変更される可能性が低いデータを扱いたい時に便利なgemです。
ハッシュを使ってアクティブレコードのモデルのように扱えるファイルを作成できます。<gemのリンク>
READMEの記述
ActiveHash is a simple base class that allows you to use a ruby hash as a readonly datasource for an ActiveRecord-like model.
2 使い方
1) gemのインストール
まずはgemfileに以下のコードを記述してbundle installします。
gem 'active_hash', '~> 2.3.0' ←2020/04/03現在のバージョン2) モデルの作成
app>modelsの中に直接ファイルを作成します。
注意: rails g model は使わない今回は都道府県のactive hashを作成するためprefecture.rbという名前でファイルを作成します。
ファイルの中身の書き方は以下の通りです。
class Prefecture < ActiveHash::Base self.data = [ {id: 0, name: '選択してください'}, {id: 1, name: '北海道'}, {id: 2, name: '青森県'}, {id: 3, name: '岩手県'}, {id: 4, name: '宮城県'}, {id: 5, name: '秋田県'}, {id: 6, name: '山形県'}, {id: 7, name: '福島県'}, {id: 8, name: '茨城県'}, {id: 9, name: '栃木県'}, {id: 10, name: '群馬県'}, {id: 11, name: '埼玉県'}, {id: 12, name: '千葉県'}, {id: 13, name: '東京都'}, {id: 14, name: '神奈川県'}, {id: 15, name: '新潟県'}, {id: 16, name: '富山県'}, {id: 17, name: '石川県'}, {id: 18, name: '福井県'}, {id: 19, name: '山梨県'}, {id: 20, name: '長野県'}, {id: 21, name: '岐阜県'}, {id: 22, name: '静岡県'}, {id: 23, name: '愛知県'}, {id: 24, name: '三重県'}, {id: 25, name: '滋賀県'}, {id: 26, name: '京都府'}, {id: 27, name: '大阪府'}, {id: 28, name: '兵庫県'}, {id: 29, name: '奈良県'}, {id: 30, name: '和歌山県'}, {id: 31, name: '鳥取県'}, {id: 32, name: '島根県'}, {id: 33, name: '岡山県'}, {id: 34, name: '広島県'}, {id: 35, name: '山口県'}, {id: 36, name: '徳島県'}, {id: 37, name: '香川県'}, {id: 38, name: '愛媛県'}, {id: 39, name: '高知県'}, {id: 40, name: '福岡県'}, {id: 41, name: '佐賀県'}, {id: 42, name: '長崎県'}, {id: 43, name: '熊本県'}, {id: 44, name: '大分県'}, {id: 45, name: '宮崎県'}, {id: 46, name: '鹿児島県'}, {id: 47, name: '沖縄県'}, {id: 48, name: '未定'} ] end3) アソシエーションを組む & バリデーションをかける(任意)
a) アソシエーションを組む
active_hashにはbelongs_to_active_hashメソッドが用意されているのでaddressモデルに記述します。
※active hashの方には記述する必要はありません。b) 保存するデータベースにバリデーションをかける
今回はユーザー登録の際に必ず都道府県を入力させたいので、addressモデルにバリデーションをかけます。address.rbextend ActiveHash::Associations::ActiveRecordExtensions belongs_to_active_hash :prefecture belongs_to :user, optional: true validates :prefecture, presence: true ←この記事に関係ないカラムは省略してます以上です。
簡単ですね。2 viewにactive hashからデータを持ってくる
active hashを使いたいビューページに以下の記述をします。
= f.collection_select :prefecture_id, Prefecture.all, :id, :name<記述内容の解説>
第一引数:登録するデータベースのカラム名
第二引数:作成したactive hashからどのようにデータを持ってくるかを指定
第三引数:paramsで送るデータを指定(:nameでも可能 ※dbのカラムはstringにしましょう。)
第四引数:ユーザーが見れるセレクトタグの内容
※今回はcollection_selectの説明は割愛します。
なお今回は、都道府県を記入させるタイミングが二回あり、二度目の発送元の記入では-未定-を選べるためactive hash内に記述しましたが、ユーザー登録では-未定-は使いたくないためそれ以外を引き出します。= f.collection_select :prefecture_id, Prefecture.where.not(name: '未定'), :id, :name3 完成写真
4、注意
formを使うと自動でidが割り振られるので、cssを当てる時は検証ツールを使ってidを確認しましょう。
以上です。最後までご覧いただきありがとうございました。
- 投稿日:2020-04-03T13:10:41+09:00
[Rails]httpからhttpsにリダイレクトする方法
はじめに
オリジナルサービスを作成した際に、httpで作成しておりパスワードを入れる時に
「このサイトは危険です!」みたいな警告が流れてきました。
これは。と思い、URLを見るとhttpのままになっていました。パスワード打つ時にこんな警告が流れるともうそのサイト使いたくならないですよね笑結論
単純ですが、
config/environments/production.rb
にこの一文を書き足す・または修正するだけでhttps化できました。config/environments/production.rbconfig.force_ssl = trueまとめ
railsってなんでもありますね。ほんとすごい。
他にも設定することがあるのかもしれませんが、特に現状は問題なく経過しています。
- 投稿日:2020-04-03T12:46:41+09:00
nullを条件にしたクエリでデータを取ってくる方法 in ruby and firebase
一覧表示させるためにwhereを使う
hogehoge.rbdef self.all list = collection.where("format", "=", null).order(:created_at).get end上記のような形でformatというフィールドにnullを持ったドキュメントを全て持ってくるようなクエリを作るようなコードを書いているつもりなのですが、null部分がundefinedのエラーが出てきてしまい、取得することができなさそうなんですね。
ここの記事には、
Firestore select where a field is null [duplicate]
it is not possible to index on a field that is NOT in the document
と書いてあるし、無理なのかとも思ったんですが、更に下の方には、
However there is a workaround which consists in querying documents that contain properties with a null value data type
とあるので、いけそうでもある。
解決策
rubyではnullってnilで表すんだわ
そう、これですね。ruy上ではnullを扱うにはnilにするのでした。hogehoge.rbdef self.all list = collection.where("format", "=", nil).order(:created_at).get endとすれば、firebaseからnullを条件にしてドキュメントを取ってくることができました。
おまけ
データを取得してくる時にコレクションに対して、インデックスがないから作れという形でエラーが出ることがあります。
9: The query requires an index. You can create it here: 長ったらしいURLこんな感じのエラーが出るので、URLをコピペすると、firebaseコンソールに飛び、インデックスを作成するためのダイアログが開くので、ポチッと押してインデックスがビルドされるのを待ってください。ビルドには数分かかります。しばらく経ってまだビルド中みたいになっていてもコンソール自体に更新かけると終わってたりします。
- 投稿日:2020-04-03T11:58:02+09:00
committeeを使ったOpenAPI3のバリデーション
committeeを使ってOpenAPI3のスキーマファイルからリクエスト&レスポンスをバリデーションする方法と実際の開発に組み込む際の設定についてまとめます。
OpenAPI3とcommiteeについてはota42yさんの登壇資料が参考になります。また、committeeについての記事はいっぱいあるので、ここでは実際の開発で取り入れるにあたって設定ファイルの書き方に絞って紹介したいと思います。
committeeを使って実現したいこと
- API仕様書を作りたい
- スキーマファーストな開発をして、常に正しい状態にメンテナンスしていきたい
- リクエストのバリデーションなどのRailsが苦手な部分を楽に実装したい
committeeを使う理由
- すでに運用中のサービスに途中から組み込める
- RSpecだけで実行したり、実際のリクエストのバリデーションに使ったりと用途に合わせて組み込みやすい
- 使い方がシンプルで導入が簡単
準備物
- OpenAPI3で記載したスキーマファイル(今回は準備されている前提)
スキーマファイルの管理は手動で管理していく方法とコードから自動生成する方法があります。前者の場合は、Stoplightのようなツールを使えば新しい人のキャッチアップも楽です。後者だとswagger-blocksのようなgemがありますが、OpenAPI3のサポートはまだ実装まで行っていないようです。 コードから自動生成の方がスキーマファイルと実際のコードとの差分が生まれないため、メンテナンスが楽になりますが現状良いgemが見つからなかったので自分は手動で管理しています。
- Gemfile
今回使うgemは以下です。
gem 'committee' group :test do gem 'committee-rails' endcommitteeをテストにだけ利用する場合は、
:test
配下に書いて問題ないです。rspecで利用する
committeeをrspecに導入するためにはcommittee-railsが便利です。
rspec_helper.rb
等に以下の設定を入れれば使えるようになります。RSpec.configure do |config| config.add_setting :committee_options config.committee_options = { schema_path: Rails.root.join('schema', 'schema.json').to_s, old_assert_behavior: false } endassert_schema_conformを実行すればスキーマを使って検証してくれます。
describe 'request spec' do include Committee::Rails::Test::Methods describe 'GET /' do it 'conform json schema' do get '/' assert_schema_conform end end end設定でold_assert_behaviorをtrueにした場合にはレスポンスしかチェックされずwarningが出る(参照)のでfalseで設定しておくと良いと思います。
全てのrequest specでテストを実行する
常に最新の仕様にスキーマファイルを合わせるために、全てのrequest specで強制的にassert_schema_conformを書くには以下のように設定すれば可能です。
RSpec.configure do |config| config.include Committee::Rails::Test::Methods config.add_setting :committee_options config.committee_options = { schema_path: Rails.root.join('schema', 'schema.json').to_s, old_assert_behavior: false } config.after(:each) do |example| next unless example.metadata[:type] == :request assert_schema_conform unless RSpec.configuration.skip_assert_schema_conform end config.before(:each) do |example| skip_flag = example.metadata[:skip_assert_schema_conform] || false config.skip_assert_schema_conform = skip_flag end endこっちの記事のように
ActionDispatch::Integration::Session
にpretendする方法もありますが、このcommitで依存関係が崩れる1ようになってpretendではエラーが出てしまうため、afterで実行するようにしています。もし、assert_schema_conformをスキップしたいテストがあった場合には、下記のように記載してスキップ可能です。
describe 'GET /', type: :request, skip_assert_schema_conform: true do it 'skip conform schema' do get '/' end endこのように設定しておけば、スキーマファイルを書き忘れた場合や、スキーマファイルの値が間違えていた場合などにCIで気づくことができ、常に最新の仕様を反映するように強制することができます。
実運用のサービスで使う
committeeではリクエスト&レスポンスのバリデーションが可能です。これを使えばRailsのController側で細かなparamsのチェックをしなくてよくなるので、テストだけでなく実際のリクエストにも適用するのがおすすめです。
設定は下記の通りで、
config/initializers/committee.rb
などのファイルに記述します。return if Rails.env.test? file = YAML.load_file(Rails.root.join('schema', 'schema.yml')) open_api = OpenAPIParser.parse(file) schema = Committee::Drivers::OpenAPI3::Driver.new.parse(open_api) Rails.application.config.middleware.insert_before( ActionDispatch::Executor, Committee::Middleware::RequestValidation, error_class: Custom::RequestValidationError, schema: schema, strict: !Rails.env.production? ) Rails.application.config.middleware.insert_after( ActionDispatch::Callbacks, Committee::Middleware::ResponseValidation, error_class: Custom::ResponseValidationError, schema: schema, validate_success_only: true )rspec実行時にも読み込まれるファイルなので、
return if Rails.env.test?
してますが他にいい方法があれば知りたい...
各オプションについては Committee::Middleware::RequestValidationを参考にしてください。上記ではstrictを本番だけOFFにすることで、開発環境では厳しくジャッジして本番では緩く運用する設定になっています。本番ではどのようなリクエストが来るのかわからないので、ある程度緩く運用したいケースもあるかと思います。他にはエラー時にcommittee独自のエラーが返ってしまうので、独自のエラークラスを定義した方が良いと思います。
https://github.com/interagent/committee#validation-errors
開発中はrspecや動作確認でスキーマファイルの正当性を担保できるので、両方でcommitteeを動作させた方が良いかなと思います。また、本番で動作させることである程度サービスへの攻撃の防御や検知に使えるかなーと思いました。
undefined local variable or method 'integration_session' for #<#<Class:0x000056477f0bcee0>:0x00005647831c1fd0>
のエラーが出るようになる。integration_sessionはActionDispatch::Integration::Session
を呼び出しているActionDispatch::Integration::Runner
側で定義されているメソッドなので参照できない。 ↩
- 投稿日:2020-04-03T11:39:19+09:00
【rspec】confirmダイアログのテスト
開発環境
ruby '2.6.3'
rails '6.0.2'railsでconfirmダイアログのテストをする。
accept_confirm
だけで表示内容の確認とOKを押してくれる- Method: Capybara::Session#accept_confirm
systemテストcontext "削除する" do it "投稿消去" do click_button "消去する" expect{ expect(page.accept_confirm).to eq "本当に削除しますか?" expect(page).to have_content "レビューを消去しました。" }. to change(@user.posts, :count).by(-1) end end注意点
- expectのブロック内にひとつ以上のexpectもしくはfindを入れないと、ダイアログが表示されてacceptされる前に次へ進んでしまうので注意が必要です。 つまり
systemテストcontext "削除する" do it "投稿消去" do click_button "消去する" expect{ expect(page.accept_confirm).to eq "本当に削除しますか?" expect(page).to have_content "レビューを消去しました。" # ↑この一文かsleepが必要です。 }. to change(@user.posts, :count).by(-1) end endその他
slim= button_to "消去する",post_path(@post), method: :delete, data: {confirm: "本当に削除しますか?"}コントローラーdef destroy @post = current_user.posts.find(params[:id]) flash[:notice] = "レビューを消去しました。" @post.destroy redirect_to user_path(current_user.id) end
- 投稿日:2020-04-03T11:01:27+09:00
Remember機能を実装する~Railsチュートリアル9章~
第9章も終了しました。
9章では主にRemember機能の実装方法について学びました。
ここはあまり時間をかけずに少し足早に通りすぎましたね。記憶トークンやRemember me機能についてざっくりとだけ理解した感じです。というのも今回はログイン機能の発展型、応用であるためまだまだ覚えることがたくさんある初学者の自分としては現段階では優先度が低い内容なのかなと感じました。Remember me機能
ユーザーのログイン状態をブラウザを閉じた後でも有効にする機能を実装する。
またこの機能を使うかをユーザーに決めてもらうためチェックボックスをフォームに追加する。
Sessionの永続化を行うために、記憶トークン(要するにパスワードのようなもの)を生成しcookiesメソッドによる永続cookiesの作成や、安全性の高い記憶ダイジェストによるトークン認証に記憶トークンを活用する。パスワードとトークンの違い
パスワードはユーザーが作成・管理する情報
トークンはコンピュータが作成・管理する情報sessionメソッドで保存した情報は自動的に安全が保たれるが、cookiesメソッドに保存する情報はそうはなっていない。特に、cookiesを永続化するとセッションハイジャックという攻撃を受ける可能性があります。(記憶トークンを奪って、特定のユーザーになりすましてログインする)
cookiesを盗み出す有名な方法
(1) 管理の甘いネットワークを通過するネットワークパケットからパケットスニッファという特殊なソフトウェアで直接cookieを取り出す。
対策...Secure Sockets Layer (SSL) をサイト全体に適用して、ネットワークデータを暗号化で保護し、パケットスニッファから読み取られないようにしている。
(2) データベースから記憶トークンを取り出す。
記憶トークンをそのままデータベースに保存するのではなく、記憶トークンのハッシュ値を保存するようにする。
(3) クロスサイトスクリプティング (XSS) を使う。
Railsによって自動的に対策が行われます。具体的には、ビューのテンプレートで入力した内容をすべて自動的にエスケープする。
(4) ユーザーがログインしているパソコンやスマホを直接操作してアクセスを奪い取る。
ログイン中のコンピュータへの物理アクセスによる攻撃については、さすがにシステム側での根本的な防衛手段を講じることは不可能。二次被害を最小限に留めることは可能である。具体的には、ユーザーが (別端末などで) ログアウトしたときにトークンを必ず変更するようにし、セキュリティ上重要になる可能性のある情報を表示するときはデジタル署名 (digital signature) を行うようにする。
記憶トークンと暗号化
まずデータベースに種類=stringのremember_digest属性をApplicationに追加する。
記憶ダイジェスト用に生成したマイグレーション
class AddRememberDigestToUsers < ActiveRecord::Migration[5.0] def change add_column :users, :remember_digest, :string end endこれをデータベースに追加。次は記憶トークンの作成を行う。新しいトークンを作成するためにnew_tokenメソッドを作成する。このダイジェストメソッドではユーザーオブジェクトが不要となるためUserモデルのクラスメソッドとして作成する。
# ランダムなトークンを返す def User.new_token SecureRandom.urlsafe_base64 end end次にuser.rememberメソッドを作成する。これは
記憶トークンをユーザーと関連付け、トークンに対応する記憶ダイジェストをデータベースに保存する役割を担う。
またremember_token属性を設定しなければならない。
(要するにパスワードを生成するフェーズ)
attr_accessorを使って「仮想の」属性を作成する。class User < ApplicationRecord attr_accessor :remember_token # 永続セッションのためにユーザーをデータベースに記憶する def remember self.remember_token = User.new_token update_attribute(:remember_digest, User.digest(remember_token)) end endselfキーワードを与えると、この代入によってユーザーのremember_token属性が期待どおりに設定される。
update_attributeメソッドを使って記憶ダイジェストを更新することでこのメソッドはバリデーションを素通りさせる。ログイン状態の保持
記憶トークンをユーザーと関連付け、トークンに対応する記憶ダイジェストをデータベースに保存する役割が完成。
次にユーザーの暗号化済みIDと記憶トークンをブラウザの永続cookiesに保存して、永続セッションを作成する準備ができました。これを実際に行うにはcookiesメソッドを使います。
個別のcookiesは、1つのvalue (値) と、オプションのexpires (有効期限) からできている。ユーザーIDとcookiesに保存するにはcookies[:user_id] = user.idを使用するがこれではIDがそのままcookiesに保存されてしまう。
そのため署名付きcookiesを使用する。cookies.signed[:user_id] = user.idさらに有効期限20年cookiesを設定できるpermanentメソッドを追加するとこうなる。
cookies.permanent.signed[:user_id] = user.idcookiesを設定すると以後のビューでこのようにcookiesからユーザーを取り出せる。
User.find_by(id: cookies.signed[:user_id])cookies.signed[:user_id]では自動的にユーザーIDのcookiesの暗号が解除され、元に戻る。
続いて、bcryptを使ってcookies[:remember_token]がremember_digestと一致することを確認します# 渡されたトークンがダイジェストと一致したらtrueを返す def authenticated?(remember_token) BCrypt::Password.new(remember_digest).is_password?(remember_token) end end引数がremember_tokenとなっているが、これはattr_accessorで定義したremember_tokenとは無関係のただの引数であることに注意。ここにはログイン時にremember(user)メソッドで設定したcookieに保存されたremember_tokenが代入される。
つまり、remember_tokenを使って設定されたcookieとremember_digestカラムの値が一致するかを検証しており、一致すればtrueを返す。cookiesメソッドでユーザーIDと記憶トークンの永続cookiesを作成する。
# ユーザーのセッションを永続的にする def remember(user) user.remember cookies.permanent.signed[:user_id] = user.id cookies.permanent[:remember_token] = user.remember_token end永続セッションの場合は、session[:user_id]が存在すれば一時セッションからユーザーを取り出し、それ以外の場合はcookies[:user_id]からユーザーを取り出して、対応する永続セッションにログインする必要があります
if (user_id = session[:user_id])
`
@current_user ||= User.find_by(id: user_id)
elsif (user_id = cookies.signed[:user_id])
user = User.find_by(id: user_id)
if user && user.authenticated?(cookies[:remember_token])
log_in user
@current_user = user
endさらにcurrent_userヘルパーを定義する# 記憶トークンcookieに対応するユーザーを返す
def current_user if (user_id = session[:user_id]) @current_user ||= User.find_by(id: user_id) elsif (user_id = cookies.signed[:user_id]) user = User.find_by(id: user_id) if user && user.authenticated?(cookies[:remember_token]) log_in user @current_user = user end end end新しくログインしたユーザーは正しく記憶される。
ユーザーを忘れる
ユーザーがログアウトできるようにするために、ユーザーを記憶するためのメソッドと同様の方法で、ユーザーを忘れるためのメソッドを定義する。このuser.forgetメソッドによって、user.rememberが取り消さる。
# ユーザーのログイン情報を破棄する def forget update_attribute(:remember_digest, nil) end end永続Sessionからログアウトする
# 永続的セッションを破棄する def forget(user) user.forget cookies.delete(:user_id) cookies.delete(:remember_token) end # 現在のユーザーをログアウトする def log_out forget(current_user) session.delete(:user_id) @current_user = nil end2つの目立たないバグ
実は小さなバグが2つ残っている。
1つ目のバグ
ユーザーは場合によっては、同じサイトを複数のタブ (あるいはウィンドウ) で開いていることもあり、ログアウト用リンクはログイン中のみ表示される。
今のcurrent_userの使い方では、ユーザーが1つのタブでログアウトし、もう1つのタブで再度ログアウトしようとするとエラーになってしまう。これは、もう1つのタブでログアウトし、current_userがnilとなるため、log_outメソッド内のforget(current_user)が失敗してしまうからである。
→この問題を回避するためには、ユーザーがログイン中の場合にのみログアウトさせる必要がある。2番目の問題は、ユーザーが複数のブラウザ (FirefoxやChromeなど) でログインしていたときに起こります。例えば、Firefoxでログアウトし、Chromeではログアウトせずにブラウザを終了させ、再度Chromeで同じページを開くと起こる。例えばユーザーがFirefoxからログアウトすると、user.forgetメソッドによってremember_digestがnilになります。この時点では、Firefoxでまだアプリケーションが正常に動作しているがlog_outメソッドによってユーザーIDが削除されるため、ハイライトされている2つの条件はfalseになります。
記憶トークンcookieに対応するユーザーを返す
def current_user if (user_id = session[:user_id]) @current_user ||= User.find_by(id: user_id) elsif (user_id = cookies.signed[:user_id]) user = User.find_by(id: user_id) if user && user.authenticated?(cookies[:remember_token]) log_in user @current_user = user end end end結果として、current_userメソッドの最終的な評価結果は、期待どおりnilになる。
一方、Chromeを閉じたとき、session[:user_id]はnilになります (これはブラウザが閉じたときに、全てのセッション変数の有効期限が切れるためです)。しかし、cookiesはブラウザの中に残り続けているため、Chromeを再起動してサンプルアプリケーションにアクセスすると、データベースからそのユーザーを見つけることができてしまいます。
# 記憶トークンcookieに対応するユーザーを返す def current_user if (user_id = session[:user_id]) @current_user ||= User.find_by(id: user_id) elsif (user_id = cookies.signed[:user_id]) user = User.find_by(id: user_id) if user && user.authenticated?(cookies[:remember_token]) log_in user @current_user = user end end end結果として、次のif文の条件式が評価される。
user && user.authenticated?(cookies[:remember_token])このとき、userがnilであれば1番目の条件式で評価は終了するのですが、実際にはnilではないので2番目の条件式まで評価が進み、そのときにエラーが発生します。原因は、Firefoxでログアウトしたときにユーザーのremember_digestが削除してしまっているにもかかわらず、Chromeでアプリケーションにアクセスしたときに次の文を実行してしまうからです。
BCrypt::Password.new(remember_digest).is_password?(remember_token)上のremember_digestがnilになるので、bcryptライブラリ内部で例外が発生します。この問題を解決するには、remember_digestが存在しないときはfalseを返す処理をauthenticated?に追加する必要があります。
テスト駆動開発は、この種の地味なバグ修正にはうってつけです。そこで、2つのエラーをキャッチするテストから書いていくことにしましょう。まずはリスト 8.31の統合テストを元に、redになるテストを作成する。
リスト 9.14: ユーザーログアウトのテスト red
test/integration/users_login_test.rb
require 'test_helper'class UsersLoginTest < ActionDispatch::IntegrationTest . . . test "login with valid information followed by logout" do get login_path post login_path, params: { session: { email: @user.email, password: 'password' } } assert is_logged_in? assert_redirected_to @user follow_redirect! assert_template 'users/show' assert_select "a[href=?]", login_path, count: 0 assert_select "a[href=?]", logout_path assert_select "a[href=?]", user_path(@user) delete logout_path assert_not is_logged_in? assert_redirected_to root_url # 2番目のウィンドウでログアウトをクリックするユーザーをシミュレートする delete logout_path follow_redirect! assert_select "a[href=?]", login_path assert_select "a[href=?]", logout_path, count: 0 assert_select "a[href=?]", user_path(@user), count: 0 end endcurrent_userがないために2回目のdelete logout_pathの呼び出しでエラーが発生し、テストスイートは redになる。
次にこのテストを成功させます。具体的にはリスト 9.16のコードで、logged_in?がtrueの場合に限ってlog_outを呼び出すように変更します。
ログイン中の場合のみログアウトする green
app/controllers/sessions_controller.rb class SessionsController < ApplicationController . . . def destroy log_out if logged_in? redirect_to root_url end end2番目の問題は、統合テストで2種類のブラウザをシミュレートは困難である。その代わり、同じ問題をUserモデルで直接テストするだけなら簡単に行えます。記憶ダイジェストを持たないユーザーを用意し (setupメソッドで定義した@userインスタンス変数ではtrueになります)、続いてauthenticated?を呼び出す。この中で、記憶トークンを空欄のままにしていることにご注目ください。記憶トークンが使われる前にエラーが発生するので、記憶トークンの値は何でも構わないのです。
ダイジェストが存在しない場合のauthenticated?のテスト red
test/models/user_test.rb require 'test_helper' class UserTest < ActiveSupport::TestCase def setup @user = User.new(name: "Example User", email: "user@example.com", password: "foobar", password_confirmation: "foobar") end . . . test "authenticated? should return false for a user with nil digest" do assert_not @user.authenticated?('') end end上のコードではBCrypt::Password.new(nil)でエラーが発生するため、テストスイートは redになる。
このテストを greenにするためには、記憶ダイジェストがnilの場合にfalseを返すようにすれば良いauthenticated?を更新して、ダイジェストが存在しない場合に対応 green
app/models/user.rb class User < ApplicationRecord . . . # 渡されたトークンがダイジェストと一致したらtrueを返す def authenticated?(remember_token) return false if remember_digest.nil? BCrypt::Password.new(remember_digest).is_password?(remember_token) end# ユーザーのログイン情報を破棄する
def forget update_attribute(:remember_digest, nil) end endここでは、記憶ダイジェストがnilの場合にはreturnキーワードで即座にメソッドを終了している。処理を中途で終了する場合によく使われるテクニックである。
- 投稿日:2020-04-03T10:54:54+09:00
【初心者向け】FizzBuzz問題
環境,前提
Ruby 2.5.1
MacOS Mojave Ver.10.14.6本記事はRubyがインストールされた前提の記事です。
Rubyをインストールしたあと、とにかくRubyをいろいろ触ってみて慣れていくための記事です。お役に立てば幸いです。採用試験に出ました。
採用試験で実際に出題されたのでやはり大事なのかなと思い今回記録に残します。
FizzBuzz問題とは?
1から30まで順に数えて出力して行き、3で割り切れる数は「Fizz」5で割り切れる数は「Buzz」両方で割り切れる数は「FizzBuzz」と出力する、ようにプログラムを書く問題です。
これが書けるか書けないかでプログラマー志願者を仕分けられるようになったようです。実際今でも使われていますから押さえておいて損はないと思います。FizzBuzz詳細
私が書いたコード
実際の試験では「言語は問わない、形式も自由」だったので今回はRubyのWhile文を用いてやってみます。
FizzBuzz.rbi=1 while i <=30 if i%15==0 puts "FizzBuzz" elsif i%3==0 puts "Fizz" elsif i%5==0 puts "Buzz" else puts i end i+=1 end他にもfor文やeach文でも実現可能なのでぜひやってみてください!!
- 投稿日:2020-04-03T09:45:44+09:00
Ruby入門メモ(プログラミング経験者用)2
TIPS
定数
英大文字で定義する(慣習的に全部大文字)。
VERSION = 1.1オブジェクトの型変換
to_i : 数値変換
to_f : 浮動小数点変換
to_s : 文字列変換number = 100 f_number = number.to_i * 0.01 l_number = number.to_s + "円" d_number = f_number.to_i puts "#{number} #{f_number} #{l_number} #{d_number}"[実行結果]
100 1.0 100円 1書式付きの値埋め込み
%d 整数
%f 浮動小数
%s 文字列puts "文字列と書式埋め込み" %[対応する値]
※埋め込む書式が複数なら配列で渡す。diagram = "円" radius = 1 ratio = 3.14 puts "半径%dcmの%sの直径は%.2fcmです" %[radius, diagram, 2 * ratio * radius][実行結果]
半径1cmの円の直径は6.28cmですハッシュ
C#でいう辞書、連想配列。
keyとそれに対応する値(value)をもつ。
型は数値でも文字列でも可能presents = {"日本" => "お肉券" , "米国" => "お金", "英国" => "お金" } puts presents["日本"] awards = { 1 => "金" , 2 => "銀" , 3 => "銅" } puts awards[1][実行結果]
お肉券 金ハッシュのメソッド
size :サイズを返す
keys :キーを返す
values :値を返すpresents = {"日本" => "お肉券" , "米国" => "お金", "英国" => "お金" } puts presents.size puts "-----------------" puts presents.keys puts "-----------------" puts presents.values puts "-----------------"[実行結果]
3 --------------------- 日本 米国 英国 --------------------- お肉券 お金 お金 ---------------------Case
CやC#のswitch文に対応する。
case 変数 when パターン1 処理 when パターン2 処理 endinput = gets.chomp case input when "red" puts "止まれ!" when "yellow" puts "警戒!" #条件を複数扱いたいときはコンマで区切る when "blue","green" puts "進め" end[実行結果]
red 止まれ!ループ
for文は入門メモ1参照
while
while 条件 do 処理 endinput = gets.chomp.to_i i = 0 while i < input do puts "あ" i += 1 end[実行結果]
3 あ あ あ途中でループ処理から抜けたいときは「break」
一回ループをスキップするときは「next」
条件文(if文)と共に扱うことが多い。times
ループする回数が決まっているときに使う。
回数.times do |変数| 処理 endinput = gets.chomp.to_i input.times do |x| puts "あ" end[実行結果]
3 あ あ あ参考文献
Progate https://prog-8.com/slides?lesson=72%2C75%2C78%2C81%2C85
ドットインストール https://dotinstall.com/lessons/basic_ruby_v3
- 投稿日:2020-04-03T09:42:59+09:00
Helmで導入したRabbitMQをRuby(Bunny)から利用した時のメモ
はじめに
アプリケーション同士を連携させるのに、必ずしもAPIをHTTPで呼び出すことが適切でない場合もあります。
特にQueueに入れた処理を逐次処理させる場合には、アプリケーションが依頼を直接受け取ってしまうと、アプリ側で永続化の処理を考えなければいけませんし、負荷分散も難しくなります。とりあえずPub/Subよりも単純なQueueをRabbitMQで実現する予定なので、その作業のメモを残しておきます。
前提
- Kubernetes v1.15.11 (by Kubespray v2.11.2)
- Rook/Cephが導入されている
- MetalLBが導入されている
References
次の資料を参考にしました。
- https://github.com/ruby-amqp/bunny
- http://rubybunny.info/articles/getting_started.html
- https://qiita.com/suketa/items/bb4a8c294351cb7b9025
- https://qiita.com/suketa/items/147aad2f0f583b1871c2
- https://stackoverflow.com/questions/39646442/using-bunny-how-to-set-x-max-length-when-connecting-to-existing-queue
HelmでのRabbitMQの導入
やり直すことを考えて、処理のためにMakefileを準備しておきます。
MakefileREPO_NAME = stable/rabbitmq NAMESPACE = rabbitmq REL_NAME = myrabbitmq RMQ_OPTIONS = --set persistence.storageClass=rook-ceph-block \ --set replicas=2 \ --set service.type=LoadBalancer \ --set persistence.storageClass=rook-ceph-block \ --set rabbitmq.erlangCookie=9a63d47049016fd933371a76af08fc8f \ --set rabbitmq.password=70550b0ac43a2e5c .PHONY: init update fetch install upgrade init: kubectl create ns $(NAMESPACE) update: helm repo update fetch: helm fetch $(REPO_NAME) install: helm install $(REPO_NAME) --name $(REL_NAME) --namespace $(NAMESPACE) $(RMQ_OPTIONS) upgrade: helm upgrade ${REL_NAME} $(REPO_NAME) --namespace $(NAMESPACE) $(RMQ_OPTIONS)導入の際の手順はおおむね次ようなものです。
k8s-masterノードでの作業$ make fetch $ ls Makefile rabbitmq-6.18.2.tgz ## tgzファイルを展開し、values.yamlの内容を確認する $ tar xvzf rabbitmq-6.18.2.tgz $ less rabbitmq/values.yaml ## 他に変更する点がなければ導入する、あればMakefileのRMQ_OPTIONSに追記 $ make installRMQ_OPTIONSに設定していた"service.type=LoadBalancer"の部分は環境に合わせて変更が必要だと思います。設定可能な項目については、rabbitmq/values.yamlを確認してください。
service.typeにLoadBalancerを指定しているので、4369,5672,15672の全ポートが公開されてしまっています。これで問題なければ良いですが、選択的にポートを絞って公開したい場合には、service.type=ClusterIPを指定したまま次のようなServiceを指定することもできます。
metadata.name,各namespace等は環境に合わせて変更してくださいapiVersion: v1 kind: Service metadata: name: my-release-rabbitmq-lb labels: app: rabbitmq namespace: rabbitmq spec: ports: - name: amqp port: 5672 protocol: TCP targetPort: amqp - name: stats port: 15672 protocol: TCP targetPort: stats selector: app: rabbitmq release: myrabbitmq type: LoadBalancerRabbitMQの準備作業
現在のサービスは次のように公開されています。
svcの状態$ kubectl -n rabbitmq get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE myrabbitmq LoadBalancer 10.233.25.181 192.168.1.120 4369:31028/TCP,5672:32031/TCP,25672:31175/TCP,15672:32214/TCP 35m myrabbitmq-headless ClusterIP None <none> 4369/TCP,5672/TCP,25672/TCP,15672/TCP 35mhttp://192.168.1.120:15672/ から、RabbitMQのWeb UIにアクセスします。
Helmから導入したRabbitMQにログインする場合は、それぞれ、--setで、rabbitmq.username (default:user), rabbitmq.password に指定したものを利用します。今回は (username,password) = (user, 70550b0ac43a2e5c) を使用します。Queueの作成
WebUIのQueuesタブをクリックし、新規のQueueを追加します。
- Name: testq
- Durable: yes
接続用Userの作成
Adminタブから新規のユーザーを追加します。
追加したら、そのユーザー名をクリックし、権限を付与します。
- username: user01
- password: secret
今回はテストなので、作成したユーザーにデフォルトで権限を付与しておきます。
- vhost: /
- Configure regexp: .*
- Write regexp: .*
- Read regexp: .*
Bunnyからの接続
Rubyのアプリケーションを作成するディレクトリに、Gemfileを準備し、bundleからlibディレクトリに配置します。
Gemfilesource 'https://rubygems.org' gem "bunny"bunnyライブラリのダウンロード$ bundle install --path libPut/Getのテスト
準備はできたので、次のようなRubyスクリプトを配置します。
put.rb#!/usr/bin/ruby # require 'bundler/setup' Bundler.require require 'bunny' conn = Bunny.new(host: "192.168.1.120", vhost: "/", user: "user01", password: "secret") conn.start ch = conn.create_channel ## x-max-lengthなどをQueueに設定している場合には、次のように:argumentsに設定を加える q = ch.queue("testq", durable: true, arguments: { 'x-max-length' => 1024 , 'x-max-length-bytes' => 1048576, 'x-queue-type' => 'classic' } ) ## arguments:の設定はQueue定義に応じて要変更 q.publish("Hello", persistent: true) ch.close conn.closeここでは、送信するだけで結果は受け取れません。受信用には次のスクリプトを作成しています。
get.rb#!/usr/bin/ruby # require 'bundler/setup' Bundler.require require 'bunny' conn = Bunny.new(host: "192.168.1.120", vhost: "/", user: "yasu", password: "zaq12wsx") conn.start ch = conn.create_channel q = ch.queue("testq", durable: true, arguments: { 'x-max-length' => 1024, 'x-max-length-bytes' => 1048576, 'x-queue-type' => 'classic' } ) ## arguments:の設定はQueue定義に応じて要変更 puts "Message Count: #{q.message_count}" delivery_info, metadata, payload = q.pop puts "Received: #{payload}" ch.close conn.closePub/Subのテスト
実際には、アプリケーション側でメッセージが届くまでWaitしたいので、get.rbのコードを少し変更しました。
sub.rb#!/usr/bin/ruby # require 'bundler/setup' Bundler.require require 'bunny' conn = Bunny.new(host: "192.168.1.120", vhost: "/", user: "yasu", password: "zaq12wsx") conn.start ch = conn.create_channel q = ch.queue("testq", durable: true, arguments: { 'x-max-length' => 1024, 'x-max-length-bytes' => 1048576, 'x-queue-type' => 'classic' } ) ## arguments:の設定はQueue定義に応じて要変更 q.subscribe(manual_ack: true) do |delivery_info, metadata, payload| puts "-------" puts "Message Count: #{q.message_count}" puts "routing_key: #{delivery_info.routing_key}" puts "Received: #{payload}" ch.ack(delivery_info.delivery_tag) sleep 10 end ## waiting for never loop { sleep 5 } ch.close conn.close以上
- 投稿日:2020-04-03T00:34:07+09:00
Reactのチュートリアルの三目並べで少しRails使う #2
前回の続きになります。
手順を履歴に保持します。
履歴機能を作成
index.html.erb<head> <!--<link rel="stylesheet" href="css/index.css" />--> <!--<script src="js/index.js"></script>--> <%= javascript_pack_tag 'tictactoes', 'data-turbolinks-track': 'reload' %> </head> <body id="tictactoes-body"> <%= form_with(url: "/tictactoes/sqClick", method: "post") do |f| %> <div id="root"> <div class="game"> <div class="gmae-board"> <div> <% @Squares.each.with_index do |sq, i| %> <% if i % 3 == 0%> <div class="board-row"></div> <% end %> <%= f.text_field '', :name => "item[]", :class => "square", :readonly => true , :value => sq.content%> <% end %> </div> </div> <div class="game-info"> <div><%= @nextStepMessage %></div> <div> <li><button type="button">Go to game start</button></li> <% (0...@buttonCount).each do |num| %> <li><button type="button">Go to move #<%= num + 1 %></button></li> <% end %> </div> </div> </div> </div> <%= f.hidden_field :clickButtonIndex, :value => @clickButtonIndex %> <%= f.hidden_field :clickNo, :value => @clickNo %> <%= f.hidden_field :stepNumber, :value => @stepNumber %> <% end %> </body>履歴ボタンのところはサーバーサイドでボタンの数を埋め込む形としました。
実装中に@buttonCount
を定義し忘れることがあって、そのまま動かすとブラウザが固まって、CPUとメモリが上昇し続けるという状態になり、めっちゃハマりましたorz
PCの寿命かとも思いましたw
0...@変数
みたいな使い方したらダメなんでしょうね多分
※SCSSは前回と同様です。tictactoes.js(function() { window.addEventListener("DOMContentLoaded", () => { // 全square document.querySelectorAll(".square").forEach((element, index) => { // クリックイベント element.addEventListener("click", squareClick.bind(element, index)); }); // 全button document .querySelectorAll(".game-info li > button") .forEach((element, index) => { // クリックイベント element.addEventListener("click", historyButtonClick.bind(null, index)); }); let isClick = false; function squareClick(index) { // 2度押し防止、既に値が埋まっているか if (isClick || this.value) { return; } else { isClick = true; } document.getElementById("clickNo").value = index; document.forms[0].submit(); } function historyButtonClick(index) { // 2度押し防止 if (isClick) { return; } else { isClick = true; } document.getElementById("clickButtonIndex").value = index; document.forms[0].action = "/tictactoes/hysBtClick"; document.forms[0].submit(); } }); })();js側はなんだかんだ結構記述しました・・・。
routes.rbRails.application.routes.draw do get '/' => 'home#top' get 'posts/index' => 'posts#index' get 'top' => 'home#top' get 'about' => 'home#about' get 'tictactoes' => 'tictactoes#index' get 'tictactoes/sqClick' => 'tictactoes#index' get 'tictactoes/hysBtClick' => 'tictactoes#index' post 'tictactoes/sqClick' => 'tictactoes#sqClick' post 'tictactoes/hysBtClick' => 'tictactoes#hysBtClick' # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html end現状POSTした後のページでF5を押すと、CSRFチェック?に引っ掛かり、エラーになっていたのでルーティングを追加しました。
普通こんなやり方しないのでしょうか。
tictactoes_controller.rbclass TictactoesController < ApplicationController #初期画面表示 def index # 手順1以降を削除して初期化 Square.where.not(stepNumber: 0).delete_all #手順0を全件取得 @Squares = Square.all #手順の開始は0から @stepNumber = 0 @clickButtonIndex = 0 @buttonCount = 0 #次の手番の文字列 @nextStepMessage = "次の手番: X" end #マス目クリック def sqClick #現在の手順インクリメント stepNumber = params[:stepNumber].to_i @stepNumber = stepNumber.succ #マス目の配列 items = params[:item] sq = Square.new #既に勝敗が着いた if sq.calculateWinner(items) return else xIsNext = stepNumber % 2 == 0 clickButtonIndex = params[:clickButtonIndex] #XとOを設定 items[params[:clickNo].to_i] = xIsNext ? "X": "O" isWinner = sq.calculateWinner(items) #現在より後ろの手順は削除 Square.where("stepNumber > ?", clickButtonIndex).delete_all #新しい手順をDBに保存 sq.saveSquare(@stepNumber, items) #次の手番の文字列更新 if isWinner @nextStepMessage = "勝者:" + (xIsNext ? "X": "O") else @nextStepMessage = xIsNext ? "次の手番: O": "次の手番: X" end #最新の手順を取り直す @Squares = Square.where(stepNumber: @stepNumber) end @buttonCount = @stepNumber #indexのhtmlを使いまわす render action: :index end #履歴ボタン押下 def hysBtClick @clickButtonIndex = params[:clickButtonIndex].to_i @Squares = Square.where(stepNumber: @clickButtonIndex) #contentの配列を作る items = @Squares.map { |s| s.content } sq = Square.new if sq.calculateWinner(items) @nextStepMessage = "勝者:" + (@clickButtonIndex % 2 == 0 ? "O": "X") else @nextStepMessage = @clickButtonIndex % 2 == 0 ? "次の手番: X": "次の手番: O" end @stepNumber = @clickButtonIndex @buttonCount = Square.count / 9 - 1 render action: :index end endjsでやっていた時のように
xIsNext
変数は削除し、stepNumber
の余りで次の手順を判断するようにしました。Square.rbclass Square < ApplicationRecord Lines = [ [0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6] ] def saveSquare(stepNumber, items) #新しい手順のマス目保存処理 (1..9).each do |i| Square.create(stepNumber: stepNumber, squareNumber: i, content: items[i - 1]) end end #勝敗判定関数(公式チュートリアルから拝借) def calculateWinner(items) Lines.each do |l| a, b, c = l if items[a] != "" && items[a] == items[b] && items[a] == items[c] return true end end return false end endモデルはほぼ変更はないです。
ネットで調べると、今の時代for
はあまり使わないとのことなのでeach do
にしました。
なぜ使わないのかをちゃんと理解しないとな・・・感想
formをsubmitしているんですんごいもっさりします。
いずれajaxで組んでみようと思います。
その場合はクライアントにはVue.js
が使えたらいいなぁと思います。Railsのお作法や知識が浅いだけかもしれませんが、今のところあまりRailsのメリットが感じられませんでした。
独特な記法すぎて、他のプログラミング言語と類似点が見つけにくく、習得しにくいし、習得しても他のプログラミングを覚えるときに役に立ちにくいと感じました。これからも触っていっていつか本感想を振り返ってみたいと思います。
- 投稿日:2020-04-03T00:28:14+09:00
【Rails】collectionとmemberの違い
はじめに
学習中の備忘録です。
概要
7つの基本アクション以外でルーティングを定義する時の、collectionとmemberの違い。
結論:collectionはルーティングに:idがつかない、memberは:idがつく。前提
- rails 5.2.3
- postsテーブルを検索するsearchアクションを定義
- 今回はcollectionとmemberの違いのみ。searchメソッドは定義済とする。
collectionで定義した場合
Rails.application.routes.draw do resources :posts do collection do get 'search' end end endcollectionのルーティング
Prefix Verb URI Pattern search_posts GET /posts/search(.:format) posts#searchmemberで定義した場合
Rails.application.routes.draw do resources :posts do member do get 'search' end end endmemberのルーティング
Prefix Verb URI Pattern search_post GET /posts/:id/search(.:format) posts#searchまとめ
URIの指定先が、collectionが:idなし、memberが:idありとなっていることが確認できます。