20201212のRubyに関する記事は30件です。

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
 end

Rubyでは、変数への代入自体が戻り値を持つため、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
三項演算子
三項演算子
ぼっち演算子

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

Ruby のハッシュには 3 種類の記法がある,わけじゃない

Ruby の初心者向けの記事によく以下のような記述がありますが,間違いです。

ハッシュには以下のように 3 種類の記法があります。
{"foo" => 3, "bar" => 4}
{:foo => 3, :bar => 4}
{foo: 3, bar: 4}

二つ目と三つ目は同じハッシュを別の記法で書いていますが,一つ目はキーが文字列なので,記法が違っているのではなく,表しているモノが違います。

Ruby のハッシュは,キーとして文字列,シンボル,数値などさまざまなものが使えます。
そして,キーがシンボルの場合に限り,ハッシュ式においてキーと値のペアを指定する方法に二通りの記法がある,ということなのです。
なお,「ハッシュ式」というのは,上の例にあるような,{ } の中にキーと値のペアを列挙して Hash オブジェクトを表す式のことです。

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

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 行ずつ読み込んで実行していく」が間違っていることだけ分かってもらえれば記事の目的は果たせました。
ついでに,近年,処理系の技術がものすごく向上・複雑化していて,何がインタープリターで何がコンパイラーか,素人には分からなくなっている,ということも言えそうです。

識者のツッコミ・助言を歓迎します。


  1. Ruby の処理系はいくつかありますが,ここでは最も普及している CRuby を前提とします。 

  2. JIT は just-in-time の頭字語。 

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

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"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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
同様に falseFalseClass というクラスのインスタンスです。

えっと,厳密にいえば,true という識別子は「擬似変数」と呼ばれるものであって,この擬似変数が指しているオブジェクトが TrueClass のインスタンスです。
これを「trueTrueClass のインスタンス」と縮めてもとくに誤解の余地はないので,しばしばそう表現されます。

一方,Ruby ではどんなオブジェクトも真偽値として使うことができます。
そのルールは極めて単純で,nilfalse だけが偽であり,他のどんなオブジェクトも真である,というものです。
ですから,

if "hoge"
  puts "いえーい"
end

いえーい を表示します。

ではどうして truefalse があるかというと,これらはそれぞれ「真の代表値」「偽の代表値」であると考えればいいでしょう。

なお,さきほど「真偽値」という言葉を使いました。
この記事では,真偽性を利用する値という意味で使っています。平たく言うと,条件式の値のことですね。
しかし,人によっては「真偽値」を「true または false」という意味で使っていることもあるので気をつけてください。


  1. 当然「英語ではどうなんだよ」という疑問が涌きますが,この記事でいう真/偽に当たる言葉として,いちおう truthy/falsy があります。JavaScript 方面ではそこそこ使われているようです。 

  2. TrueClass はインスタンスをただ一つしか持てないことが保証されています。それが true です。FalseClassfalse の関係も同様。 

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

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_typetarget_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に関してもこのポリモーフィックを使って、どのモデルに対しても画像を紐付けれる様にしています。

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

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 に紐付いた文字列オブジェクトを破壊的に大文字化しました。

二つの名札には同じオブジェクトが紐付いているので,表示させるオブジェクトをどちらの名札で指定しても,当然同じ結果になります。


  1. 私はよくこのことをよく「ドッペルゲンガー かい」と言っています。 

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

【Ruby超入門】変数宣言と代入(ローカル変数)

変数宣言と代入

変数の宣言と代入をするには
  変数名 = 式や値
と書く。 

a = "aloha"
b = 1 + 2

これで 変数aとbにそれぞれ aloha  1+2 を代入。

ちなみに変数名はスネークケースで書くのが慣習となっている。
スネークケース: animal_dog
単語の間はアンダースコア_で区切る記法。
※アルファベット小文字,アンダースコアで書き始める。

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

ActiveRecordで生SQLを実行して結果を配列にキャストする

※ Railsのバージョンは5.2を想定

  1. 何はともあれ生SQLを実行するコードを書く

    result = ActiveRecord::Base.connection.select_all('SELECT x_id as id FROM users')
    
  2. 取得結果から配列にしたいSELECT結果を指定して抜き出し、新たな配列を生成する

    ids = result.map { |h| h['id'] }
    pp ids
    # [1, 2, 3, 4, 5, 6, 7]
    
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby超入門】ファイルでRubyプログラムを実行する

Rubyプログラムをファイルで実行する。

なんでもいいので,サンプルとなるコードを書き,「○○○○.rb」というファイル名で保存する。

○○○○.rb
a = "hello"
puts a

その後,ターミナルから直接ファイル名を指定することで,実行できる。

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

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

最後に

無事エラーは解消されグラフ機能を実装する事ができました!
エラー文は苦手意識があるのですが、しっかり読解しようとすれば案外解決方法を提示してくれてる場合が多いので今後はしっかりエラーと向き合っていきます!!

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

【Ruby超入門】irbで動かす。

irbでRubyを動かそう

Rubyには対話型評価環境が用意されているので,ターミナル上でRubyを実行できる。

ターミナル上でirbを実行しよう。

$ irb

と入力すると,irbが起動し,実行できる。

irb(main):001:0> 3+5
=> 8

気軽にRubyの実行ができるので試してみよう。
ちなみに終了するには,

irb(main):002:0> exit

と入力するとよい。

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

【Ruby超入門】irbでRubyプログラムを実行する

irbでRubyを動かそう

Rubyには対話型評価環境が用意されているので,ターミナル上でRubyを実行できる。

ターミナル上でirbを実行しよう。

$ irb

と入力すると,irbが起動し,実行できる。

irb(main):001:0> 3+5
=> 8

気軽にRubyの実行ができるので試してみよう。
ちなみに終了するには,

irb(main):002:0> exit

と入力するとよい。

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

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"}}
参考文献

プログラマーのための YAML 入門 (初級編)

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

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"}}
参考文献

プログラマーのための YAML 入門 (初級編)

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

【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.5

3.resourcesとルーティングヘルパー

3.1 resourcesについて

〜共同開発中〜
私はサイト作成のCRUD処理の一部を担当することになりました。

私:「routeも指定しないとだな」
私の中にはあれ(↓)が浮かんでいます。

config/route.rb
Rails.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"

end

Aさん:「resourcesで設定してあるので、routeの設定は終わってます〜」
私:「えっ」

config/route.rb
Rails.application.routes.draw do
〜略〜
  resources :posts
end

resources :posts(今回は投稿に関するものなのでpostsとします)の一行で、コントローラのindexshowneweditcreateupdatedestroyアクションの宣言が完了してしまいます。
参照リンク: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という表記でも可。
showupdatedestroypost_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.最後に

記事の感想や意見、ご指摘等あれば伝えていただけるとありがたいです。
読んでいただき、ありがとうございました。

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

アルゴリズムの勉強したので、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
end

5.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
end

Graphクラス

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
end

DFS

ベタ書きコード

  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
  end

BFS

ベタ書きコード

  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
end

Graphクラス

 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自体のメソッドの勉強にもなりました。
気が向いたらまた、やってみようとおもいます。

書籍自体もおすすめなのでぜひ。

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

ストロングパラメーターに外部キーを渡す方法

概要

ポートフォリオに投稿機能を実装中、ストロングパラメーターを用いて値を渡そうとしたのですが
何度やっても投稿が保存されませんでした。

  def create
    @post = Post.new(post_params)
    binding.pry
    if @post.save
      flash[:success] = "投稿しました"
      redirect_to posts_path
    else
      render action: :new
    end
  end

binding.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メソッドを使ったら投稿できるようになりました!!

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

#3 Ruby始めました 「たのしいRuby 第6版」第二章 2−1 便利なオブジェクト

こちらの分かりやすい図で前回は、制御フロー、本日はデータ構造になります。
https://speakerdeck.com/machu/zerokarawakarurubychao-ru-men-falsebu-kifang?slide=7

それでは書いたものは実際にどう処理されるのか処理系を使い学んで行きます。
https://tanoshiiruby.github.io/6/opal.html

2−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円と一緒にぶっ込んで作ったら特盛何杯分?みたいなw

2−2 ハッシュ(Hash)

hname={}

・配列
配列タグ=管理番号+データ
・ハッシュ
ハッシュタグ=:シンボル+データ
ハッシュ名[キー]

の違いですね。

コメント

このデータ構造ですが、私的に、一番わかっていないところでもあります。

スクリプト系開発は、ほとんどの場合、箱ものであり、器というものが用意されています。
DBでもOracle、Access、Excelシート、Web系でもHtml CSSなど全て器が用意されており、ソフトウェア内での処理になり、ソフトウェアの用意した器にデーターであるオブジェクトやら変数と言われるキャラクターを格納、放り込んで行くことになります。

このデータ構造というものはその器になるのか?
データをまとめて扱う道具とあるので、きっと器、テーブルそのもののことだと思います。

Excelが一番わかりやす例で例えるなら

配列が列で、ハッシュが行ですかね?
でもただのデータに列も行も関係ないかw

parlのようなソフトウェアではない、開発環境のデータは、全てただのテキストファイルに格納されていきます。
そしてやはり取り出しやら表示はこうした配列、ハッシュで扱われるような気がしたので、そんな感じですかね。

まあ配列とハッシュというものはコンテナと呼ばれているので、ただの器と思っていいと思います。

タンス型の小物入れみたいなもので、そこからハサミを取り出す、ペンを出すみたいなものですね。

そしてたくさんのコンテナにタグ(名前)をつけて保管する。

まあAccessで言うならテーブルのこと、Excelならセルですが、その器であるテーブルやセルとデータの関係は1:1ですが、プログラミングの場合、コンテナのとして扱われるので、若干ニュアンスが違いますが、テーブルやセルの中に複数のデータを入れて部品だけ取り出すみたいなものですね。

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

【Active Job】shoryuken + Amazon SQSを動かしてみた

sqs_settings.png
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_top.png

ここでSQSの詳細な設定は割愛しますが、要件に応じて様々な設定はできそうです。
キューの名前だけ指定し、その他はデフォルト値で設定します。

sqs_settings.png

これでSQS側の設定は完了です。
あとはローカル環境からアクセスするためのアクセスキーをIAMで発行しました。

2. shoryukenの設定

まずGemを入れていきます。

Gemfile
gem 'aws-sdk-sqs'
gem 'shoryuken'

bundle installを実行し、shoryukenの設定ファイルも追加します。
AWSへの接続情報として、発行したアクセスキー等を環境変数に設定して利用します。
キーの管理はcredentialsでも良いかなと思いました。(プロダクトの方針次第)

config/shoryuken.yml
aws:
  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_default

3. Active Jobの設定

Active Jobで非同期処理を実装していきます。

config/application.rb
require "active_job/railtie"

module App
  class Application < Rails::Application
    ...
    config.active_job.queue_adapter = :shoryuken
    ...
  end
end

4. ジョブの作成と動作確認

ようやくジョブを作成します。

rails g job shoryuken_sample
app/jobs/shoryuken_sample_job.rb
class 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側でジョブが実行されたことが確認できました。

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

投稿アプリに基本的な検索機能を実装

概要

検索機能を実装する方法をまとめました。

例として、写真投稿アプリに基本的な検索機能を実装する方法を紹介します。

初学者なので、間違いがあればご指摘いただきたいです。

参照

以下のページを参照しました。ありがとうございました。
Railsで検索機能を実装する方法を現役エンジニアが解説【初心者向け】

完成イメージ

今回は、投稿写真のキャプションで検索する機能を実装します。
貼り付けた画像_2020_12_12_19_32.png
Image from Gyazo

環境

  • macOS Catalina 10.15.7
  • ruby 2.6.5
  • Rails 6.0.3.4

実装の流れ

  • ルーティングを追加
  • コントローラーにsearchメソッドを作成
  • 検索フォームを作成
  • 検索結果表示画面を作成

今回のコード

1. ルーティングを追加

routes.rb
  resources :photos do
    resources :comments, only: :create
    collection do
      get 'search'
    end
  end
  1. photosコントローラーにsearchアクションを追加します。
  2. collectionとすることで:idを含まないルーティングになります。
    ちなみに、memberとすると:idを含むルーティングになります。
    参照:https://qiita.com/k152744/items/141345e34fc0095217fe

2. コントローラーにsearchメソッドを作成

photos_controller.rb
  def search
    if params[:keyword].present?
      @photos = Photo.where('caption LIKE ?', "%#{params[:keyword]}%")
      @keyword = params[:keyword]
    else
      @photos = Photo.all
    end
  end
  1. 検索ボタンをクリックするとsearchアクションが呼び出され、入力した検索ワードをparams[:keyword]で取得できます。:keywordでなくとも好きなキー名をつけることができますが、今回は:keywordとしました。
  2. 今回は写真のキャプションで検索するため、Photo.where('caption LIKE ?', "%#{params[:keyword]}%")としました。これで、入力した検索ワードがキャプション内に含まれる写真をすべて取得できます。whereメソッドの使い方については、以下をご参照ください。https://techacademy.jp/magazine/22330
  3. 何も入力せずに検索ボタンをクリックした場合は、Photo.allですべての写真を取得することとしました。
  4. 入力した検索ワードを取得して検索結果表示画面で使用するため、@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 %>
  1. 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>
  1. 上記のとおり、コントローラーのsearchメソッドで、検索ワードが含まれる写真を@photosに格納しました。@photos.each do |photo|で、それらひとつひとつの写真を取り出し、画面に表示させます。
  2. <%= @keyword %>で、入力した検索ワードも表示させました。

おわりに

以上が、今回行った基本的な検索機能実装の方法です。
ハッシュタグ検索など、より高度な検索も今後挑戦してみたいですね。

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

railsのmigrateファイルと仲良くなる

はじめに

現在、某プログラミングスクールに通い、Railsを学び初めて4ヶ月ほどの初学者です
個人開発、チーム開発でいつも同じところでRubyのRailから外れてしまうので、道標を残します
初めての投稿なので誤字脱字あったらすいません

開発環境、他

IDE : Cloud9
Ruby : 2.6.3
Ruby on Rails : 5.2.4.4
rails new〜 でなんらかのアプリケーション作成済
テーブル作成済
sample_appという名前で作った簡易写真投稿アプリケーションでmigrateあたりをまとめていこうと思います

カラムの追加・削除・変更

listsというテーブルを使います
スクリーンショット 2020-12-12 15.56.38.png

カラムの追加

ここに"hoge"というカラムをstring型で追加するとします

ターミナル
 rails g migration AddhogeToLists hoge:string

db/migrate/日時addhoge_to_lists.rb というファイルが作成されます
スクリーンショット 2020-12-12 15.40.44.png
add
column :テーブル名, :カラム名, :データ型
こんな感じに書いてあげて、

ターミナル
 rails db:migrate

schemaファイルをみてみると
スクリーンショット 2020-12-12 15.46.20.png

しっかりいてます
追加できました(上2行は気にしないでください)

カラムの削除

次は先ほど追加したものを削除します

ターミナル
 rails g migration RemovehogeToLists hoge:string

スクリーンショット 2020-12-12 15.55.55.png

ターミナル
 rails db:migrate

schema確認!
スクリーンショット 2020-12-12 15.56.38.png
消えてますね( ˊ̱˂˃ˋ̱ )

カラムの変更

スクリーンショット 2020-12-12 15.56.38.png
上から2行目のbodyカラムのデータ型をstringからtextに変更しようと思います

ターミナル
 rails g migration change_data_body_to_lists

カラム追加・削除と同じように
db/migrate/日時_addhoge_to_lists.rb というファイルが作成されるので、
スクリーンショット 2020-12-12 16.13.50.png

ターミナル
 rails db:migrate

schema確認!!
スクリーンショット 2020-12-12 16.17.00.png
しっかりと変わっていますね

まとめます

カラム追加→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:migrate

migrateファイルを削除する

原則migrateファイルは削除しません
テーブル内容の変更削除をする場合、上記のようにmigrateファイルを作って反映の繰り返しです
(migrateだけファイルの量膨大になる)
でも個人開発の時やgitにpush前で自分が作ったmigrateファイルの時を戻したいと思うこともあるはず…
今回は上記で作った3つのmigrateファイルがいらないので無かったことにしたいと思います

今のmigrateファイルってどんな感じ?

ターミナル
 rails db:migrate:status

スクリーンショット 2020-12-12 16.29.11.png
下3行目からが今さっき追加したファイルですね
Statusがupになっているのでバッチリ反映されています

rollbackの出番

ファイルがupの状態だと、schemaに反映されているのでファイルを削除するとエラーは起こりませんがデータベース問題でややこしくなります
下から2行目を消してみました
スクリーンショット 2020-12-12 16.42.04.png
確かにmigrateフォルダからは消え去っているのですが、migrate statusには残っています
チーム開発の場合、このままだとデータベースの相違問題が起こるし、何より気持ち悪いのでファイルを直で消すのはやめましょう
schema
スクリーンショット 2020-12-12 16.42.57.png
hogeカラムも復活してないのでやめましょう

Statusをdownにすると消しても問題ありません
(down状態だとファイルの内容はどこにも反映されていない状態)

では戻してみましょう

ターミナル
 rails db:rollback

これで一つ前のmigrateファイルが巻き戻り、downに戻ります

でも今回は複数のファイルを巻き戻したいので複数行指定します

ターミナル
 rails db:rollback STEPn

nには巻き戻したい行数を入れます。
今回は3ですね
*巻き戻したぶん、全部downになりますが、最後にrails db:migrateをすると全部反映してくれるので問題ありません
やってみみました
スクリーンショット 2020-12-12 16.49.54.png
…エラーかよ(´༎ຶོρ༎ຶོ`)
「migrationにchange_columnがあるから巻き戻せません
巻き戻すには
1、#changeメソッドのところを#upか#downにしてね
2、#reversibleメソッドを使ってね」
ということなので、changeをupに変えていざ実行
スクリーンショット 2020-12-12 16.57.52.png
rollbackしてみる
スクリーンショット 2020-12-12 17.00.56.png
できました
ステータス確認!
スクリーンショット 2020-12-12 17.02.27.png
schema確認!
スクリーンショット 2020-12-12 17.05.50.png
全てもとに戻りました
ファイルを消して、再びステータス確認!!
スクリーンショット 2020-12-12 17.07.08.png

いい感じですね( ˊ̱˂˃ˋ̱ )

ぐちゃぐちゃになった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

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

Railsチュートリアル(第6版)攻略 〜アカウント作成編〜

本記事の目的

本記事の目的はRailsチュートリアルの学習をしている未経験エンジニアの学習・理解を支援することです。本記事では、Railsチュートリアルに沿って開発していくアプリの動作をテーマに区切って概説します。Railsチュートリアルを初めて走行する方にとっては、完成したアプリの動作全体をあらかじめ理解しておくことで、現在自分がチュートリアルのどの部分を学習しているのか、学習進捗の現在地を把握するマップとしてご活用いただけると思います。また、Railsチュートリアルを1周走破した方にとっては、自身が開発したアプリの動作全体像を振り返ることで、復習と理解の一助としてご活用いただければ幸いです。

アカウント作成の流れ

概要

ユーザー操作の観点で見ると、アカウント新規作成は下記3stepで進みます。

  1. ユーザーがサインアップへのリンクを踏み、新規ユーザー登録のための入力フォームが表示される。
  2. ユーザーがフォームに必要情報を入力して送信ボタンを押すと、入力フォームに記入したMailアドレスへ向けてアカウント有効化メールが送信される。
  3. ユーザーがアカウント有効化メールに記載されたリンクを踏むと、アカウントが有効化され、ユーザーの新規作成が完了する。

詳細

概要で説明した3stepでそれぞれでのアプリ動作の詳細を以下に説明します。

  1. ユーザーが/signupパスへのリンクをクリックすると、クライアント(Webブラウザ)からサーバーへ/signupパスへのGETリクエストが送信され、Usersコントローラ内のnewアクションが実行される。newアクションでは、Userモデルのインスタンスが新規作成され、newアクションに対応したview(new.html.erb)をRailsが自動で表示する。このとき、新規作成したuserインスタンスはviewへ渡される。なお、new.html.erbは新規ユーザー登録の入力フォームである。
  2. Railsは、new.html.erbへ渡されたuserインスタンスが空であるかどうか自動で判定を行う。空であった場合にRailsはユーザーの新規作成であると判断し、ユーザーが入力フォームの送信ボタンをクリックすると、/signupパスへのPOSTリクエストがサーバー側へ送信され、Usersコントローラ内のcreateアクションが実行される。このcreateアクション実行の直前にはアカウント有効化トークンが発行され、かつ、トークンがダイジェスト化され、ダイジェスト化したアカウント有効化トークン(アカウント有効化ダイジェスト)がDBに保存される。createアクションでは、フォームの入力情報を受け取り、受け取った情報から許可されたキーに対応した値のみを新規作成されたuserインスタンスに格納し、DBへの保存を試みる。そして、新規作成したユーザーのDB保存に成功すると、そのユーザーに向けてアカウント有効化メールを送信する。このアカウント有効化メールには、アカウント有効化のためのURLが添付されている。
  3. 新規ユーザーがアカウント有効化のURLをクリックすると、AccountActivationコントローラのeditアクションが実行される。また,当該URLにはアカウント有効化トークンとユーザーのemailアドレスが仕込まれており、サーバー側はGETリクエストを受けるとURLに仕込まれたemailアドレスを手がかりにしてDBから該当ユーザーの検索を行う。そして、検索にヒットしたユーザーが存在し、かつ、そのユーザーが未だアカウント有効化されておらず、加えて、受け取ったアカウント有効化トークンをダイジェスト化した値がDBに保存されていたアカウント有効化ダイジェストと一致した場合に、アカウントを有効化してセッションを立ててログインする。最後にuserのホーム画面へリダイレクトを行い、StaticPagesコントローラ内のhomeアクションを実行して、アクションに対応したview(home.html.erb)を表示する。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.4

N+1問題とは

一言で表すと、

「テーブル参照のSQLが大量に発行されてしまうこと」

実際に見ていきましょう。
今回は掲示板投稿機能を実装するためのrailsのファイルを使用します。

まず、N + 1問題を引き起こす投稿機能モデルのコントローラーとビューのファイルの中身です。

app/controllers/boards_controller.rb(コントローラー)

class BoardsController < ApplicationController

  def index
    @boards = Board.all
  end
end

app/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:16

SQL文の発行も2回で済みます。
1対多のアソシエーション関係を持つモデルを扱う場合は是非覚えておきましょう!

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

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

参考にしたもの

? 痒いところに手が届きそうなサンプルコードがシンプルにまとまっていて、大変重宝しました✨

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

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 < 31 < 2 の部分が計算され、戻り値が 2 なので 2 < 3 になります。つまり 1 < 22 < 3 の2つの比較が行われることになり、1 < 2 && 2 < 3 と書いた場合と実質同じ比較がなされたことになります。

つまり < が本来 true を戻す状況では右辺値を戻すように改造すれば、目的の式を書くことができそうです。

真の場合に < が右辺値を戻すように改造

さて、黒魔術を使っていきましょう。黒魔術師と言うと社会のルールから外れた無法者のイメージがありますが、現代の Ruby 黒魔術師はマナーをちゃんと守る必要があります。

というわけで組み込みクラスを改造するような場合には、Refinement を用いるのがマナーですね。

るりまサーチで「<=」と検索すると、<= を定義している組み込みクラス・モジュールは Module, Comparable, Hash, Integer, Float の 5 つのようですが、ModuleHash はあまり必要性が無い気がする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 < 11 == (0 < 1) と解釈され、< が右辺値を戻すよう改造されたため 1 == 1 となり、真になるのです。

このように予期しない結果になることがあるため、== までは改造しないほうが良さそうです。

ちなみに

-w オプション付きで実行すると、comparison '<' after comparison と警告が出ます。

Warning.warn を書き換えて警告が出ないようにしようとしたのですが、そもそもパースの時点で警告が出ているようなので、-r オプションでパース前に require するとかしない限り警告を抑えることはできないようです。


  1. Julia では Python よりもさらに数学的に、1 ≤ x < 5 と書けます。 

  2. ただし 1 <= x && x < 5 という書き方では、x が呼ぶたびに値の変わりうるようなメソッドだった場合に意図しない結果になり得ます。 

  3. 「このクラス X が A を継承していて、なおかつ B の先祖クラスかどうか知りたい! そしてそれを A < X < B と書きたい!」と今まで一度でも思ったことがある方が居ましたらご連絡ください。 

  4. 想像ですが、< を比較以外の用途に使うようなメソッドを作った場合の使い勝手を良くするためですかね? 

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

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値の選択項目が表示されるものの、絞り込みが動作しない現象が発生する。
スクリーンショット 2020-12-12 14.15.24.png

原因

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

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

【Ruby on Rails】Railsコマンドまとめ 

はじめに

Rails 初心者用。
よく使うコマンドをまとめてみた。

コマンド一覧

rails new

railsで新しいアプリケーションを作成する際に使用するコマンド

rails new [アプリケーション名] -オプション

rails generate controller

ページを表示する為に必要なファイルを生成する。
コントローラーとかビュー。それに伴ってルーティングも作成される。
()内は指定しなくても大丈夫。後からファイルに指定すれば。
※「generate」の部分は「g」と省略可能。 → rails g controller

rails g controller [コントローラー名] ([アクション名])

rails generate migration

マイグレーションファイルを生成する。
マイグレーションファイルとは、データベースのテーブルの情報を変更する際に使用するファイル。
※「generate」の部分は「g」と省略可能。 → rails g migration

rails g controller [マイグレーションファイル名] 

rails generate model

モデルファイルを生成する。
モデルファイルは、コントローラから受け取ったデータをデータベースに渡したり、反対にデータベースからの情報をコントローラに渡したりする役割がある。
※「generate」の部分は「g」と省略可能。 → rails g model

rails g model [モデル名]

rails server

rails サーバーを起動する。
※「server」の部分は「s」と省略可能。 → rails s

rails s

rails console

コンソールを起動することができる。
オプションで「--sandbox」をつけると、データベースに変更を与えないようにすることができる。
※「console」の部分は「c」と省略可能。 → rails c

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

Active Adminで設定した心当たりのない検索条件が表示される。

Active Adminで設定した心当たりのない検索条件が表示される現象が発生した。
101763720-84ed4080-3b22-11eb-8c53-983a21187eaf.png

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-12 13.52.35.png
設定した検索条件のみ表示されることが確認できた。
これで解決!

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

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

記事の内容

  1. Virtual Box, Vagrantのインストール
  2. Vagrant で Cent OS7 (Linux) をインストール
  3. CentOSの起動と停止方法
  4. Cyberduckをインストール
  5. rbenv をインストール
  6. ruby-buildプラグイン を追加
  7. Ruby をインストール
  8. Bundler のインストール
  9. Ruby on Rails のインストール
  10. Ruby on Rails の接続

構築手順

私は下記記事を参照に基本的には環境構築を実施した。

https://www.sejuku.net/blog/39936#_Ruby_on_Rails

ただしCent OS内のフォルダーを見える化(操作しやすく)するため、上記記事に描かれていない、Cyberduckのインストールも合わせて実施した。

こちらの手順はドットインストールを参照いただきたいが、エラーがでた場合な下記記事を参考にして欲しい。

https://qiita.com/Lassieena/items/603fe89df26b59ca06f7

エラー

環境構築を実施していく中で、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'
end 

2. 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'
end

3. Ruby on Rails の接続時エラー

最終接続時につながらずエラーが出る場合は下記記事を参照
私はNo.2までの処理を実行し、接続できた

https://qiita.com/Ago0727/items/325df5e39e3406fa16d2

最後に

私自身、初学者であり、コマンドラインの使い方も分からずにインストール、アンインストールを繰り返してかなり苦労した。

参考サイトをご覧いただき、少しでも困っている方々の助けになればと思い投稿しました。

初投稿でもありますので、温かい目で見てください笑

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