- 投稿日:2020-12-12T23:48:14+09:00
Ruby リファクタリング
最近は、コードを書いていて
短く書くかつ、わかりやすくかけるように意識しています
・コードレビューで指摘される
・普段からリファクタリングを意識して書くのでは大違いだと思うから
ということで、記法をいくつかみてまとめてみます繰り返し使う文字列をlocaleに定義する
コードをDRYにする
時刻のフォーマット等もlocaleを使い、strftimeは基本使わない。localeとは…
Railsガイド
住所、会社名、時間をフォーマットする時の形式、
今日の日付を出力する時の形式などを管理する。
使い方
これは、業務でも使っている。何度も使う文言とかをまとめている
テンプレのようなもの埋め込みRuby
文字の中にRubyの式を埋め込む
リファクタ前
name = '太郎' puts "こんにちは、 " + name + "!" #=> "こんにちは、太郎さん"リファクタ後
name = '太郎' puts "こんにちは、#{name}さん" #=> "こんにちは、太郎さん"
シングルクォーテーション( '' )だと、
式展開されないので注意する三項演算子
リファクタ前
def click_button if controller.action_name == 'new' puts "New" else puts "Nothing" end endリファクタ後
def click_button controller.action_name == 'new' ? puts "New" : puts "Nothing" end複雑なif文を書き換えるのには、向いていないので
なるべくシンプルな if else文で使うとよさそう三項演算子をネストさせたりすると、かえって
可読性が悪くなる後置if
リファクタ前
def walk if user.people? puts '歩きます' end endリファクタ後
def walk puts '歩きます' if user.people? endこちらも、多用するのではなく
こちらの記事を見るのがよさそうぼっち演算子
def find_currency(country) currencies = { japan: 'yen', us: 'dollar' } currencies[country] end def show_currency(country) currency = find_currency(country) if currency currency.upcase end end show_currency(:japan) => "YEN" show_currency(:brazil) => nilさらにこう書ける
def show_currency(country) #条件分岐内で直接変数に代入する(値が取得できれば真、できなければ偽) if currency = find_currency(country) currency.upcase end endRubyでは、変数への代入自体が戻り値を持つため、if文の中で
直接、変数に代入することも可能。
しかし、これだと「=」と「==」を書き間違えたのではと勘違いされる可能性もある。
nilかもしれないオブジェクトに対して、安全にメソッドを呼び出したい場合には
ぼっち演算子を使うことができる
メソッドを呼び出したオブジェクトがnilではない場合→その結果
メソッドを呼び出したオブジェクトがnilだった場合→nil
を、それぞれ返すリファクタ後
def find_currency(country) currencies = { japan: 'yen', us: 'dollar' } currencies[country] end def show_currency(country) currency = find_currency(country) currency&.upcase end||= (nilガード)
nilが入らないようにするもの
変数に値を入れるときに、変数がnilかfalseのときのみ値を入れることができるpry(main)> user = nil => nil pry(main)> user ||= '太郎' => "OK" pry(main)> user => '太郎'既に値が入っている場合は値の代入は行われない
pry(main)> user = '次郎' => '次郎' pry(main)> user ||= '三郎' => '次郎'引数
メソッドにわたす引数の種類ですが、割とたくさんあるので
まとめますデフォルト引数
引数にデフォルト値を入れておいて、引数がなければデフォルト値を使用する
def putsHello(msg="No msg", name="No name") puts(msg + "," + name + "¥n") end putsHello("Hello", "Yamada") #=> 'Hello, Yamada' putsHello("Hello") #=> 'Hello, No name' putsHello() #=> 'No msg, No name'キーワード引数
・ハッシュを作成したときのようにシンボル: 値で指定する
・デフォルト値の役割も果たす
・呼び出す際に、順番も自由に入れ替えることができるdef buy_burger(menu, drink: true, potato: true) if drink && potato puts "#{menu}バーガーセットとドリンクとポテトを頼みました" elsif drink puts "#{menu}バーガーセットとドリンクを頼みました" elsif potato puts "#{menu}バーガーセットとポテトを頼みました" else puts"#{menu}バーガーセットを頼みました" end end buy_burger("cheese") #=> "cheeseバーガーセットとドリンクとポテトを頼みました" buy_burger("cheese", drink: false, potato: true) #=> "cheeseバーガーセットとポテトを頼みました" buy_burger("fish", potato: false, drink: false) #=> "fishバーガーセットを頼みました"可変長引数
・引数を配列として受け取ることができる
・引数名に*をつけるdef order(*food) puts "#{food.join('と')},お願いします!" end order('ハンバーガー') #ハンバーガー,お願いします! order('ハンバーガー','ポテト') #ハンバーガーとポテト,お願いします! order('ハンバーガー','ポテト','コーラ') #ハンバーガーとポテトとコーラ,お願いします!参考記事
チェリー本
https://qiita.com/jnchito/items/dedb3b889ab226933ccf
https://qiita.com/d0ne1s/items/fabbc0df7c273cf04fc3
三項演算子
三項演算子
ぼっち演算子
- 投稿日:2020-12-12T23:27:34+09:00
Ruby のハッシュには 3 種類の記法がある,わけじゃない
Ruby の初心者向けの記事によく以下のような記述がありますが,間違いです。
ハッシュには以下のように 3 種類の記法があります。
{"foo" => 3, "bar" => 4}
{:foo => 3, :bar => 4}
{foo: 3, bar: 4}二つ目と三つ目は同じハッシュを別の記法で書いていますが,一つ目はキーが文字列なので,記法が違っているのではなく,表しているモノが違います。
Ruby のハッシュは,キーとして文字列,シンボル,数値などさまざまなものが使えます。
そして,キーがシンボルの場合に限り,ハッシュ式においてキーと値のペアを指定する方法に二通りの記法がある,ということなのです。
なお,「ハッシュ式」というのは,上の例にあるような,{ }の中にキーと値のペアを列挙して Hash オブジェクトを表す式のことです。
- 投稿日:2020-12-12T23:13:49+09:00
Ruby は 1 行ずつ読み込んで実行していく,わけじゃない
Ruby の初心者向け記事によく
Ruby は C のようなコンパイラーではなくインタープリターです。プログラムを 1 行ずつ読み込んで実行していきます。
というような記述があります(記述のバリエーションはかなりある)。
間違いです。正直なところ,この問題に関して私も完全に理解してるわけではありませんが,分かる範囲で誤解を解きたいと思います。
Ruby の処理系1がプログラムを実行する方式は,Ruby 1.9 以降とそれまでとで大きく違っています。
どちらも,Ruby の処理系は,プログラムを読み込むと構文解析を行って,「抽象構文木」(AST: Abstract Syntax Tree)というものを構築します。
Ruby 1.9 未満では,出来上がった抽象構文木のノードを辿りながら処理を実行していきます(知らんけど)。
Ruby 1.9 以降は,抽象構文木を RubyVM という仮想機械の命令列(バイトコードと呼ぶ)に変換し,それを実行していきます。バイトコードへの変換は一種のコンパイルですね。
ですから,昔も今も,Ruby の処理系は「スクリプトを 1 行読み込んでは解釈して実行し,次の行を読み込んでは解釈して実行し,を繰り返す」などということはやっていませんし,Ruby 1.9 以降は一種のコンパイルも行っています。
さらに,Ruby 2.6 になると,JIT コンパイルという技術も導入されました2。
これは,RubyVM バイトコードの,頻繁に実行される箇所を実行中に C 言語に変換してコンパイルし,機械語にして実行する,という仕組みです(知らんけど)。何だか難しいですね。私も分かりません。
ただ,「1 行ずつ読み込んで実行していく」が間違っていることだけ分かってもらえれば記事の目的は果たせました。
ついでに,近年,処理系の技術がものすごく向上・複雑化していて,何がインタープリターで何がコンパイラーか,素人には分からなくなっている,ということも言えそうです。識者のツッコミ・助言を歓迎します。
- 投稿日:2020-12-12T22:42:53+09:00
Ruby のローカル変数名は小文字で始めなければならない,はウソ
Ruby の初心者向け記事によく
変数名は小文字で始めなければならない
ということが書かれています。
ここでいう「変数名」は「ローカル変数の名前」のことでしょうね。変数にはグローバル変数やインスタンス変数もあるので,ローカル変数なら「ローカル変数」と書いたほうがいいと思います。それはさておき,「小文字で始めなければならない」は誤りです。
まず,アンダースコア(_)で始めることが可能です。
それから,やろうと思えば非 ASCII 文字を使うことも可能です。年齢 = 108 나이 = 108 aĝo = 108 වයස = 108この四つの行はすべてローカル変数への代入です。
全角スペースなんかも使えます。まあやめといたほうがいいでしょうけど。ただし,非 ASCII 文字であっても,大文字で始まっていたら定数です。
# ギリシャ文字の例 ηλικία = 108 Ηλικία = 108 # 全角英字の例 Age = 108 p defined?(ηλικία) #=> "local-variable" p defined?(Ηλικία) # => "constant" p defined?(Age) # => "constant"
- 投稿日:2020-12-12T22:22:06+09:00
Ruby の真/偽と true/false は違う
Qiita の Ruby の初心者向け記事で,
true/falseに関して,誤ったもしくは誤解を招く表現を非常によく目にします。
たとえばif は条件が true のときに・・・を実行し,false のときは実行しません
とか
Ruby では nil と false だけが false で,それ以外のオブジェクトは true です。
といったような。
明らかに 真/偽 とtrue/falseを混同しています。「えっ? 日本語と英語だけの違いじゃないの?」って?
ハイ,確かに単語の一般的意味として,真=true,偽=false という対応はあります。
しかし,Ruby について日本語で書かれた文章に「true」「false」とあったら,それは「真」「偽」という意味ではなく,次に述べるある特定のオブジェクトを指しています1。Ruby の
trueは,TrueClassというクラスのインスタンスです2。
同様にfalseはFalseClassというクラスのインスタンスです。えっと,厳密にいえば,
trueという識別子は「擬似変数」と呼ばれるものであって,この擬似変数が指しているオブジェクトがTrueClassのインスタンスです。
これを「trueはTrueClassのインスタンス」と縮めてもとくに誤解の余地はないので,しばしばそう表現されます。一方,Ruby ではどんなオブジェクトも真偽値として使うことができます。
そのルールは極めて単純で,nilとfalseだけが偽であり,他のどんなオブジェクトも真である,というものです。
ですから,if "hoge" puts "いえーい" endは
いえーいを表示します。ではどうして
trueやfalseがあるかというと,これらはそれぞれ「真の代表値」「偽の代表値」であると考えればいいでしょう。なお,さきほど「真偽値」という言葉を使いました。
この記事では,真偽性を利用する値という意味で使っています。平たく言うと,条件式の値のことですね。
しかし,人によっては「真偽値」を「trueまたはfalse」という意味で使っていることもあるので気をつけてください。
- 投稿日:2020-12-12T21:52:19+09:00
Railsのモデルのpolymorphicの使い方
はじめに
モデルのpolymorphicに関してちょっとまとめます
マイグレーションファイルの作成
commentモデルを下記の様に作成します。
class CreateComments < ActiveRecord::Migration[6.0] def change create_table :comments do |t| t.references :target, null: false, polymorphic: true t.text :body, null: false t.timestamps end end end
t.references :target, null: false, polymorphic: trueこれによって、target_typeとtarget_idカラムが追加されます。
target_typeには対象のモデル名が入り、target_idには対象のデータのidが入ります。
これによって、どのモデルとでも関連を結ぶことができます。モデルファイルの修正
commentモデルとuserモデルが紐づく場合
commentのモデルファイルには
class Comment < ApplicationRecord belongs_to :target, polymorphic: true endと記載し、userのモデルファイルには
class User < ApplicationRecord has_many :comments, :as => :target endと記載します。
データの作成
user.comments.create(body: 'hello')この様にしてデータを作成できます。
もちろん、別のモデルでも
has_many :comments, :as => :targetと記載するだけで、紐付けをすることができるので便利です。終わりに
active storageに関してもこのポリモーフィックを使って、どのモデルに対しても画像を紐付けれる様にしています。
- 投稿日:2020-12-12T21:49:55+09:00
Ruby の変数は箱じゃない
Ruby の初心者向けの記事で
変数とは値を入れる箱のようなものです
とか
変数とは名前の付いた箱です
といった記述を非常によく目にします。
しかし,Ruby の場合,変数を箱のアナロジー(類推)で捉えようとすると困ったことになります。
以下のコードを見てみましょう。
a = "Ruby" b = a a.upcase! p a # => "RUBY" p b # => "RUBY"1 行目で,ローカル変数
aに文字列"Ruby"を代入しました。
2 行目で,そのaの値をローカル変数bに代入しました。
3 行目で,aの値である文字列オブジェクトを破壊的に大文字化しました。
そして,aの値とbの値を表示させました。
どちらも大文字のRUBYが表示されました。これは,
aの値とbの値が同じオブジェクトであることを意味しています。
「同じ」というのは「内容的に同じ」という意味ではありません。bの値はaの値そのものということです。
「完璧に同じ姿をした二人の人物」ではなく「同一人物」ということです。いったい,二つの箱の中に同時に一つの物体が入っている,などということが想像できるでしょうか。
これは箱のアナロジーが破綻していることを意味しています1。アナロジーというものは,〈よく理解している似たもの〉を持ってきて,その類似性を手がかりにして〈よく知らないもの〉を理解したり推し量ったりすることですね。
このとき,何もかも似ている必要はなくて,違っているところがあっても構いません。しかし,その違っている部分のために理解に支障をきたすのであれば,そのアナロジーは適切ではなかったということになります。
Ruby の変数の箱のアナロジーは混乱を招くと私は考えています。ではどのようなアナロジーならいいのでしょうか。
私は「名札」を推します。代入とは「オブジェクトに名札を紐付けること」です。まあ,木の札に名前が書いてあって,そこからひゅるひゅると紐が伸びていてその先に物体が括り付けられている様子でも思い浮かべてください
変数の値とは「名札に紐付いたオブジェクト」です。
一つのオブジェクトにはいくつでも名札を紐付けることができます。
しかし,一つの名札を同時に複数のオブジェクトに紐付けることはできません。上に掲げたコードは以下のように表現できます。
1 行目:文字列オブジェクト
"Ruby"に名札aを紐付けました。
2 行目:名札aに紐付いたオブジェクトに名札bを紐付けました。
3 行目:名札aに紐付いた文字列オブジェクトを破壊的に大文字化しました。二つの名札には同じオブジェクトが紐付いているので,表示させるオブジェクトをどちらの名札で指定しても,当然同じ結果になります。
- 投稿日:2020-12-12T21:48:59+09:00
【Ruby超入門】変数宣言と代入(ローカル変数)
変数宣言と代入
変数の宣言と代入をするには
変数名 = 式や値
と書く。a = "aloha" b = 1 + 2これで 変数aとbにそれぞれ aloha 1+2 を代入。
ちなみに変数名はスネークケースで書くのが慣習となっている。
スネークケース: animal_dog
単語の間はアンダースコア_で区切る記法。
※アルファベット小文字,アンダースコアで書き始める。
- 投稿日:2020-12-12T21:40:45+09:00
ActiveRecordで生SQLを実行して結果を配列にキャストする
※ Railsのバージョンは5.2を想定
何はともあれ生SQLを実行するコードを書く
result = ActiveRecord::Base.connection.select_all('SELECT x_id as id FROM users')取得結果から配列にしたいSELECT結果を指定して抜き出し、新たな配列を生成する
ids = result.map { |h| h['id'] } pp ids # [1, 2, 3, 4, 5, 6, 7]
- 投稿日:2020-12-12T21:32:54+09:00
【Ruby超入門】ファイルでRubyプログラムを実行する
Rubyプログラムをファイルで実行する。
なんでもいいので,サンプルとなるコードを書き,「○○○○.rb」というファイル名で保存する。
○○○○.rba = "hello" puts aその後,ターミナルから直接ファイル名を指定することで,実行できる。
$ ruby ○○○○.rb hello
- 投稿日:2020-12-12T21:31:46+09:00
groupdateを使った際にGroupdate::Errorが出た話
はじめに
今回railsで簡単なグラフを作成する際にchartkickなどと一緒に使うgemのgroupdateを使った際に
出たエラー解決を備忘録として残します。
初学者のため間違えばあればご指摘頂けると幸いです。Groupdate::Errorが出た
chartkickとgroupdateを無事導入しいざ実装と思った時にGroupdate::Errorの文字が、、、
bundleの確認やサーバーの再起動を試したが特に変化なし。エラーが出た理由
エラー文を詳しく確認してみると
Groupdate::Error - Be sure to install time zone support - https://github.com/ankane/groupdate#for-mysql:
これはタイムゾーンサポートをこのURLからインストールしろと言われてました。解決策
URL先でタイムゾーンサポートについての記述があり、そこで指定された以下のコマンドをターミナルで実行
$ mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysql最後に
無事エラーは解消されグラフ機能を実装する事ができました!
エラー文は苦手意識があるのですが、しっかり読解しようとすれば案外解決方法を提示してくれてる場合が多いので今後はしっかりエラーと向き合っていきます!!
- 投稿日:2020-12-12T21:24:53+09:00
【Ruby超入門】irbで動かす。
irbでRubyを動かそう
Rubyには対話型評価環境が用意されているので,ターミナル上でRubyを実行できる。
ターミナル上でirbを実行しよう。
$ irbと入力すると,irbが起動し,実行できる。
irb(main):001:0> 3+5 => 8気軽にRubyの実行ができるので試してみよう。
ちなみに終了するには,irb(main):002:0> exitと入力するとよい。
- 投稿日:2020-12-12T21:24:53+09:00
【Ruby超入門】irbでRubyプログラムを実行する
irbでRubyを動かそう
Rubyには対話型評価環境が用意されているので,ターミナル上でRubyを実行できる。
ターミナル上でirbを実行しよう。
$ irbと入力すると,irbが起動し,実行できる。
irb(main):001:0> 3+5 => 8気軽にRubyの実行ができるので試してみよう。
ちなみに終了するには,irb(main):002:0> exitと入力するとよい。
- 投稿日:2020-12-12T20:46:05+09:00
yamlファイルとは
はじめに
現在Railsを勉強中です。
アプリケーションを作成する上で、モデルやカラム、その他メッセージ等を日本語で出力したり扱いたい場面が必ずあると思います。自分の場合、すぐに忘れてしまうためその都度ググって、書き方合ってたかを確認しています。
ぶっちゃけYAMLファイルの仕様等を詳細に把握していなくても、問題なく作業が進むので今までないがしろにしてきましたが、今回は基本的な部分を調べて、アウトプットしようと思います。YAMLファイルの特徴
分かりやすさ
構造化されたデータを扱うためのフォーマットであり、目的はXML等と似ているが、「読みやすい」「書きやすい」「わかりやすい」という利点がある。
構造
インデントによって、階層の構造を表現している
YAMLファイルで扱うデータ型
- スカラー(文字列・数値・真偽値 etc)
- シーケンス(配列)
- マッピング(ハッシュ)
YAMLファイルの書き方
スカラー
YAMLはデータ型を自動的に判別してくれる
数字を文字列で扱いたい場合は'(シングルクォート)もしくは"(ダブルクォート)で囲むと強制的に文字列として認識してくれる#数値 int1: 2 int2: 123,456 #浮動小数点 float1: 0.1 #NULL nil1: nil nil2: ~ #Boolean bool1: true bool2: false bool3: yes bool4: no bool5: on bool6: of #日付 birthday: 1990-01-01 #タイムスタンプ stamp: 2020-12-01 10:00:00 + 0900 #文字列 str1:hoge str2:'true' str3:"333"配列
行頭に
-をいれることで、配列として扱うことが出来る。このときに-と値の間には半角スペースをいれること- Ruby - Java - PHP出力用メソッド
以降の出力例でも同様のものを使用
require 'yaml' p YAML.load_file('test.yml')出力結果
yaml_test.rb["Ruby", "Java", "PHP"]半角スペースでインデントを入れると、配列をネストさせることが出来る
- parent1 - - children1.1 - children1.2 - children1.3 - - grandchild1.3.1 - grandchild1.3.2 - grandchild1.3.3 - parent2出力結果
["parent1", ["children1.1", "children1.2", "children1.3", ["grandchild1.3.1", "grandchild1.3.2", "grandchild1.3.3"]], "parent2"]ハッシュ
:で「キー:値」のように区切って記述する
おそらくこれが、railsで扱う中で一番多いパターンだと思います。name: 名前 age: 年齢 gender: 性別 address: 住所出力結果
{"name"=>"名前", "age"=>"年齢", "gender"=>"性別", "address"=>"住所"}ハッシュに関しても半角スペースでインデントすることで、ネストさせることが可能
music: rock: ロック pop: ポップス jaz: ジャズ出力結果
{"music"=>{"rock"=>"ロック", "pop"=>"ポップス", "jaz"=>"ジャズ"}}アンカーとエイリアス
YAMLではアンカーとエイリアスを仕様することで、対象の構造に対して参照することが出来る
&nameでアンカーをつけ、*nameで参照することができる。Railsの
database.ymlなどではデフォルトで使われているdefault: &default adapter: postgresql encoding: unicode development: <<: *defaultちなみにここで登場している
<<を用いることでハッシュをマージ出来る。{"default"=>{"adapter"=>"postgresql", "encoding"=>"unicode"}, "development"=>{"adapter"=>"postgresql", "encoding"=>"unicode"}}参考文献
- 投稿日:2020-12-12T20:46:05+09:00
yamlファイルの書き方やと特徴
はじめに
現在Railsを勉強中です。
アプリケーションを作成する上で、モデルやカラム、その他メッセージ等を日本語で出力したり扱いたい場面が必ずあると思います。自分の場合、すぐに忘れてしまうためその都度ググって、書き方合ってたかを確認しています。
ぶっちゃけYAMLファイルの仕様等を詳細に把握していなくても、問題なく作業が進むので今までないがしろにしてきましたが、今回は基本的な部分を調べて、アウトプットしようと思います。YAMLファイルの特徴
分かりやすさ
構造化されたデータを扱うためのフォーマットであり、目的はXML等と似ているが、「読みやすい」「書きやすい」「わかりやすい」という利点がある。
構造
インデントによって、階層の構造を表現している
YAMLファイルで扱うデータ型
- スカラー(文字列・数値・真偽値 etc)
- シーケンス(配列)
- マッピング(ハッシュ)
YAMLファイルの書き方
スカラー
YAMLはデータ型を自動的に判別してくれる
数字を文字列で扱いたい場合は'(シングルクォート)もしくは"(ダブルクォート)で囲むと強制的に文字列として認識してくれる#数値 int1: 2 int2: 123,456 #浮動小数点 float1: 0.1 #NULL nil1: nil nil2: ~ #Boolean bool1: true bool2: false bool3: yes bool4: no bool5: on bool6: of #日付 birthday: 1990-01-01 #タイムスタンプ stamp: 2020-12-01 10:00:00 + 0900 #文字列 str1:hoge str2:'true' str3:"333"配列
行頭に
-をいれることで、配列として扱うことが出来る。このときに-と値の間には半角スペースをいれること- Ruby - Java - PHP出力用メソッド
以降の出力例でも同様のものを使用
require 'yaml' p YAML.load_file('test.yml')出力結果
yaml_test.rb["Ruby", "Java", "PHP"]半角スペースでインデントを入れると、配列をネストさせることが出来る
- parent1 - - children1.1 - children1.2 - children1.3 - - grandchild1.3.1 - grandchild1.3.2 - grandchild1.3.3 - parent2出力結果
["parent1", ["children1.1", "children1.2", "children1.3", ["grandchild1.3.1", "grandchild1.3.2", "grandchild1.3.3"]], "parent2"]ハッシュ
:で「キー:値」のように区切って記述する
おそらくこれが、railsで扱う中で一番多いパターンだと思います。name: 名前 age: 年齢 gender: 性別 address: 住所出力結果
{"name"=>"名前", "age"=>"年齢", "gender"=>"性別", "address"=>"住所"}ハッシュに関しても半角スペースでインデントすることで、ネストさせることが可能
music: rock: ロック pop: ポップス jaz: ジャズ出力結果
{"music"=>{"rock"=>"ロック", "pop"=>"ポップス", "jaz"=>"ジャズ"}}アンカーとエイリアス
YAMLではアンカーとエイリアスを仕様することで、対象の構造に対して参照することが出来る
&nameでアンカーをつけ、*nameで参照することができる。Railsの
database.ymlなどではデフォルトで使われているdefault: &default adapter: postgresql encoding: unicode development: <<: *defaultちなみにここで登場している
<<を用いることでハッシュをマージ出来る。{"default"=>{"adapter"=>"postgresql", "encoding"=>"unicode"}, "development"=>{"adapter"=>"postgresql", "encoding"=>"unicode"}}参考文献
- 投稿日:2020-12-12T19:38:47+09:00
【Rails】resourcesで設定したCRUDのルーティングヘルパーの表記の仕方
0.はじめに
ProgateでRuby on Railsを学習していると、URLパターンでの表記(例:新規登録画面→
/posts/new)で進んでいきます。私はその後共同開発に携わる機会があり、
初めてresoucesの存在とルーティングヘルパーの表記の仕方を学びました。私のような「ルーティングヘルパーって何?」という方に読んで頂ければと思います。
1.読んで欲しい人
・「ルーティングヘルパーって何?」「resourcesって何?」という人
・ProgateでRuby on Railsの学習を終えたての人2.使用環境
・mac.os バージョン10.15.6
・Ruby 2.5.1
・Rails 5.2.4.4
・psql (PostgreSQL) 12.53.resourcesとルーティングヘルパー
3.1 resourcesについて
〜共同開発中〜
私はサイト作成のCRUD処理の一部を担当することになりました。私:「routeも指定しないとだな」
私の中にはあれ(↓)が浮かんでいます。config/route.rbRails.application.routes.draw do get "posts/index" => "posts#index" get "posts/new" => "posts#new" get "posts/:id" => "posts#show" post "posts/create" => "posts#create" get "posts/:id/edit" => "posts#edit" post "posts/:id/update" => "posts#update" post "posts/:id/destroy" => "posts#destroy" endAさん:「resourcesで設定してあるので、routeの設定は終わってます〜」
私:「えっ」config/route.rbRails.application.routes.draw do 〜略〜 resources :posts end
resources :posts(今回は投稿に関するものなのでpostsとします)の一行で、コントローラのindex、show、new、edit、create、update、destroyアクションの宣言が完了してしまいます。
参照リンク:Railsガイド★2.リソースベースのルーティング
Progateには確かその説明はなかったので驚きました。3.2ルーティングヘルパーについて
〜プルリク後のコードレビューにて〜
Aさん:「パスの書き方ですが、基本的にはルーティングヘルパーを使ってもらった方が良いかと思います」
私:「えっ」
・・そうです、またまたあれを浮かべてコードを書いていました。app/views/posts/show.html.erb<div class="post-menus"> <%= link_to("編集", "/posts/#{@post.id}/edit") %> <%= link_to("削除", "/posts/#{@post.id}/destroy", {method: :post}) %> </div>★
link_toとは
link_to("編集","/posts/#{@post.id}/edit")(1番目の引数にリンクを設定する文字列、2番目の引数にURLを指定してlink_toメソッドを呼び出すこと)でリンクを作成します。
今回だと<a href ="/posts/#{@post.id}/edit">編集</a>のaタグが作成されます。
Rubyのコードなので「<%= %>」で囲みます。ルーティングヘルパーについては、ターミナルで
該当のディレクトリ $ rake routesとすると一覧を表示してくれます。
~略~ posts GET /posts(.:format) posts#index POST /posts(.:format) posts#create new_post GET /posts/new(.:format) posts#new edit_post GET /posts/:id/edit(.:format) posts#edit post GET /posts/:id(.:format) posts#show PATCH /posts/:id(.:format) posts#update PUT /posts/:id(.:format) posts#update DELETE /posts/:id(.:format) posts#destroy表にまとめると以下のようになります。ターミナル上の空欄の部分は上と同様という意味です。
URLパターン パスをつける HTTPメソッド コントローラ名#アクション 機能 posts posts_path GET posts#index 一覧 posts posts_path POST posts#create 登録 new_post new_posts_path GET posts#new 新規登録画面 edit_post edit_posts_path GET posts#edit 編集画面 post post_path GET posts#show 詳細 post post_path PATCHまたはPUT posts#update 更新 post post_path DELETE posts#destroy 削除 参照リンク1:link_toメソッドを使ったリンクの作成
参照リンク2:【Rails入門説明書】routesについて解説リンクを参照にルーティングヘルパーに変更します。
参照リンク:Railsガイド★2.3パスとURL用ヘルパーapp/views/posts/show.html.erb<div class="post-menus"> <%= link_to("編集", "/posts/#{@post.id}/edit") %> <%= link_to("削除", "/posts/#{@post.id}/destroy", {method: "post"}) %> </div>↓
app/views/posts/show.html.erb<div class="post-menus"> <%= link_to "編集", edit_post_path(@post) %> <%= link_to "削除", post_path(@post), method: :delete %> </div>・削除のmethodを
postからdeleteに変更
もともと、post "posts/:id/destroy" => "posts#destroy"というルーティングを想定していたためpostにしていましたが、削除のHTTPメソッドはdeleteなので変更しました。
・idパラメータを@postにする
削除に関してはid情報を持たせる必要があるので、モデルオブジェクト(今回では@post変数に格納されている)に変更しました。4.まとめ
他のアクションと一緒にまとめてみます。
matome.erb<div class="post-menus"> <%= link_to "一覧", posts_path %> <%= link_to "登録", posts_path, method: :post %> <%= link_to "新規登録", new_posts_path %> <%= link_to "編集", edit_post_path(@post) %> <%= link_to "詳細", post_path(@post) %> <%= link_to "更新", post_path(@post), method: :put %> <%= link_to "削除", post_path(@post), method: :delete %> </div>★
method: :putは:method => :putという表記でも可。
★showupdatedestroyはpost_pathを外し@postのみでも可。
パスをつける メソッド メソッド指定 id コントローラ名#アクション 機能 posts_path GET × × posts#index 一覧 posts_path POST ◎ × posts#create 登録 new_posts_path GET × × posts#new 新規登録画面 edit_posts_path GET × ◎ posts#edit 編集画面 post_path GET × ◎ posts#show 詳細 post_path PATCHまたはPUT ◎ ◎ posts#update 更新 post_path DELETE ◎ ◎ posts#destroy 削除 5.最後に
記事の感想や意見、ご指摘等あれば伝えていただけるとありがたいです。
読んでいただき、ありがとうございました。
- 投稿日:2020-12-12T18:32:18+09:00
アルゴリズムの勉強したので、Rubyで実装してみた
対象読者は"ruby BFS"とか"ruby 二分探索"とかで検索してたまたまたどり着いた方向け。
みんなのコンピュータ・サイエンスという書籍(5章)でソートや探索アルゴリズムについて学んだのでRubyで実装してみました。
実装したのアルゴリズムは次の通り
- 挿入ソート(Insert Sort)
- 二分探索(Binary Search)
- 幅優先探索(BFS)
- 深さ優先探索(DFS)
どんなアルゴリズムなのか?については他のネット記事に筆を譲るとして、今回はrubyでどう実装したのか? どのように動くのか?について書いてみようと思います。
5.1ソート
挿入ソート
コード
def insert_sort(ary) (0..ary.size-1).each do |i| (1..i).each.reverse_each do |j| break if ary[j-1] < ary[j] swap(ary, j-1, j) end end end慣習として、for文はrubyで使われないということなのでループはすべてeachに変更しました。
数字の大きさが順番通りになるまで、入れ替え続ける、という直感的な理解とも一致しているかと思います。解説
大雑把には、一気にすべてソート対象にせず、最小の範囲からはじめて、
1.ソートする
2.ソート対象を1つ分、広げる
を繰り返すイメージです。またソート自体は
1.一番新しい値がその一つ手前の値より小さければ、新しい値を一つ前にずらす
2.↑を新しい値が手前の値より大きい状態になるまで繰り返します。def insert_sort(ary) # 配列の前から一定の範囲をソート対象に含めるループ (0..ary.size-1).each do |i| # 実際にソートするループ (1..i).each.reverse_each do |j| # 新しい値が手前の値より大きければ、今与えられた範囲でのソートは完了 break if ary[j-1] < ary[j] # 入れ替え操作 swap(ary, j-1, j) end end end5.2の後に実行ファイルを添付しているので、putsメソッド駆使しながら実際に動かして確認してみてください。
5.2 探索
二分探索
def binary_search(items, key) i = items.size / 2 return items[i] if key == items[i] if key > items[i] sliced = items[i..-1] else sliced = items[0..i-1] end binary_search(sliced, key) end解説
前提としては、検索対象がソート済みであること。
i = items.size / 2渡された配列の半分の位置を取得します
return items[i] if key == items[i]取得した位置=真ん中にある値が探している値かどうか確認します
if key > items[i]探している値が、先ほど取得した真ん中の値より大きいか小さいか判定します。
# 探している値のほうが大きい場合 sliced = items[i..-1]ソート済みなので、探している値は、真ん中の値より後にあるはず。
配列の後ろ半分を取得します。# 探している値のほうが小さい場合 sliced = items[0..i-1]探している値は真ん中の値より前にあるはず。
配列の前半分を取得します。binary_search(sliced, key)取得した配列=半分になった配列を再度、再帰的に呼び出した二分探索メソッドに渡します。
求める値が見つかるまで、真ん中の値と求める値が一致しているか確認→配列を半分にする、を繰り返します。実行コード
.rbファイルにコピペして、そのまま実行すると動きます。
実行コード(クリックで開く)
# ランダム配列の生成 base_ary = (1..100).to_a ary = base_ary.sample(21) # 挿入ソート puts "ソート前:#{ary}\n\n" def swap(ary, x, y) ary[x], ary[y] = ary[y], ary[x] end def insert_sort(ary) (0..ary.size-1).each do |i| (1..i).each.reverse_each do |j| break if ary[j-1] < ary[j] swap(ary, j-1, j) end end end insert_sort(ary) puts "\nソート後:#{ary}" # 二分探索 puts "\n探索(binary_search)" def binary_search(items, key) return "#{key}は存在しません。" if items.size == 0 i = items.size / 2 return items[i] if key == items[i] if key > items[i] sliced = items[i..-1] print "検索中...右半分に存在すると判定: " else sliced = items[0..i-1] print "検索中...左半分に存在すると判定: " end puts "探索中の配列: #{sliced}" binary_search(sliced, key) end print "探したい数字を入力してください:" key = gets.chomp.to_i result = binary_search(ary, key) puts "#{result}が見つかりました!"
5.3 グラフ
Rubyでグラフを扱う
Nodeクラス
class Node attr_accessor :key, :value, :connected_nodes def initialize(key, value) @key = key @value = value @connected_nodes = [] # Nodeオブジェクトのkeyを格納する end def connect(key) connected_nodes.push(key) end endGraphクラス
Nodeクラスの集合を格納するためのクラス。
BFSやDFSなどNodeを取り扱うメソッドも後でここに追加します。class Graph attr_accessor :vertices def initialize(vertices = []) @vertices = vertices end def self.random_factory(node_amount = 10) ary = Array.new(node_amount){ |i| Node.new(i, rand(100)) } new(ary) end def connect(key1, key2) if (v1 = find_nodes(key1)) && (v2 = find_nodes(key2)) v1.connect(key2) v2.connect(key1) else false end end def find_nodes(key) vertices.find{|v| v.key == key } end def get_connected_nodes(node) keys = node.connected_nodes nodes = keys.map{|k| find_nodes(k)} end endDFS
ベタ書きコード
def dfs(start_node_key, key) next_nodes_stack = [] seen_nodes = [] start_node = vertices[start_node_key] next_nodes_stack.push(start_node) seen_nodes.append(start_node) while next_nodes_stack.any? node = next_nodes_stack.pop return node if node.key == key get_connected_nodes(node).each do |n| unless seen_nodes.include?(n) next_nodes_stack.push(n) seen_nodes.append(n) end end end return nil endBFS
ベタ書きコード
def dfs(start_node_key, key) next_nodes_stack = [] seen_nodes = [] start_node = vertices[start_node_key] next_nodes_stack.push(start_node) seen_nodes.append(start_node) while next_nodes_stack.any? node = next_nodes_stack.shift return node if node.key == key get_connected_nodes(node).each do |n| unless seen_nodes.include?(n) next_nodes_stack.push(n) seen_nodes.append(n) end end end return nil end解説
next_nodes_stack = [] seen_nodes = []BFS/DFSともに下記のノードを格納する配列を用意します
- 探しているノードかどうか、これから確認するノード
- すでに確認済みのノードstart_node = vertices[start_node_key] next_nodes_queue.push(start_node) seen_nodes.append(start_node)始点となるノードを用意した2つの配列に格納して、ループスタート
# DFS node = next_nodes_stack.pop # BFS node = next_nodes_queue.shiftこれから確認するノードを配列から取り出します
return node if node.key == keyノードのキーが探しているキーと一致しているか確認
get_connected_nodes(node).each do |n|探しているノードでなければ、今見たノードと接続しているノードを配列として呼び出します
unless seen_nodes.include?(n) next_nodes_stack.push(n) seen_nodes.append(n) end接続しているノードが確認済みの配列になければ、2つの配列(これから確認する/確認済み)に格納します。
以下、これから確認する配列からノードを取り出すところから、繰り返し。
実装面におけるBFSとDFSの違い
キューかスタックか、これに尽きます
コードでいうと次に確認するノードを配列から取り出すところのみ。# DFS node = next_nodes_stack.pop # BFS node = next_nodes_queue.shift確認リストの
- 後ろから取り出す=スタックの動き
- 先頭から取り出す=キューの動き後ろに格納されているのは、先程確認したばかりのノードと接続されているノード。
つまりノードを見たら更にその奥のノードを見ようとする動き=深さ優先先頭に格納されているのは、その逆=広さ優先
どっちの探索方法だと、どういうグラフ構造のときに早い、とかは添付のコードで色々と試してみてください。
リファクタリング後
Nodeクラス
class Node attr_accessor :key, :value, :connected_nodes def initialize(key, value) @key = key @value = value @connected_nodes = [] # Nodeオブジェクトのkeyを格納する end def connect(key) connected_nodes.push(key) end # 標準出力用 def shown_as_search_result puts self ? "発見しました! #{self} { key: #{key}, connected: #{connected_nodes} }" : "見つかりませんでした" end endGraphクラス
class Graph attr_accessor :nodes def initialize(nodes = []) @nodes = nodes end def initialize_search_memory @next_nodes = [] @seen_nodes = [] end def push_memory(node) @next_nodes.push(node) @seen_nodes.push(node) end def pop_next_nodes @next_nodes.pop end def dequeue_next_nodes @next_nodes.shift end def saw?(node) @seen_nodes.include?(node) end def next_nodes_exist? @next_nodes.any? end def connect(key1, key2) if (v1 = find_node(key1)) && (v2 = find_node(key2)) v1.connect(key2) v2.connect(key1) else false end end def find_node(key) nodes.find{|v| v.key == key } end def get_connected_nodes(node) keys = node.connected_nodes nodes = keys.map{|k| find_node(k)} end # 検索メソッド def dfs(start_key, key) initialize_search_memory start_node = find_node(start_key) push_memory(start_node) while next_nodes_exist? node = pop_next_nodes return node if node.key == key get_connected_nodes(node).each do |n| push_memory(n) unless saw?(n) end end end def bfs(start_key, key) initialize_search_memory start_node = find_node(start_key) push_memory(start_node) while next_nodes_exist? node = dequeue_next_nodes return node if node.key == key get_connected_nodes(node).each do |n| push_memory(n) unless saw?(n) end end end # ランダムにグラフを生成するメソッド def self.random_factory(node_amount = 10) ary = Array.new(node_amount){ |i| Node.new(i, rand(100)) } new(ary) end # ランダムにノードを接続する一連のメソッド def node_keys nodes.map{|v| v.key} end def random_connectors node_keys.combination(2).to_a end def generate_random_connection(connection_amount) random_ary = random_connectors.sample(connection_amount) random_ary.each {|c| connect(c[0], c[1])} end end※DFSとBFS別々に書いていますが、先述の通り、popかdequeueかだけ変えられるようにすればDRYに書けそうです。
実行
# グラフの生成とノード間の接続 @gragh = Graph.random_factory(10) @gragh.generate_random_connection(20) # DFS実行 @gragh.dfs(0, 3) # BFS実行 @gragh.dfs(0, 3)実行コード
.rbファイルにコピペして、そのまま実行すると動きます。
実行コード(クリックで開く)
class Graph attr_accessor :nodes def initialize(nodes = []) @nodes = nodes end def initialize_search_memory @next_nodes = [] @seen_nodes = [] end def push_memory(node) @next_nodes.push(node) @seen_nodes.push(node) end def pop_next_nodes @next_nodes.pop end def dequeue_next_nodes @next_nodes.shift end def saw?(node) @seen_nodes.include?(node) end def next_nodes_exist? @next_nodes.any? end def connect(key1, key2) if (v1 = find_node(key1)) && (v2 = find_node(key2)) v1.connect(key2) v2.connect(key1) else false end end def find_node(key) nodes.find{|v| v.key == key } end def get_connected_nodes(node) keys = node.connected_nodes nodes = keys.map{|k| find_node(k)} end # 検索メソッド def dfs(start_key, key) initialize_search_memory start_node = find_node(start_key) push_memory(start_node) while next_nodes_exist? node = pop_next_nodes return node if node.key == key get_connected_nodes(node).each do |n| push_memory(n) unless saw?(n) end end end def bfs(start_key, key) initialize_search_memory start_node = find_node(start_key) push_memory(start_node) while next_nodes_exist? node = dequeue_next_nodes return node if node.key == key get_connected_nodes(node).each do |n| push_memory(n) unless saw?(n) end end end # ランダムにグラフを生成するメソッド def self.random_factory(node_amount = 10) nodes = Array.new(node_amount) do |i| Node.new(i, rand(100)) end new(nodes) end # ランダムにノードを接続する一連のメソッド def node_keys nodes.map{|v| v.key} end def random_connectors node_keys.combination(2).to_a end def generate_random_connection(connection_amount) random_ary = random_connectors.sample(connection_amount) random_ary.each {|c| connect(c[0], c[1])} end # 標準出力用メソッド def show_nodes puts "<ノード一覧>" nodes.map{|v| puts "key: #{v.key}, connected: #{v.connected_nodes}"} end end class Node attr_accessor :key, :value, :connected_nodes def initialize(key, value) @key = key @value = value @connected_nodes = [] # Nodeオブジェクトのkeyを格納する end def connect(key) connected_nodes.push(key) end # 標準出力用 def shown_as_search_result puts self ? "発見しました! #{self} { key: #{key}, connected: #{connected_nodes} }" : "見つかりませんでした" end end @gragh = Graph.random_factory(10) @gragh.generate_random_connection(20) puts "\n" @gragh.show_nodes puts "\n" puts "DFS実行\n-------------------------------" found_node = @gragh.dfs(0, 3) found_node.shown_as_search_result puts "\n" @gragh.show_nodes puts "\n" puts "BFS実行\n-------------------------------" found_node = @gragh.bfs(0, 3) found_node.shown_as_search_result
感想
自分の勉強も兼ねて、書籍に例示されていたものをrubyで書きました。
実際に動かすことで、動きを感覚的につかめるのもあるし、rubyナイズして書くことでruby自体のメソッドの勉強にもなりました。
気が向いたらまた、やってみようとおもいます。書籍自体もおすすめなのでぜひ。
- 投稿日:2020-12-12T17:57:00+09:00
ストロングパラメーターに外部キーを渡す方法
概要
ポートフォリオに投稿機能を実装中、ストロングパラメーターを用いて値を渡そうとしたのですが
何度やっても投稿が保存されませんでした。def create @post = Post.new(post_params) binding.pry if @post.save flash[:success] = "投稿しました" redirect_to posts_path else render action: :new end endbinding.pryを差し込んでデバッグしたみたのですが結果は
permitted: trueじゃあなんで投稿できない。。
調べたところ単純な記述ミスでした。
軽くハマってしまったので自分の備忘録も兼ねての投稿です。
開発環境
- Rails 6.0.3.4
- ruby 2.6.3
実装方法
元々のコード
def post_params params.require(:post).permit(:user_id, :title, :content, :price, :img) end正しいコード
def post_params params.require(:post).permit(:title, :content, :price, :img).merge(user_id: current_user.id) end
外部キーのuser_idを渡す時にmergeメソッドを使ったら投稿できるようになりました!!
- 投稿日:2020-12-12T17:50:42+09:00
#3 Ruby始めました 「たのしいRuby 第6版」第二章 2−1 便利なオブジェクト
こちらの分かりやすい図で前回は、制御フロー、本日はデータ構造になります。
https://speakerdeck.com/machu/zerokarawakarurubychao-ru-men-falsebu-kifang?slide=7それでは書いたものは実際にどう処理されるのか処理系を使い学んで行きます。
https://tanoshiiruby.github.io/6/opal.html2−1 配列(Array)
aname=[]aname=["小","並","大"] p aname[1]並。
いつもこれしか言ってませんw配列は生データという感じですね。
中のものは01234で管理される。
並び替えも、取り出しもこの数値管理。aname=["大","並","小"] p aname aname[0]="小" aname[2]="大" aname[3]="特盛" p aname腹減った・・・特盛食ったことないけど、500円は躊躇しますw
実際にスーパーで500円分の牛ロース肉(オージー、アメリカ)買うとかなり買えますからねw
玉ねぎ3つ150円と一緒にぶっ込んで作ったら特盛何杯分?みたいなw2−2 ハッシュ(Hash)
hname={}・配列
配列タグ=管理番号+データ
・ハッシュ
ハッシュタグ=:シンボル+データ
ハッシュ名[キー]の違いですね。
コメント
このデータ構造ですが、私的に、一番わかっていないところでもあります。
スクリプト系開発は、ほとんどの場合、箱ものであり、器というものが用意されています。
DBでもOracle、Access、Excelシート、Web系でもHtml CSSなど全て器が用意されており、ソフトウェア内での処理になり、ソフトウェアの用意した器にデーターであるオブジェクトやら変数と言われるキャラクターを格納、放り込んで行くことになります。このデータ構造というものはその器になるのか?
データをまとめて扱う道具とあるので、きっと器、テーブルそのもののことだと思います。Excelが一番わかりやす例で例えるなら
配列が列で、ハッシュが行ですかね?
でもただのデータに列も行も関係ないかwparlのようなソフトウェアではない、開発環境のデータは、全てただのテキストファイルに格納されていきます。
そしてやはり取り出しやら表示はこうした配列、ハッシュで扱われるような気がしたので、そんな感じですかね。まあ配列とハッシュというものはコンテナと呼ばれているので、ただの器と思っていいと思います。
タンス型の小物入れみたいなもので、そこからハサミを取り出す、ペンを出すみたいなものですね。
そしてたくさんのコンテナにタグ(名前)をつけて保管する。
まあAccessで言うならテーブルのこと、Excelならセルですが、その器であるテーブルやセルとデータの関係は1:1ですが、プログラミングの場合、コンテナのとして扱われるので、若干ニュアンスが違いますが、テーブルやセルの中に複数のデータを入れて部品だけ取り出すみたいなものですね。
- 投稿日:2020-12-12T17:44:31+09:00
【Active Job】shoryuken + Amazon SQSを動かしてみた
Railsの非同期処理をActive Job + shoryuken + SQSで実装したのでメモを残します。※RailsアプリケーションはDocker環境で構築済みの前提です。環境構築はこちら。
※Active Jobとバックエンドの比較はこちら。環境
- Ruby 2.7.2
- Rails 6.0.3.4
- MySQL 8.0.20
- shoryuken 5.0.5
- Docker version 19.03.13
1. SQSの設定
まずAWSコンソールにログインし、画面上でSQSの設定をしていきます。
Amazon Simple Queue Service (SQS) は、完全マネージド型のメッセージキューイングサービスです。
画面や設定等は2020/11時点のものです。キューを作成します。
ここでSQSの詳細な設定は割愛しますが、要件に応じて様々な設定はできそうです。
キューの名前だけ指定し、その他はデフォルト値で設定します。これでSQS側の設定は完了です。
あとはローカル環境からアクセスするためのアクセスキーをIAMで発行しました。2. shoryukenの設定
まずGemを入れていきます。
Gemfilegem 'aws-sdk-sqs' gem 'shoryuken'
bundle installを実行し、shoryukenの設定ファイルも追加します。
AWSへの接続情報として、発行したアクセスキー等を環境変数に設定して利用します。
キーの管理はcredentialsでも良いかなと思いました。(プロダクトの方針次第)config/shoryuken.ymlaws: access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %> secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %> region: <%= ENV["AWS_REGION"] %> concurrency: 1 logfile: ./log/shoryuken.log :queues: - development_default3. Active Jobの設定
Active Jobで非同期処理を実装していきます。
config/application.rbrequire "active_job/railtie" module App class Application < Rails::Application ... config.active_job.queue_adapter = :shoryuken ... end end4. ジョブの作成と動作確認
ようやくジョブを作成します。
rails g job shoryuken_sampleapp/jobs/shoryuken_sample_job.rbclass ShoryukenSampleJob < ApplicationJob queue_as :default def perform puts '--------------------------------' puts '----------- 昇龍拳 ------------' puts '--------------------------------' end end続いて動作確認を行います。
$ bundle exec shoryuken -C config/shoryuken.yml(↑僕の環境では特にコマンド実行後、特に何も表示されませんでしたが、キュイーイングしたら動きました。)
別タブのターミナルでRailsコンソールからShoryukenSampleJobをキューイングしてみます。$ rails c Loading development environment (Rails 6.0.3.4) > ShoryukenSampleJob.set(wait: 5.second).perform_later Enqueued ShoryukenSampleJob (Job ID: ad754d56-8f50-4864-8b24-69479ef5c7f7) to Shoryuken(development_default) at 2020-11-25 11:32:39 UTC => #<ShoryukenSampleJob:0x000055821986c0b8 @arguments=[], @exception_executions={}, @executions=0, @job_id="ad754d56-8f50-4864-8b24-69479ef5c7f7", @priority=nil, @queue_name="development_default", @scheduled_at=1606303959.7208154>キューイングすると先ほど起動したshoryuken側でジョブが実行されたことが確認できました。
-------------------------------- ----------- 昇龍拳 ------------ --------------------------------
- 投稿日:2020-12-12T17:43:38+09:00
投稿アプリに基本的な検索機能を実装
概要
検索機能を実装する方法をまとめました。
例として、写真投稿アプリに基本的な検索機能を実装する方法を紹介します。
初学者なので、間違いがあればご指摘いただきたいです。
参照
以下のページを参照しました。ありがとうございました。
Railsで検索機能を実装する方法を現役エンジニアが解説【初心者向け】
完成イメージ
環境
- macOS Catalina 10.15.7
- ruby 2.6.5
- Rails 6.0.3.4
実装の流れ
- ルーティングを追加
- コントローラーにsearchメソッドを作成
- 検索フォームを作成
- 検索結果表示画面を作成
今回のコード
1. ルーティングを追加
routes.rbresources :photos do resources :comments, only: :create collection do get 'search' end end
- photosコントローラーにsearchアクションを追加します。
- collectionとすることで:idを含まないルーティングになります。
ちなみに、memberとすると:idを含むルーティングになります。
参照:https://qiita.com/k152744/items/141345e34fc0095217fe2. コントローラーにsearchメソッドを作成
photos_controller.rbdef search if params[:keyword].present? @photos = Photo.where('caption LIKE ?', "%#{params[:keyword]}%") @keyword = params[:keyword] else @photos = Photo.all end end
- 検索ボタンをクリックするとsearchアクションが呼び出され、入力した検索ワードをparams[:keyword]で取得できます。:keywordでなくとも好きなキー名をつけることができますが、今回は:keywordとしました。
- 今回は写真のキャプションで検索するため、Photo.where('caption LIKE ?', "%#{params[:keyword]}%")としました。これで、入力した検索ワードがキャプション内に含まれる写真をすべて取得できます。whereメソッドの使い方については、以下をご参照ください。https://techacademy.jp/magazine/22330
- 何も入力せずに検索ボタンをクリックした場合は、Photo.allですべての写真を取得することとしました。
- 入力した検索ワードを取得して検索結果表示画面で使用するため、@keyword = params[:keyword]としました。
3. 検索フォームを作成
index.html.erb# クラス名などは省略してあります <%= form_with url: search_photos_path, local: true, method: :get do |form| %> <%= form.text_field :keyword %> <%= form.submit "検索" %> <% end %>
- form.text_field :keywordとすることで、検索ボタンをクリックした後、:keywordキーに、入力した検索ワードが値として設定されます。コントローラーのsearchメソッドでparams[:keyword]と設定すると、その値を取得することができます。
4. 検索結果表示画面を作成
search.html.erb## クラス名などは省略してあります <h2>検索結果 "<%= @keyword %>"</h2> <ul> <% @photos.each do |photo| %> <li> <%= link_to photo_path(photo.id) do %> <%= image_tag photo.image.variant(gravity: :center, resize:"640x640^", crop:"640x640+0+0"), if photo.image.attached? %> <% end %> </li> <% end %> </ul>
- 上記のとおり、コントローラーのsearchメソッドで、検索ワードが含まれる写真を@photosに格納しました。@photos.each do |photo|で、それらひとつひとつの写真を取り出し、画面に表示させます。
- <%= @keyword %>で、入力した検索ワードも表示させました。
おわりに
以上が、今回行った基本的な検索機能実装の方法です。
ハッシュタグ検索など、より高度な検索も今後挑戦してみたいですね。
- 投稿日:2020-12-12T17:23:41+09:00
railsのmigrateファイルと仲良くなる
はじめに
現在、某プログラミングスクールに通い、Railsを学び初めて4ヶ月ほどの初学者です
個人開発、チーム開発でいつも同じところでRubyのRailから外れてしまうので、道標を残します
初めての投稿なので誤字脱字あったらすいません開発環境、他
IDE : Cloud9
Ruby : 2.6.3
Ruby on Rails : 5.2.4.4
rails new〜 でなんらかのアプリケーション作成済
テーブル作成済
sample_appという名前で作った簡易写真投稿アプリケーションでmigrateあたりをまとめていこうと思いますカラムの追加・削除・変更
カラムの追加
ここに"hoge"というカラムをstring型で追加するとします
ターミナルrails g migration AddhogeToLists hoge:stringdb/migrate/日時addhoge_to_lists.rb というファイルが作成されます
addcolumn :テーブル名, :カラム名, :データ型
こんな感じに書いてあげて、ターミナルrails db:migrateしっかりいてます
追加できました(上2行は気にしないでください)カラムの削除
次は先ほど追加したものを削除します
ターミナルrails g migration RemovehogeToLists hoge:stringターミナルrails db:migrateカラムの変更
上から2行目のbodyカラムのデータ型をstringからtextに変更しようと思いますターミナルrails g migration change_data_body_to_listsカラム追加・削除と同じように
db/migrate/日時_addhoge_to_lists.rb というファイルが作成されるので、
ターミナルrails db:migrateまとめます
カラム追加→rails g migration Addカラム名To複数形テーブル名 (なくてもok)カラム名:データ型
rails db:migrate
カラム削除→rails g migration Removeカラム名To複数形テーブル名 (なくてもok)カラム名:データ型
rails db:migrate
カラム変更→rails g migration change_data_カラム名to複数形テーブル名
rails db:migratemigrateファイルを削除する
原則migrateファイルは削除しません
テーブル内容の変更削除をする場合、上記のようにmigrateファイルを作って反映の繰り返しです
(migrateだけファイルの量膨大になる)
でも個人開発の時やgitにpush前で自分が作ったmigrateファイルの時を戻したいと思うこともあるはず…
今回は上記で作った3つのmigrateファイルがいらないので無かったことにしたいと思います今のmigrateファイルってどんな感じ?
ターミナルrails db:migrate:status
下3行目からが今さっき追加したファイルですね
Statusがupになっているのでバッチリ反映されていますrollbackの出番
ファイルがupの状態だと、schemaに反映されているのでファイルを削除するとエラーは起こりませんがデータベース問題でややこしくなります
下から2行目を消してみました
確かにmigrateフォルダからは消え去っているのですが、migrate statusには残っています
チーム開発の場合、このままだとデータベースの相違問題が起こるし、何より気持ち悪いのでファイルを直で消すのはやめましょう
schema
hogeカラムも復活してないのでやめましょうStatusをdownにすると消しても問題ありません
(down状態だとファイルの内容はどこにも反映されていない状態)では戻してみましょう
ターミナルrails db:rollbackこれで一つ前のmigrateファイルが巻き戻り、downに戻ります
でも今回は複数のファイルを巻き戻したいので複数行指定します
ターミナルrails db:rollback STEP=nnには巻き戻したい行数を入れます。
今回は3ですね
*巻き戻したぶん、全部downになりますが、最後にrails db:migrateをすると全部反映してくれるので問題ありません
やってみみました
…エラーかよ(´༎ຶོρ༎ຶོ`)
「migrationにchange_columnがあるから巻き戻せません
巻き戻すには
1、#changeメソッドのところを#upか#downにしてね
2、#reversibleメソッドを使ってね」
ということなので、changeをupに変えていざ実行
rollbackしてみる
できました
ステータス確認!
schema確認!
全てもとに戻りました
ファイルを消して、再びステータス確認!!
いい感じですね( ˊ̱˂˃ˋ̱ )
ぐちゃぐちゃになったmigrateファイルはリセットしましょう
ターミナルrails db:migrate:resetその後に
ターミナルrails db:migrateデータベースがまっさらになるので最初から投稿し直しになります
(もしseedファイルにデータを入れている場合、rails db:seedをやってあげてください)まとめます
migrateファイルを巻き戻す場合はrails db:rollbackを使う
複数行ならrails db:rollback STEP=n
def changeの場合だと巻き戻らないのでupやdownに変えてあげる
巻き戻したmigrateファイルをいじって、rails db:migrate
migrateファイルがぐちゃぐちゃになったり、データ投稿した内容を一旦リセットしたくなったら(投稿物が”テスト”ばかりとか)rails db:migrate:resetをやってみる終わりに
migrate周辺でいつも迷子になるのでまとめました
カラム名を打ち間違えてそのままmigrateとかやってしまうので、そういうところ改善したいですね
記念すべき1記事目を最後までご覧いただきましてありがとうございました
間違えてるところ、おかしなところがあれば教えてください参考
https://qiita.com/azusanakano/items/a2847e4e582b9a627e3a
https://qiita.com/yana_dev/items/c96594bbea3329ef0fec
- 投稿日:2020-12-12T16:12:54+09:00
Railsチュートリアル(第6版)攻略 〜アカウント作成編〜
本記事の目的
本記事の目的はRailsチュートリアルの学習をしている未経験エンジニアの学習・理解を支援することです。本記事では、Railsチュートリアルに沿って開発していくアプリの動作をテーマに区切って概説します。Railsチュートリアルを初めて走行する方にとっては、完成したアプリの動作全体をあらかじめ理解しておくことで、現在自分がチュートリアルのどの部分を学習しているのか、学習進捗の現在地を把握するマップとしてご活用いただけると思います。また、Railsチュートリアルを1周走破した方にとっては、自身が開発したアプリの動作全体像を振り返ることで、復習と理解の一助としてご活用いただければ幸いです。
アカウント作成の流れ
概要
ユーザー操作の観点で見ると、アカウント新規作成は下記3stepで進みます。
- ユーザーがサインアップへのリンクを踏み、新規ユーザー登録のための入力フォームが表示される。
- ユーザーがフォームに必要情報を入力して送信ボタンを押すと、入力フォームに記入したMailアドレスへ向けてアカウント有効化メールが送信される。
- ユーザーがアカウント有効化メールに記載されたリンクを踏むと、アカウントが有効化され、ユーザーの新規作成が完了する。
詳細
概要で説明した3stepでそれぞれでのアプリ動作の詳細を以下に説明します。
- ユーザーが/signupパスへのリンクをクリックすると、クライアント(Webブラウザ)からサーバーへ/signupパスへのGETリクエストが送信され、Usersコントローラ内のnewアクションが実行される。newアクションでは、Userモデルのインスタンスが新規作成され、newアクションに対応したview(new.html.erb)をRailsが自動で表示する。このとき、新規作成したuserインスタンスはviewへ渡される。なお、new.html.erbは新規ユーザー登録の入力フォームである。
- Railsは、new.html.erbへ渡されたuserインスタンスが空であるかどうか自動で判定を行う。空であった場合にRailsはユーザーの新規作成であると判断し、ユーザーが入力フォームの送信ボタンをクリックすると、/signupパスへのPOSTリクエストがサーバー側へ送信され、Usersコントローラ内のcreateアクションが実行される。このcreateアクション実行の直前にはアカウント有効化トークンが発行され、かつ、トークンがダイジェスト化され、ダイジェスト化したアカウント有効化トークン(アカウント有効化ダイジェスト)がDBに保存される。createアクションでは、フォームの入力情報を受け取り、受け取った情報から許可されたキーに対応した値のみを新規作成されたuserインスタンスに格納し、DBへの保存を試みる。そして、新規作成したユーザーのDB保存に成功すると、そのユーザーに向けてアカウント有効化メールを送信する。このアカウント有効化メールには、アカウント有効化のためのURLが添付されている。
- 新規ユーザーがアカウント有効化のURLをクリックすると、AccountActivationコントローラのeditアクションが実行される。また,当該URLにはアカウント有効化トークンとユーザーのemailアドレスが仕込まれており、サーバー側はGETリクエストを受けるとURLに仕込まれたemailアドレスを手がかりにしてDBから該当ユーザーの検索を行う。そして、検索にヒットしたユーザーが存在し、かつ、そのユーザーが未だアカウント有効化されておらず、加えて、受け取ったアカウント有効化トークンをダイジェスト化した値がDBに保存されていたアカウント有効化ダイジェストと一致した場合に、アカウントを有効化してセッションを立ててログインする。最後にuserのホーム画面へリダイレクトを行い、StaticPagesコントローラ内のhomeアクションを実行して、アクションに対応したview(home.html.erb)を表示する。
- 投稿日:2020-12-12T14:43:37+09:00
N + 1問題 - Railsで解説!
はじめに
Ruby on RailsなどDB(データベース)を使用するサーバーサイド言語で必ず上がる「N + 1問題」。
主に1対多のアソシエーション関係がある時に起こる問題です。
Railsでは一覧表示機能を含む1対多の関係が不可欠な機能が実装されるので、N + 1問題は理解しておくべきかと思います。対象者
rails tutorialを学習済みの方
SQL初学者の方使用環境
・ruby 2.6.4
・Rails 5.2.3
・MySQL 5.4N+1問題とは
一言で表すと、
「テーブル参照のSQLが大量に発行されてしまうこと」
実際に見ていきましょう。
今回は掲示板投稿機能を実装するためのrailsのファイルを使用します。まず、N + 1問題を引き起こす投稿機能モデルのコントローラーとビューのファイルの中身です。
app/controllers/boards_controller.rb(コントローラー)
class BoardsController < ApplicationController def index @boards = Board.all end endapp/views/boards/index.html.erb(ビュー)
<div class="row"> <div class="col-12"> <div class="row"> <% if @boards.present? %> <%= render @boards %> <% else %> <p><%= t('.no_board') %></p> <% end %> </div> </div> </div>/render @boards/
これによってrailsはファイルの同じディレクトリにある_board.html.erbファイルをパーシャルとして読み込んでくれる仕様になっています。
(よしなにやってくれるrailsの特徴ですね)同時にコントローラーで取得した@boardsを繰り返し処理で1つずつboardとしてパーシャルに渡します。
(長いですが、一言で言うと掲示板の投稿が1つずつ作成されているコードです)app/views/boards/_board.html.erb(ビュー)
<div class="col-sm-12 col-lg-4 mb-3"> <div id="board-id-<%= board.id %>"> <div class="card"> <%= image_tag 'board_placeholder.png', class: 'card-img-top', width: 300, height: 200 %> <div class="card-body"> <h4 class="card-title"> <%= link_to board.title, "#" %> </h4> <div class='mr10 float-right'> <%= link_to '#', id: 'button-edit-#{board.id}' do %> <%= icon 'fa', 'pen' %> <% end %> <%= link_to '#', id: 'button-delete-#{board.id}', method: :delete, data: {confirm: ''} do %> <%= icon 'fas', 'trash' %> <% end %> </div> <ul class="list-inline"> <li class="list-inline-item"><i class="far fa-user"></i> <%= board.user.decorate.full_name %> </li> <li class="list-inline-item"><i class="far fa-calendar"></i> <%= l board.created_at, format: :short %> </li> </ul> <p class="card-text"> <%= board.body %> </p> </div> </div> </div> </div>この掲示板一覧を表示するタイミングで発行されるSQL文が以下になります。
Rendering boards/index.html.erb within layouts/application Board Load (1.6ms) SELECT "boards".* FROM "boards" ↳ app/views/boards/index.html.erb:16 User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] ↳ app/views/boards/_board.html.erb:19 User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 3], ["LIMIT", 1]] ↳ app/views/boards/_board.html.erb:19 CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] ↳ app/views/boards/_board.html.erb:19 CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] ↳ app/views/boards/_board.html.erb:19 CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] ↳ app/views/boards/_board.html.erb:19 CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] ↳ app/views/boards/_board.html.erb:19 User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]] ↳ app/views/boards/_board.html.erb:19 CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]] ↳ app/views/boards/_board.html.erb:19 CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 3], ["LIMIT", 1]] ↳ app/views/boards/_board.html.erb:19 CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 3], ["LIMIT", 1]] ↳ app/views/boards/_board.html.erb:19 CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]] ↳ app/views/boards/_board.html.erb:19 CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 3], ["LIMIT", 1]] ↳ app/views/boards/_board.html.erb:19 CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 3], ["LIMIT", 1]] ↳ app/views/boards/_board.html.erb:19 CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] ↳ app/views/boards/_board.html.erb:19 CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]] ↳ app/views/boards/_board.html.erb:19 CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] ↳ app/views/boards/_board.html.erb:19 CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] ↳ app/views/boards/_board.html.erb:19 CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 3], ["LIMIT", 1]] ↳ app/views/boards/_board.html.erb:19 CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] ↳ app/views/boards/_board.html.erb:19 CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]めちゃくちゃ長い、、、
これrails tutorialではマイクロポストで投稿一覧を表示しますが、同じように長いSQL文が発行されます。なぜこのようなことが起きるのか?
諸悪の根源はたった一文です@boards = Board.allん?この部分はrails tutorialで習った通りじゃないですか?
何が問題かというと、@boardsに格納されている投稿(board)が呼び出される度に、そのboardがどのuserのものであるかを検索してい状態です。
つまり、投稿画面に遷移する時に、掲示板の全ての投稿を取得するために
Boardテーブル全体を参照するSQL文が1回発行される
Board Load (1.6ms) SELECT "boards".* FROM "boards"そして、のUsersテーブルを参照するSQLの文章が
掲示板の投稿の数(N回)だけ発行されている状態
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]この投稿数の数(N) + 最初の全体参照(1)が"N + 1問題"です。
解決方法
しかし、このN + 1問題は問題の文を以下のように書き換えるだけで簡単に解決できてしまいます。
@boards = Board.all.includes(:user)includesメソッドによりBoardテーブル参照時に、(boardの外部参照のためのuser_idカラムを元に)Userテーブルも同時に参照するようにしています。
よってテーブル参照回数も2回となります!!
(たとえ掲示板の投稿が増えたとしても)Board Load (3.0ms) SELECT "boards".* FROM "boards" ORDER BY "boards"."created_at" DESC ↳ app/views/boards/index.html.erb:16 User Load (0.9ms) SELECT "users".* FROM "users" WHERE "users"."id" IN (?, ?, ?) [["id", 2], ["id", 1], ["id", 3]] ↳ app/views/boards/index.html.erb:16SQL文の発行も2回で済みます。
1対多のアソシエーション関係を持つモデルを扱う場合は是非覚えておきましょう!
- 投稿日:2020-12-12T14:39:36+09:00
Nokogiri でクラス名を変更する
The 自分用メモ?
やりたいこと
h1タグの中にあるspanタグのクラス名を既存のものからすべてnumberに変更したい!やったこと
Nokogiriを使ってギコします!require 'nokogiri' # ?ここは環境に合わせて上手く読み込ませてください html = Nokogiri::HTML(File.read("ファイル名.html")) html.search('h1 span').each do |span| span['class'] = 'number' end参考にしたもの
? 痒いところに手が届きそうなサンプルコードがシンプルにまとまっていて、大変重宝しました✨
- 投稿日:2020-12-12T14:35:04+09:00
Ruby でも 1 <= x < 5 みたいに書きたい! ~やさしい黒魔術入門~
Python では数学のように
1 <= x < 5のような比較式を書くことができます1が、それをうらやましく思うことがあります。…ええ、もちろん1 <= x && x < 5と書けばいいだけの話です2し、あるいは(1...5).include?(x)のように書いた方が Ruby 的ですね。でも、時には Ruby 的にではなくて 数学的に書きたいんですよ!というわけで、Ruby の黒魔術を使って
1 <= x < 5のような比較式を書けるようにしてしまおう、というあまり実用性の無い記事です。そもそも、なぜ 1 <= x < 5 はエラーになるのか
さて、irb を起動して「
1 < 2 < 3」とでも入力してみましょう。irb(main):001:0> 1 < 2 < 3 NoMethodError (undefined method `<' for true:TrueClass)はい、当然エラーになりました。ですがどうしてエラーになってしまったのでしょう?「それが Ruby の言語仕様なのだから当たり前だ」と思わずに、改めて考えてみます。
1 < 2 < 3という式を与えられた Ruby は、まずは1 < 2の部分を計算します。これは実際には1.<(2)というメソッド呼び出しであり、true が返ってきます。つまり、1 < 2 < 3という式はまずtrue < 3(つまりはtrue.<(3))になるわけです。ところが true には
<というメソッドは定義されていないので、上述のように NoMethodError になってしまったわけですね。では、
1.<(2)の戻り値が true ではなく 2 になればどうでしょう。先ほど同様にまずは1 < 2 < 3の1 < 2の部分が計算され、戻り値が 2 なので2 < 3になります。つまり1 < 2と2 < 3の2つの比較が行われることになり、1 < 2 && 2 < 3と書いた場合と実質同じ比較がなされたことになります。つまり
<が本来 true を戻す状況では右辺値を戻すように改造すれば、目的の式を書くことができそうです。真の場合に < が右辺値を戻すように改造
さて、黒魔術を使っていきましょう。黒魔術師と言うと社会のルールから外れた無法者のイメージがありますが、現代の Ruby 黒魔術師はマナーをちゃんと守る必要があります。
というわけで組み込みクラスを改造するような場合には、Refinement を用いるのがマナーですね。
るりまサーチで「<=」と検索すると、
<=を定義している組み込みクラス・モジュールはModule,Comparable,Hash,Integer,Floatの 5 つのようですが、ModuleとHashはあまり必要性が無い気がする3ので、他のInteger,Float,Comparableのクラス・モジュールの<等を改造していきます。module ContinuousComparison refine Integer do def <(other); super && other; end def <=(other); super && other; end def >(other); super && other; end def >=(other); super && other; end end refine Float do def <(other); super && other; end def <=(other); super && other; end def >(other); super && other; end def >=(other); super && other; end end refine Comparable do def <(other); super && other; end def <=(other); super && other; end def >(other); super && other; end def >=(other); super && other; end end end既存の比較式への影響は?
Refinement を使ったとはいえ基幹部分の改造ですので、既存のコードが正しく動かなくなりはしないか心配ですね。ですが、この改造ではそれほど影響は無いはずです。なぜなら
<の戻り値自体は変化するものの、その真偽は変わらないからです。
- < がもともと false を戻す状況
- 変わらず、false が戻る。
- < がもともと true を戻す状況
- 右辺値が戻る。右辺値が偽 (false, nil) の場合は、そもそもエラーになるのでこの状況に該当しない。よって右辺値は必ず真である。
他の言語では 0 を偽として扱うことが多いですが、Ruby の「false と nil 以外はすべて真」という仕様のおかげで、こうした真似が可能になっています。
FalseClass を改造する
これで目的の式が書ける!…と言いたいところですが、下の式は NoMethodError になります。
2 < 1が false になり、false.<(3)になってしまうためです。2 < 1 < 3これを直すには、FalseClass を改造して
<メソッドを付け加える必要があります。このメソッドは、いかなる状況でも false を返しておけば大丈夫でしょう。refine FalseClass do def <(other); false; end def <=(other); false; end def >(other); false; end def >=(other); false; end end出来上がり
Refinement を使った黒魔術を発動するためには、呪文
usingを詠唱する必要があります。using ContinuousComparison 1 < 2 < 3 # => 3 (truely) 2 < 1 < 3 # => false使う人がいるかはわかりませんが、一応 gem 化しました。
== は改造しないの?
上のコードを見て「不等号は改造したのに、等号は改造しないの?」と疑問に思った方もいるかもしれません。確かに、例えば「x と y の値が等しくて、どちらも正」を
x == y > 0と書けた方が嬉しそうです。ところがこれについては2つ問題があったため、行いませんでした。
問題点1: a == b == c は SyntaxError になる。
a < b < cは Ruby の文法上問題無い式ですが、a == b == cは SyntaxError になってしまいます。これは、演算子には「左結合」「右結合」「非結合」があり、
<は左結合であるが==は非結合であることが理由です。(@znz さんに ruby-jp 上で教えていただきました。ありがとうございます。)
- 左結合: 例)
1 - 2 - 3は、(1 - 2) - 3と解釈され、-4 になる。- 右結合: 例)
2 ** 2 ** 3は、2 ** (2 ** 3)と解釈され、256 になる。- 非結合: 例)
1 == 2 == 3は、パースエラーとなる。参考:第9章 速習yacc
ただ、どうして
<と==で結合ルールに違いを持たせたのかは謎として残りました。4ともかく、
a == b == cと書くことはできません。(a == b) == cならエラーにはなりませんが、そんな書き方するくらいならa == b && b == cでいいです。問題点2: < と == では演算子の優先度が違う。
また、仮に
==も true 時に右辺値を戻すように改造したとします。その場合下のコードは真偽どちらになるでしょうか?1 == 0 < 1「偽に決まっているじゃないか」と思われた方も多いでしょうが、正解は真です。なぜなら
==より<の方が優先度が高いので、1 == 0 < 1は1 == (0 < 1)と解釈され、<が右辺値を戻すよう改造されたため1 == 1となり、真になるのです。このように予期しない結果になることがあるため、
==までは改造しないほうが良さそうです。ちなみに
-w オプション付きで実行すると、
comparison '<' after comparisonと警告が出ます。Warning.warn を書き換えて警告が出ないようにしようとしたのですが、そもそもパースの時点で警告が出ているようなので、
-rオプションでパース前にrequireするとかしない限り警告を抑えることはできないようです。
- 投稿日:2020-12-12T14:28:58+09:00
Active Adminの検索条件にenum値が反映されない時の対処方法
Active Adminの検索条件にenum値が反映されない現象が発生する。
現象詳細
enum値は以下の通り。
enum status: { not_started: 0, in_progress: 1, done: 9 }Active Admin内のfilter設定は以下の通り。(enum値の日本語化対応も込み)
filter :status, as: :select, collection:Inquiry.statuses_i18n.invertこの時に検索条件にenum値の選択項目が表示されるものの、絞り込みが動作しない現象が発生する。
原因
activeadminの検索にはransack gemを使用してが、ransackがうまくenumに対応できていないことが原因。
以下のように変更すると正しく動作する。filter :status, as: :select, collection:Inquiry.statuses_i18n.invert.map{ |k, v| [k, Inquiry.statuses[v]]}原因詳細
filter :status, as: :select, collection:Inquiry.statuses_i18n.invert上記フィルターを指定した際に、サーバへ送付されるパラメータは以下のようになる。
{"q"=>{"status_eq"=>"in_progress"}, "commit"=>"絞り込む", "order"=>"id_desc"}これを受けて、本来は、in_prgressに対応した1で検索をしてほしいが、実際のSQLを見ると以下のように'status'=0となっている。
SELECT COUNT(*) FROM (SELECT 1 AS one FROM `inquiries` WHERE `inquiries`.`status` = 0 LIMIT 30 OFFSET 0) subquery_for_countつまりransack側でin_progress → 1という変換を期待してたがenumの値をとってくれず、
数値にカラムに文字列を指定したので、0が指定された。解決方法
filterに指定したcollection:以降の部分は、コンソールで叩くと以下のようになる。
>Inquiry.statuses_i18n.invert {"未着手"=>"not_started", "進行中"=>"in_progress", "完了"=>"done"}collectionではkeyが表示内容、valueがサーバへ送信される値なので、ハッシュを以下のような形にできれば、ransackで正しく検索できる様になる。
{"未着手"=>0, "進行中"=>1, "完了"=>9}そのため、collectionに読み込ませる値を以下のようにすると、ransackでも読み取れる値にできる(arrayでもcollectionは問題なく読み込んでくれる模様)
Inquiry.statuses_i18n.invert.map{ |key, value| [key, Inquiry.statuses[value]]} > [["未着手", 0], ["進行中", 1], ["完了", 9]]参照:RansackはRailsのenumに対応していないっぽい
https://www.tom08.net/entry/2016/12/05/121746
- 投稿日:2020-12-12T14:14:26+09:00
【Ruby on Rails】Railsコマンドまとめ
はじめに
Rails 初心者用。
よく使うコマンドをまとめてみた。コマンド一覧
rails new
railsで新しいアプリケーションを作成する際に使用するコマンド
rails new [アプリケーション名] -オプションrails generate controller
ページを表示する為に必要なファイルを生成する。
コントローラーとかビュー。それに伴ってルーティングも作成される。
()内は指定しなくても大丈夫。後からファイルに指定すれば。
※「generate」の部分は「g」と省略可能。 → rails g controllerrails g controller [コントローラー名] ([アクション名])rails generate migration
マイグレーションファイルを生成する。
マイグレーションファイルとは、データベースのテーブルの情報を変更する際に使用するファイル。
※「generate」の部分は「g」と省略可能。 → rails g migrationrails g controller [マイグレーションファイル名]rails generate model
モデルファイルを生成する。
モデルファイルは、コントローラから受け取ったデータをデータベースに渡したり、反対にデータベースからの情報をコントローラに渡したりする役割がある。
※「generate」の部分は「g」と省略可能。 → rails g modelrails g model [モデル名]rails server
rails サーバーを起動する。
※「server」の部分は「s」と省略可能。 → rails srails srails console
コンソールを起動することができる。
オプションで「--sandbox」をつけると、データベースに変更を与えないようにすることができる。
※「console」の部分は「c」と省略可能。 → rails crails c
- 投稿日:2020-12-12T13:57:09+09:00
Active Adminで設定した心当たりのない検索条件が表示される。
Active Adminで設定した心当たりのない検索条件が表示される現象が発生した。
BOOKMARK
FACILITIES
BOOKMARK FACILITIESなんてパラメータ知らない。。。選択項目に変な値が出るし。。。
結論
Active Admin内でfilterの設定を行なっていないことが原因。
filterで検索条件を指定しておかないと全てのパラメータの検索条件が表示される。検索条件を明示したいなら、filterの設定を行う必要ある。
app/admin/users.rb # 絞り込み条件の項目設定 filter :email filter :nickname filter :birth_year filter :sex, as: :select, collection:User.sexes_i18n.invert.map{|key,value| [key,User.sexes[value]]}
- 投稿日:2020-12-12T12:41:15+09:00
VagrantでRuby2.7.2 (Ruby on Rails5.1.7)のローカル環境の構築
記事の目的
- 自分のメモ用
- dotinstallにおけるローカル環境構築講座に躓いた方に参考にしてもらう
初投稿になりますので、至らぬ所多々あると思いますがご了承下さい
PC環境
- macOS Catalina 10.15.7
- MacBook Pro (13-inch, 2020, Two Thunderbolt 3 ports)
- プロセッサ 1.4 GHz クアッドコアIntel Core i5
- メモリ 8GB
バージョン
- CentOs version: 7
- Ruby version: 2.7.2 (x86_64-linux)
- Rails version: 5.1.7
記事の内容
- Virtual Box, Vagrantのインストール
- Vagrant で Cent OS7 (Linux) をインストール
- CentOSの起動と停止方法
- Cyberduckをインストール
- rbenv をインストール
- ruby-buildプラグイン を追加
- Ruby をインストール
- Bundler のインストール
- Ruby on Rails のインストール
- Ruby on Rails の接続
構築手順
私は下記記事を参照に基本的には環境構築を実施した。
ただしCent OS内のフォルダーを見える化(操作しやすく)するため、上記記事に描かれていない、Cyberduckのインストールも合わせて実施した。
こちらの手順はドットインストールを参照いただきたいが、エラーがでた場合な下記記事を参考にして欲しい。
エラー
環境構築を実施していく中で、2点詰まったエラーがあったのでメモしておく
1. gem 'listen'が見つかりません
Could not load the 'listen' gem. Add `gem 'listen'` to the development group of your Gemfile (LoadError)解決法
cybetduckからGemfileを見つけて開き、下記をコード内に追加
group :development do
gem 'listen'
end
Gemfileは下記のようになるはずsource "https://rubygems.org" git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } gem "rails", "~> 5.1.0" group :development do gem 'listen' end2. gem 'sqlite3'が見つかりません
Add `gem 'sqlite3'` to your Gemfile解決法
先ほどと同様にcybetduckからGemfileを見つけて開き、下記をコード内に追加
gem 'sqlite3', '~> 1.3.6'
Gemfileは下記のようになるはずsource "https://rubygems.org" git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } gem "rails", "~> 5.1.0" gem 'sqlite3', '~> 1.3.6' group :development do gem 'listen' end3. Ruby on Rails の接続時エラー
最終接続時につながらずエラーが出る場合は下記記事を参照
私はNo.2までの処理を実行し、接続できた最後に
私自身、初学者であり、コマンドラインの使い方も分からずにインストール、アンインストールを繰り返してかなり苦労した。
参考サイトをご覧いただき、少しでも困っている方々の助けになればと思い投稿しました。
初投稿でもありますので、温かい目で見てください笑
























